本文将学习如何为用户创建图形化界面来与图书应用交互。我们将了解不同视图类型和小组件(widgets)之间的差别,以及如何使用它们来提供更优的用户体验。
本文主要内容有:
- 菜单项
- 窗口操作(Window Actions)
- 表单视图结构
- 字段
- 按钮和智能按钮
- 动态视图元素
- 列表视图
- 搜索视图
- 其它视图类型
开发准备
我们将继续使用library_checkout插件模块,它已经有了模型层,现在需要视图层来实现用户界面。本文中的代码基于第八章 Odoo 12开发之业务逻辑 - 业务流程的支持,相关代码请参见 GitHub 仓库,本章完成后代码也请参见 GitHub仓库。
菜单项
用户界面的入口是菜单项,菜单项形成一个层级结构,最顶级项为应用,其下一级为每个应用的主菜单。还可以添加更深的子菜单。可操作菜单与窗口操作关联,它告诉客户端在点击了菜单项后应执行什么操作。
菜单项存储在ir.ui.menu模型中,可通过Settings > Technical > User Interface > Menu Items菜单进行查看。
library_app模块为图书创建了一个顶级菜单,library_checkout插件模块添加了借阅和借阅阶段的菜单项。在library_checkout/views/library_menu.xml文件中,借阅的菜单项 XML 代码如下:
<menuitem id="menu_library_checkout"
name="Checkout"
action="action_library_checkout"
parent="library_app.menu_library" />
这里有一个
- name是展示在用户界面中的菜单项标题
- action是点击菜单项时运行的窗口操作的XML ID
- parent是父级菜单项的XML ID。本例中父级项由其它模块创建,因此们使用了完整的XML ID,
. 进行引用。
还有以下可用属性:
- sequence设置一个数字来在展示菜单项时进行排序,如sequence=“10”
- groups是一个逗号分隔的可访问菜单项安全组的XML ID列表,如groups=“library_app.library_group_user, library_app.library_group_manager”
- web_icon是菜单项的图标,仅用于企业版的顶级菜单项,如web_icon=“library_app,static/description/icon.png”
窗口操作(Window Actions)
窗口操作给 GUI(图形化用户界面)客户端操作指令,通常用于菜单项或视图中的按钮。它告诉 GUI 所作用的模型以及要显示的视图。这些操作可以通过域过滤器过滤出可用记录,设置默认值以及从上下文属性中过滤。窗口操作存储在ir.actions.act_window模型中,可通过Settings > Technical > Actions > Window Actions菜单进行查看。
在library_checkout/views/library_menu.xml文件中,我们可以找到借阅菜单项中使用的窗口操作,我们需要对其进行修改来启用本文中将添加的视图类型:
<act_window id="action_library_checkout"
name="Checkouts"
res_model="library.checkout"
view_mode="tree,form,activity,calendar,graph,pivot" />
窗口操作通常像以上这样使用<act_window>快捷标签创建。这里修改”tree, form”为更大的列表”tree, form, activity, calendar, graph, pivot”。以上使用的窗口操作属性有:
- name是通过操作打开的视图中显示的标题
- res_model是目标模型的标识符
- view_mode是一个逗号分隔的可用视图类型列表。第一项为默认打开时的视图。
窗口操作还有一些其它属性:
- target:如果设置为 new,会在弹出的对话框窗口中打开视图,例如target=“new”。默认值是current,在主内容区行内打开视图。
- context:为目标视图设置上下文信息,可设置默认值或启用过滤器等,例如context=”{‘default_user_id’: uid}”。
- domain:是对可在打开视图中浏览的记录强制过滤的域表达式,例如domain=”[(‘user_id’, ’=’, uid)]”。
- limit:列表视图中每页显示的记录数,例如limit=“80”。
做了这些修改后,在选择Checkouts菜单项并浏览相应的列表视图时,右上角在列表和表单按钮后会增加一些按钮。但在我们创建对应视图前并不能使用,本文将一一学习。窗口操作还可在列表和表单视图的上方的 Action 菜单按钮中使用,它在 Fitlers 按钮旁。要使用这个,我们需要在元素中添加以下两个属性:
- src_model设置Action所作用的模型,例如src_model=“library.checkout”
- multi=“true”也启用列表视图中的Action,这样它可以作用于多个已选记录。否则仅在表单视图中可用,并且一次只能应用于一条记录。
补充:此时打开借阅表单会提示Insufficient fields for Calendar View!,在编写日历视图前最好选视图模式里删除 calendar 来进行效果查看
表单视图结构
表单视图要么按照简单布局,要么按与纸质文档相似的业务文档布局。我们将学习如何设计这些业务文档布局以及使用可用的元素和组件。要进行这一学习,我们重新查看并扩展第八章 Odoo 12开发之业务逻辑 - 业务流程的支持中创建的图书借阅表单。
业务文档视图
业务应用中记录的很多数据可以按纸质文档那样展示。表单视图可模仿这些纸质文档来提供更直观的用户界面。例如,在我们的应用中,可以把一次借阅看作填写一张纸,我们将编写一个遵循这一设计的表单视图。编辑library_checkout/views/chceckout_view.xml文件并修改表单视图记录来带有业务文档视图的基本框架:
<record id="view_form_checkout" model="ir.ui.view">
<field name="model">library.checkout</field>
<field name="arch" type="xml">
<form>
<header>
<!--以下仅供查看效果使用-->
<field name="state" widget="statusbar" clickable="True" />
</header>
<sheet>
...
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
视图名称是可选的,在不写时会自动生成。为简便以上利用了这一点,在视图记录中省略了
- header状态栏
- sheet主内容
- 底部交流区,也称作chatter
底部的交流区使用了 mail 插件模块中提供的社交网络组件。可使用这些,我们的模型需要继承mail.thread和mail.activity.mixin,可参见第八章 Odoo 12开发之业务逻辑 - 业务流程的支持。

头部 Header
头部header 通常用于文档所走过的生命周期或步骤,还包含相关的操作按钮。这些按钮是普通表单按钮,最重要的下一步可以高亮显示。
头部按钮
编辑表单视图中的
<header>
<field name="state" invisible="True" />
<button name="button_done"
string="Return Books"
attrs="{'invisible':
[('state', 'in', ['new', 'done'])]}"
class="oe_highlight" />
</header>
这里我们在头部添加了一个Return Books 按钮,在点击时调用button_done模型方法。注意可使用class=“oe_highlight”来对用户高亮显示操作。例如,在有几个可选按钮时,我们可以高亮显示主操作或下一步要执行的“更多”操作。attrs用于在 New 和 Done 状态时隐藏该按钮。实现这点的条件使用了不会在表单显示的 state 字段。要使条件生效,我们需要将使用的所有值在网页客户端中加载。我们不打算向终端用户显示 state 字段,因此使用 invisible 将其添加为不可见字段。
ℹ️domain 或 attrs 表达式中使用的字段必须在视图中加载,作用于它们的
元素。如果字段不对用户可见,则必须以不可见字段元素对其进行加载。
本例中我们使用的是 state 字段,相同的效果可通过 states 字段属性实现。虽然没有 attrs 属性灵活,但它更为精简。可将 attrs 一段替换为如下代码:
<button name="button_done"
type="object"
string="Returned"
states="open,cancel"
class="oe_highlight" />
attrs和states元素可见功能也可用于其它视图元素,如 field。本文后续会深入讨论。要让按钮可以运作,我们还需要实现调用的方法。在library_checkout/models/library_checkout.py file文件的借阅类里添加以下方法:
def button_done(self):
Stage = self.env['library.checkout.stage']
done_stage = Stage.search(
[('state', '=', 'done')],
limit=1)
for checkout in self:
checkout.stage_id = done_stage
return True
该方法首先查找 done 阶段的记录来使用,然后对 self 记录集中的每条记录,设置其 stage_id 值为完成阶段。

阶段管道
下面我们为头部添加状态条组件,显示文档所在阶段。从代码层面说,是使用statusbar组件的stage_id字段的
<header>
...
<field name="stage_id"
widget="statusbar"
clickable="True"
options="{'fold_field': 'fold'}" />
</header>
这会在头部添加一个阶段管道组件,它在表示文档当前所在生命周期点的字段上使用了statusbar组件。通常是一个状态选项字段或阶段many-to-one字段。这两类字段在 Odoo 核心模块中多次出现。clickable属性让用户可通过点击状态条来修改文档阶段。一般需要开启它,但有时又不需要,比如需要对工作进行更强的控制,并且要求用户仅使用可用的操作按钮来进入下一步。这种方法允许在切换阶段时进行指定验证。
对阶段使用状态条组件时,我们可将很少使用的阶段隐藏(折叠)在 More 阶段组中。对应的阶段模型必须要有一个标记来配置需隐藏的阶段,通常命名为 fold。然后statusbar组件使用 options 属性来将这一字段名提供给fold_field选项,如以上代码所示。

使用状态代替阶段
阶段是一个使用了模型来设置进度步骤的many-to-one字段。因此终端用户可对其动态配置来符合他们具体的业务流程以及支持看板的完美展示。我们将在图书借阅中使用到state。
状态是一个包含了流程中相当稳定步骤的选择列表,如新建、处理中和完成。终端用户无法对其进行配置,因为它是静态的,更易于在业务逻辑中使用。视图字段对状态甚至还有特别的支持:状态字段属性仅在记录处理特定状态才对用户开放。
ℹ️阶段引入的时间要晚于状态。两者现在共存,在 Odoo 内核的趋势是使用阶段来替代状态。但如前所述,状态仍提供一些阶段所不具备的功能。
可通过将阶段映射到状态中来同时获得两者的优势。在借阅模型中我们通过向借阅阶段中添加一个状态字段来实现,借阅文档通过一个关联字段来使用状态。使用状态代替阶段的模型中,我们也可以使用进度条管道。这种情况下要在进度条中列出状态,需要使用statusbar_visible属性来替换fold_field选项。具体代码如下:
<field name="state"
widget="statusbar"
clickable="True"
statusbar_visible="draft,open,done" />
注意在我们实际的图书借阅项目中并不能这么使用,因为它是阶段驱动的,而非状态驱动。

文档表单
表单画布是表单的主区域,这里放置实际的数据元素,设计上类似一张真实的纸质文档,通常 Odoo 中的这些记录也会被称为文档。通常文档表单结构包含如下区域:
- 左上角文档标题和副标题
- 右上角按钮区
- 其它文档头部字段
- 底部笔记区,将附加字段组织成选项卡或页面
文档各行通常在笔记区的第一页,在表单之后,通常有一个 chatter 组件,带有文档订阅者、讨论消息和活动规划。下面逐一了解这些区域。
补充:关于sheet的翻译Alan的理解sheet 仅为单(据),但出于行文习惯一律使用表单
标题和副标题
一个元素之外的字段不会自动带有渲染它们的标签。对于标题元素就是如此,因此该元素应用来对其进行渲染。虽然要花费额外的工作量,但这样的好处是对标签显示控制有更好的灵活性。常规 HTML,包括 CSS 样式元素,可用于美化标题。一般标题放在oe_title类中。以下为扩展后的
<sheet>
<field name="member_image" widget="image" class="oe_avatar" />
<div class="oe_title">
<label for="member_id" class="oe_edit_only" />
<h1><field name="member_id" /></h1>
<h3>
<span class="oe_read_only">By </span>
<label for="user_id" class="oe_edit_only" />
<field name="user_id" class="oe_inline" />
</h3>
</div>
<!-- More elements will be added from here... -->
</sheet>
此处可以看到我们使用了div, span, h1和h3这些常规 HTML 元素。
我们还可在表单左上角标题旁包含展示图像。它用在 parnter 或产品这类模型的表单视图中。作为示例,我们在标题区前添加了一个member_image字段,它使用图像组件widget=“image”,以及特定的 CSS 类class=“oe_avatar”。该字段尚未添加至模型中,下面我们就来添加,我们使用关联字段来将会员的图片显示在借阅文档中。编辑library_checkout/models/library_checkout.py文件并在借阅类中添加如下字段:
member_image = fields.Binary(related='member_id.partner_id.image')

表单内容分组
表单主内容区应通过
继续修改表单视图,在主内容区标题
<group name="group_top">
<group name="group_col1">
<field name="user_id" />
<field name="checkout_date" />
</group>
<group name="group_col2">
<field name="state" />
<field name="closed_date" />
</group>
</group>
为 group 标签分配name是一个好的编码实践,这样在其它模块中继承时会更易于对它们进行引用。还可设置 string 属性,一旦设置将作为该部分的标题来显示。
ℹ️Odoo 11中的修改 string 属性不能作为继承的锚点,因为在应用继承前会对其进行翻译。这时应使用 name 属性来代替它。
在 group 内,
col 属性可用于
<group name="group_top">
<group name="group_col1"
col="4"
colspan="2"
string="Group 1">
<field name="user_id" />
<field name="state" />
<field name="checkout_date" />
<field name="closed_date" />
</group>
<group name="group_col2" string="Group2" />
</group>
以上我们使用 string 属性为组添加了标题,来更清楚地看组所在位置。注意字段的顺序不同,它们先是从左到右,然后从上到下。

选项卡笔记本(Tabbed notebooks)
另一种组织内容的方式是 notebook 元素,一个包含多个称为页面(page)的选项卡分区的容器。它们可以让不常用的内容在不使用时隐藏起来,或者用于按话题组织大量字段。
我们将在借阅表单中添加一个带有已借图书列表的notebook 元素。在前面的
<notebook>
<page string="Borrowed Books" name="page_lines">
<field name="line_ids" />
</page>
</notebook>
本例中笔记本仅有一个页面。添加更多,我们需在
- string:选项卡的标题(必填)
- attrs:不可见属性与表达式映射的字典
- accesskey:HTML访问密钥

字段
视图字段有一些可用属性。大部分从模型定义中获取值,但可在视图中覆盖。以下来快速查看字段的可用属性:
- name标识字段数据库中名称
- string用于想要覆盖模型中标签文本的标签文本
- help是鼠标悬停在字段上显示的提示文本,它允许我们覆盖模型定义中提供的帮助文本
- placeholder是在字段中显示的提示文本
- widget让我们可以覆盖字段的默认组件,一会儿我们就会讲到可用的组件
- options是一个带有组件附加数据的JSON数据结构,值随各组件的不同支持而不同
- class是用于字段 HTML 渲染的CSS类
- nolabel=“True”阻止自动字段标签的展示。仅对
元素内的字段有作用,通常与 - invisible=“True”让字段不可见,但仍会从服务端获取数据并可在表单中使用
- readonly=“True”让表单中该字段不可编辑
- required=“True”让表单中该字段为必填
一些特定字段的属性如下:
- password=“True”用于文本字段。显示为密码项,隐藏所输入文字
- filename用于二进制字段,它是用于存储上传文件名的模型字段的名称
字段标签
<label for="name" class="oe_edit_only" />
这么做时,如果字段在
字段组件
每个字段类型都会使用相应的默认组件在表单中显示。但还有一些替代组件可以使用。对于文本字段,有如下组件:
- email用于让 email 文本成为可操作的”mail-to”地址
- url用于将文本格式化为可点击的URL
- html用于将文本渲染为HTML内容;在编辑模式下,它显示为一个WYSIWYG(所见即所得)编辑器,可在不使用 HTML 代码的情况下格式化内容。
对于数字字段,有以下组件:
- handle在列表视图中作为一个排序字段,显示一个句柄来让我们可以拖放进行自定义排序
- float_time将一个浮点型字段格式化为带有小时和分钟的值
- monetary将一个浮点型字段显示为货币金额。它与currency_id字段一起使用,还可以通过options=”{‘currency_field’: ‘currency_id’}“来使用另一个字段名
- progressbar将一个浮点值显示为进度条百分比,有助于将字段展示为完成率
- percentage和percentpie组件可用于浮点型字段
对于关联和选择项字段,有以下附加组件:
- many2many_tags将值显示为按钮标签列表
- many2many_checkboxes将选项值显示为一个复选框列表
- selection对many-to-one字段使用选择字段组件
- radio以单选按钮显示选择字段选项
- priority将选项字段显示为一个可点击星形列表。选择项目通常是数值。
- state_selection将看板状态选择列表显示为信号灯。普通状态显示为灰色,完成显示为绿色,其它状态显示为红色。
- pdf_viewer是一个二进制字段(在 Odoo 12中引入)。
ℹ️Odoo 11中的修改 state_selection在 Odoo11中引入来替换掉kanban_state_selection。后者被淘汰,但为保持向后兼容性,还支持使用。
关联字段
在关联字段中,我们可让用户操作做一些额外控制。默认用户从这些字段中创建新记录(也称作“快速创建”)并打开关联记录表单。可通过options字段属性来关闭:
options="{'no_open': True, 'no_create': True}"
context和domain也是字段属性并对于关联字段特别有用。context可定义关联字记录默认值,domain 可限制可选记录。常见的示例为让一个字段依赖其它字段值来产生选择项。domain可在模型中直接定义,但也可在视图中进行覆盖。
在to-many字段中,我们还可使用 mode 属性来更改用于显示记录的视图类型。默认为 tree,但还有其它选项:form, kanban或graph。关联字段可定义行内指定视图来使用。这些视图在元素中的嵌套视图定义中声明。例如,在line_ids借阅中,我们可以为这些线路定义特定的列表和表单视图:
<notebook>
<page string="Borrowed Books" name="page_lines">
<field name="line_ids">
<tree>
<field name="book_id" />
</tree>
<!--form>
<field name="book_id" />
</form-->
</field>
</page>
</notebook>
线路列表将带有给定的