Advanced Views
Tree views
Tree views 可以通過增加 attribute 來提供更深度的定製化
- decoration-{$name}
允許針對不同的record的特定值,來修改record 對應 行 的樣式
值爲普通的python 表達式,此表達式將會依次作用於每一個record,record 的各項屬性,將會作爲 context 傳入此表達式,如果爲表達式值爲 True,對應行的樣式就會修改。除了 record 的各項屬性,Odoo也會自動傳入,uid
和current_date
(as string yyyy-MM-dd 傳入)
{$name}
可以是bf
( font-weight: bold )it
(font-style: italic),也可以是Bootstrap 的一些樣式(danger
,info
,muted
,primary
,success
,warning
)
<tree string="Idea Categories" decoration-info="state=='draft'"
decoration-danger="state=='trashed'">
<field name="name"/>
<field name="state"/>
</tree>
- editable
值爲"top"
或者"bottom"
,可以讓 Tree view 就地進入編輯狀態,而不是點擊進入 Form view 去修改,這兩個就是 新的record 出現的位置。
練習 5-1
將 session tree 添加一定的色彩,當session duration < 5是,這一行 顯示爲 blue, duration >15 , 將其顯示爲 red
openacademy/views/openacademy.xml
<field name="name">session.tree</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<tree string="Session Tree" decoration-info="duration<5" decoration-danger="duration>15"> <!-- 修改 -->
<field name="name"/>
<field name="course_id"/>
<field name="duration" invisible="1"/> <!-- 修改 -->
<field name="taken_seats" widget="progressbar"/>
</tree>
</field>
Calendars
將 record 以 calendar events 的方式展現,常用的 attributes 有:
color
The name of the field used for color segmentation. Colors are automatically distributed to events, but events in the same color segment (records which have the same value for their @color field) will be given the same color.date_start
record’s field holding the start date/time for the eventdate_stop
(可選)
record’s field holding the end date/time for the event
field (to define the label for each calendar event)
<calendar string="Ideas" date_start="invent_date" color="inventor_id">
<field name="name"/>
</calendar>
練習 5-2
給session 添加一個 calendar view,
- 添加一個 computed field
end_date
,根據start_date
和duration
計算而來 - 添加 calendar view
openacademy/models.py
# -*- coding: utf-8 -*-
from datetime import timedelta # new line
from openerp import models, fields, api, exceptions
class Course(models.Model):
attendee_ids = fields.Many2many('res.partner', string="Attendees")
taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')
end_date = fields.Date(string="End Date", store=True,
compute='_get_end_date', inverse='_set_end_date') # new field
@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
},
}
# new add
@api.depends('start_date', 'duration')
def _get_end_date(self):
for r in self:
if not (r.start_date and r.duration):
r.end_date = r.start_date
continue
# Add duration to start_date, but: Monday + 5 days = Saturday, so
# subtract one second to get on Friday instead
start = fields.Datetime.from_string(r.start_date)
duration = timedelta(days=r.duration, seconds=-1)
r.end_date = start + duration
def _set_end_date(self):
for r in self:
if not (r.start_date and r.end_date):
continue
# Compute the difference between dates, but: Friday - Monday = 4 days,
# so add one day to get 5 days instead
start_date = fields.Datetime.from_string(r.start_date)
end_date = fields.Datetime.from_string(r.end_date)
r.duration = (end_date - start_date).days + 1
# end
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
for r in self:
openacademy/views/openacademy.xml
</field>
</record>
<!-- calendar view 新增-->
<record model="ir.ui.view" id="session_calendar_view">
<field name="name">session.calendar</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<calendar string="Session Calendar" date_start="start_date"
date_stop="end_date"
color="instructor_id">
<field name="name"/>
</calendar>
</field>
</record>
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field> <!-- 修改 -->
</record>
<menuitem id="session_menu" name="Sessions"
Search views
search view 中的 field 可以有一個 filter_domain
屬性,可以覆蓋掉 Odoo自動爲這個field設置的 search domain。 在 filter_domain 中 self 代表的就是 用戶在搜索框中輸入的 數據。
search view 中也可以有 <filter>
元素, 用以預先定義一些快捷搜索,在其中必須有以下屬性中的一個
domain
就給 search 提供篩選條件context
給當前 search 提供 context ,通常使用group_by
,來分組搜素結果。
<search string="Ideas">
<field name="name"/>
<field name="description" string="Name and description"
filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
<field name="inventor_id"/>
<field name="country_id" widget="selection"/>
<filter name="my_ideas" string="My Ideas"
domain="[('inventor_id', '=', uid)]"/>
<group string="Group By">
<filter name="group_by_inventor" string="Inventor"
context="{'group_by': 'inventor_id'}"/>
</group>
</search>
如果想要在 action 中不使用 默認的 search view, 可以明確指定 search_view_id
的值
action 中同樣可以設定 search view 的初始行爲,只需要在 action 的 context
中傳入一定的參數即可,如: search_default_field_name
,
練習 5-3
- 添加一個button,使其可以將 當前登錄用戶的 course 顯示出來,再將其設置爲默認。
- 添加一個button,使course 可以根據用戶 分組顯示
openacademy/views/openacademy.xml
<search>
<field name="name"/>
<field name="description"/>
<!-- 添加 -->
<filter name="my_courses" string="My Courses"
domain="[('responsible_id', '=', uid)]"/>
<group string="Group By">
<filter name="by_responsible" string="Responsible"
context="{'group_by': 'responsible_id'}"/>
</group>
<!-- 結束 -->
</search>
</field>
</record>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context" eval="{'search_default_my_courses': 1}"/> <!-- 添加 -->
<field name="help" type="html">
<p class="oe_view_nocontent_create">Create the first course
</p>
Gantt
由於 Odoo community 版本不支持 gantt, 所以我就不翻譯了。
https://github.com/odoo/odoo/issues/9640
Graph views
Graph view 提供了一種全局的概覽 以及 數據的分析,root element 是 <graph>
它有4種展現方式,修改默認的展現方式可以通過設置 type
屬性
Bar (默認)
在 bar chart 中,第一個是用來橫向分組的,剩餘的都是在 第一個分組的情況下,展現各自的數據。
默認的 bar 是 side-by-side, 但可以在<graph stacked='True'>
來修改。Line 2D 線
- Pie 2D 餅圖
Graph view 裏面的 <field>
都會被強制賦予 type 屬性,兩種選擇:
row
(默認) the field should be aggregated by defaultmeasure
the field should be aggregated rather than grouped on
<graph string="Total idea score by Inventor">
<field name="inventor_id"/>
<field name="score" type="measure"/>
</graph>
注意: graph 不支持 computed field, 除非 此 computed field 設置了 store=True 屬性
練習 5-4
在 給 session 添加一個graph view,在每個 course 下,顯示 attendees number
- 添加 attendees_count which computed field but stored
- add 相關view
openacademy/models.py
hours = fields.Float(string="Duration in hours",
compute='_get_hours', inverse='_set_hours')
# 新增
attendees_count = fields.Integer(
string="Attendees count", compute='_get_attendees_count', store=True)
@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
for r in self:
for r in self:
r.duration = r.hours / 24
# 新增
@api.depends('attendee_ids')
def _get_attendees_count(self):
for r in self:
r.attendees_count = len(r.attendee_ids)
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
for r in self:
openacademy/views/openacademy.xml
</field>
</record>
# 新增
<record model="ir.ui.view" id="openacademy_session_graph_view">
<field name="name">openacademy.session.graph</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<graph string="Participations by Courses">
<field name="course_id"/>
<field name="attendees_count" type="measure"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt,graph</field> # 修改
</record>
<menuitem id="session_menu" name="Sessions"
Kanban
通常用來顯示,任務進度,生產流程等,root element是 <kanban>
kanban view 將一對的 cards 已 coloumn 的形式展現,每一 card 代表一個 record ,每一個 column 都代表 按照 某個 field 的分類。
比如, project 可能按照 stage 狀態被分類(column 就是 stage),或者被 負責人responsible (column 就是 user), 等。
kanban view 爲每一個 card 定義了類似 form view的 效果,當然也支持原生HTML 和 QWeb
練習 5-5
- 添加一個 integer
color
字段到 Session model - 添加kanban view, update action
openacademy/models.py
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
active = fields.Boolean(default=True)
color = fields.Integer() # new line
instructor_id = fields.Many2one('res.partner', string="Instructor",
domain=['|', ('instructor', '=', True),
openacademy/views/openacademy.xml
</record>
<!-- 新增 -->
<record model="ir.ui.view" id="view_openacad_session_kanban">
<field name="name">openacad.session.kanban</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<kanban default_group_by="course_id">
<field name="color"/>
<templates>
<t t-name="kanban-box">
<div
t-attf-class="oe_kanban_color_{{kanban_getcolor(record.color.raw_value)}}
oe_kanban_global_click_edit oe_semantic_html_override
oe_kanban_card {{record.group_fancy==1 ? 'oe_kanban_card_fancy' : ''}}">
<div class="oe_dropdown_kanban">
<!-- dropdown menu -->
<div class="oe_dropdown_toggle">
<i class="fa fa-bars fa-lg"/>
<ul class="oe_dropdown_menu">
<li>
<a type="delete">Delete</a>
</li>
<li>
<ul class="oe_kanban_colorpicker"
data-field="color"/>
</li>
</ul>
</div>
<div class="oe_clear"></div>
</div>
<div t-attf-class="oe_kanban_content">
<!-- title -->
Session name:
<field name="name"/>
<br/>
Start date:
<field name="start_date"/>
<br/>
duration:
<field name="duration"/>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt,graph,kanban</field> <!-- 修改 -->
</record>
<menuitem id="session_menu" name="Sessions"
parent="openacademy_menu"