全书完整目录请见:Odoo 12开发者指南(Cookbook)第三版
本章中,我们将讲解如下小节:
- 添加菜单项和窗口动作
- 在指定视图中打开动作
- 向表单视图添加内容和组件
- 向表单添加按钮
- 向表单和动作传递参数 – 上下文
- 在记录列表上定义过滤器 – 域
- 定义列表视图
- 定义搜索视图
- 在表单视图边栏显示附件
- 修改已有视图 – 视图继承
- 定义文档样式表单
- 使用attrs的动态表单元素
- 定义内嵌视图
- 定义看板视图
- 根据状态在列中显示看板卡片
- 定义日历和甘特视图
- 定义图表和透视表视图
- 定义流存(cohort)视图
- 定义仪表盘视图
贯穿本章,我们将假定你的数据库中安装了base插件并且有一个空的插件模块可以从各节中添加XML代码到插件声明文件所引用的数据文件中。参见第四章 创建Odoo插件模块,获取更多有关激活插件中修改的信息。
引言
本章讲解所有用户在Odoo网站版块以外所会碰到的UI元素。曾经这是OpenERP的全部,在Odoo中,用户界面通常仅被称为网页客户端。更为明确的说法是后台,以有别于网站前台。
技术准备
本章的技术要求包含一个在线Odoo平台。
本章中所使用的所有代码都在可GitHub仓库中下载:https://github.com/PacktPublishing/Odoo-12-Development-Cookbook-Third-Edition/tree/master/Chapter10。
观看如下视频来查看实时代码操作:https://youtu.be/usr6cshIoy4
添加菜单项和窗口动作
最明显的方式是通过添加菜单项来为用户添加新功能。在点击菜单项时,会发生一些操作。本节将带你学习如何定义这些操作。
我们将创建一个顶级菜单及子菜单,这会打开一个客户列表。
这也可使用网页用户界面借由settings菜单来实现,但更推荐使用XML数据文件,因为在创建我们的插件模块时需要使用此方法。
如何实现…
在我们的插件模块的XML数据文件中,执行如下步骤:
- 定义待执行的动作:
123456<act_window id="action_all_customers"name="All customers"res_model="res.partner"view_mode="tree,form"domain="[('customer', '=', True)]"context="{'default_customer': True}" /> - 创建顶级菜单,如下:
123<menuitem id="menu_custom_top_level"name="My App menu"web_icon="my_module,static/description/icon.png"/> - 在菜单中引用我们的动作:
1234<menuitem id="menu_all_customers"parent="menu_custom_top_level"action="action_all_customers"sequence="10"/>
如果我们现在升级模块的话,会看到一个可打开子菜单的顶级菜单。点击该菜单项会打开一个所有客户的列表。
运行原理…
第一个XML元素act_window,定义了一个展示带有所有客户列表视图的窗口动作。我们使用了最重要的属性:
- name:用作动作所打开视图的标题。
- res_model:这是待使用的模型,我们使用res.partner,Odoo在这里存储着所有的合作伙伴和地址,包括客户。
- view_mode:这会列出可以使用的视图类型。它是一个视图类型的逗号分隔值。默认值为list, form,会让列表视图和表单视图可用。如果你只是想要显示日历视图和表单视图,那么view_mode的值应为calendar, form。其它可选的视图选项有kanban, graph, pivot, calendar, cohort和dashboard。我们会在后续的小节中进行更多的学习。
- domain:这是个可选项,让我们可设置在视图中可用记录的过滤器。本例中我们想要将成员限制为仅包含客户。我们会在本章在记录列表上定义过滤器 – 域一节中更深入地进行学习。
- context:它可以设置打开的视图中可用的值,并影响它们的行为。本例中,在新记录上,我们希望客户标记的默认值为True。这会在本章向表单和动作传递参数 – 上下文一节中进行详细讲解。
- limit:这会设置列表视图中可见的记录默认数量。默认值为80。
小贴士:在老代码中,你会经常看到tree视图模式。这是截至并包含Odoo 11中所使用的内部名称。版本12还接收该值,但会等同于list来进行处理。
接下来,我们创建从顶级菜单向可点击的末梢菜单项创建菜单项等级。这些menuitem元素最重要的属性如下:
- name:它用于菜单项所展示的文本。如果你的菜单项关联到动作,可以留空,因为此时会使用动作名。
- parent (在使用record元素时为parent_id):这是引用父级菜单项的 XML ID。不包含父级项目为顶级菜单。
- action:这是引用要被调用的动作的XML ID。
- sequence:它用于对兄弟菜单项排序。
- groups (record标签中为groups_id):这是一个可选项,为可访问该菜单项的用户组列表。若留空,则对所有用户都可用。
- web_icon:该选项仅用于顶级菜单。它会在企业版本中显示应用的图标。
窗口动作自动决定以所希望的类型(form, tree等等)对目标模型查找所使用的视图,并选择序号最低的那个。
act_window和menuitem是隐藏所进行操作的快捷XML标签。如果不想使用快捷XML标签,那么可以通过该标签创建一个ir.actions.act_window和ir.ui.menu模型的记录。例如,如果想要通过<record>加载act_window,可以这么做:
1 2 3 4 5 6 7 8 |
<record id='action_all_customers' model='ir.actions.act_window'> <field name="name">All customers</field> <field name="res_model">res.partner</field> <field name="view_mode">tree,form</field> <field name="domain">[('customer', '=', True)]</field> <field name="context">{'default_customer': True}</field> <field name="limit">20</field> </record> |
你可以相同的方式通过<record>创建menuitem。
ℹ️注意menuitem快捷标签中使用的名称可能不会映射到使用record元素(parent应为parent_id,groups应为groups_id)时所用的字段名。
要创建菜单,网页客户端从ir.ui.menu读取所有的记录并从parent_id字段推断它们的等级。菜单还会根据分配给菜单和动作的模型和组的用户权限来进行过滤。在用户点击菜单项时,执行它的动作。
扩展知识…
窗口动作还支持target属性,用于指定如何展现视图。可选项如下:
- current:这是默认值,在网页客户端的主内容区域打开该视图。
- new:在一个弹窗中打开视图。
- inline:类似于current,但以编辑模式打开表单并禁用了Action菜单。
- fullscreen:动作会覆盖整个浏览器窗口,因此会遮挡住菜单。有时,这被称为平板(tablet)模式。
- main:类似current,但还会清理掉面包屑导航。
窗口动作的view_type属性现在大多已经淘汰。默认的表单的替代为tree,会引起分组列表渲染一个等级树。不要将这个属性与本节运行原理中使用和讲解的view_mode属性相混淆,那是决定所使用的视图类型的。
对于窗口动作有一些额外的可用属性,在act_window快捷标签中不被支持。要使用这些属性,我们需要使用带有如下字段的记录元素:
- res_id:如果打开一个表单,你可以通过在这里设置其ID来打开指定记录。这对于多步骤向导或者在你需要经常查看或编辑指定记录时都非常有用。
- search_view_id:这会指定一个具体的搜索视图来用作树状和图表视图。
- auto_search:默认为True。如果对象的搜索非常耗时且/或消耗大量资源的话将其设置为False。通过这种方式,用户可以重审搜索参数并在满意时按下Search。使用默认值搜索会在动作打开时立即触发。
记住左上角的菜单(或是企业版中应用的图标)及顶栏中的菜单都是由菜单项所组成。唯一的不同的是左上角中菜单的这里项没有父级菜单,但顶栏上的这些菜单有顶栏的相应菜单项作为父级。在左栏中,等级结构更为明显。
同时应记住出于设计原因,如果第二级菜单有子菜单时第一级菜单会打开下拉菜单。Odoo会根据子菜单项的排序打开第一个菜单项。
其它内容
参照如下内容来学习有关菜单和视图的更多知识:
- 你会在第七章 模块数据中发现有关XML ID引用机制的更多讨论。目前只要记住你能以这种方式设置引用 ,并且顺序很重要。如果前面的标签被反转了,包含这一XML代码的插件不会被安装,因为menuitem会引用一个未知动作。
ℹ️在开发过程中添加新数据文件和新元素会非常危险,因为那样你添加这些文件和元素的顺序不会反映它们在空数据库中所加载的顺序。在部署前,保持检查你的插件在空数据库中安装是否会出错。
- ir.actions.act_window是最常见的动作类型,但菜单可引用任意类型的动作。技术上你链接一个客户端动作、服务端动作或在ir.actions.*命名空间中所定义的任意其它模型都毫无分别。只是在后台由什么动作组成上会存在差别。
- 如果你只需要调用具体动作稍微多一点的灵活性,使用返回窗口动作的服务端动作。如果你需要完全的灵活性,使用客户端动作(ir.actions.client),它允许你有一个完全自定义的用户界面。但是,仅在没有其它选择时才考虑使用它,因为这样会损失掉大量的Odoo便捷助手 。
在指定视图中打开动作
在未给定时窗口动作自动决定所使用的视图,但有时,我们希望一个动作打开具体的视图。
我们会为res.partner模型创建一个基本表单视图,并且随后我们会新建一个具体打开该表单视图的窗口动作。
如何实现…
- 定义partner的最小树状和表单视图:
1234567891011121314151617181920<record id="view_all_customers_tree" model="ir.ui.view"><field name="name">All customers</field><field name="model">res.partner</field><field name="arch" type="xml"><tree><field name="name" /></tree></field></record><record id="view_all_customers_form" model="ir.ui.view"><field name="name">All customers</field><field name="model">res.partner</field><field name="arch" type="xml"><form><group><field name="name" /></group></form></field></record> - 更新添加菜单项和窗口动作一节中的动作来使用新的表单视图:
1234567891011121314<record id="action_all_customers_tree"model="ir.actions.act_window.view"><field name="act_window_id" ref="action_all_customers" /><field name="view_id" ref="view_all_customers_tree" /><field name="view_mode">tree</field><field name="sequence" eval="2"/></record><record id="action_all_customers_form"model="ir.actions.act_window.view"><field name="act_window_id" ref="action_all_customers" /><field name="view_id" ref="view_all_customers_form" /><field name="view_mode">form</field><field name="sequence" eval="2"/></record>
现在如果打开菜单并点击列表中的成员,你会看到刚刚定义的最小化表单和列表。
运行原理…
这次,我们使用了针对任意记录类型通用XML代码,即带有所需 id 和model属性的记录元素。记录元素上的id属性是任意一个在插件中唯一的字符串。model属性引用所希望创建的模型。假定我们想要创建视图,则需要创建一个 ir.ui.view模型的记录。在该元素内, 我们设置你通过model属性选择的模型中定义的字段。对ir.ui.view最关键的字段是model和arch。model字段包含你所想要定义视图的模型而arch字段包含视图本身的定义。稍后我们就会讲到它的内容。
name字段虽然不是严格必要,但有助于通过视图调试问题,因些将其设置为一个字符串可以告诉你这个视图的作用。该字段的内容不对用户显示,因此你可以填入任意对你有意义的提示。如果不进行设置,会得到一个包含模型名和视图类型的默认名称。
ir.actions.act_window.view
我们所定义的第二条记录与在前面添加菜单项和窗口动作小节中定义的act_window一同发挥作用。我们已经知道通过设置view_id字段,可以选择第一个视图模式所使用的视图。但是,假设我们将view_mode设置为tree, form视图,view_id则应选择树状视图,但如我们想要设置表单视图,这里放在了第二位。
如果你发现自己牌同等状况,使用ir.actions.act_window.view模型,它会对哪种视图类型加载哪种视图进行更细致的控制。这里定义的前两个字段是引用其它对象的通用方式示例,保持元素体为空,但添加一个ref属性,它包含你想要引用的对象的XML ID。因此这里所发生的是我们在act_window_id字段中引用前一小节中的动作,并引用我们刚刚在view_id中所创建的视图。然后,虽然不是严格必要,我们添加了对同一动作这一视图分配相对于其它视图分配的排序号码。仅与通过创建多条ir.actions.act_window.view记录为不同视图模式分配视图相关。
ℹ️一旦定义了 ir.actions.act_window.view记录,优先级高于在动作的view_mode字段中所填写内容。因此在前面的记录中,看不到列表视图,仅有表单视图。需要添加另一个指向针对res.partner模型的列表视图的ir.actions.act_window.view。
扩展知识…
我们在添加菜单项和窗口动作一节中学习了可以将act_window替换为<record>。如果想要使用自定义视图,可以遵循给定的语法:
1 2 3 4 5 6 7 8 9 10 11 12 |
<record id='action_all_customers' model='ir.actions.act_window'> <field name="name">All customers</field> <field name="res_model">res.partner</field> <field name="view_mode">tree,form</field> <field name="domain">[('customer', '=', True)]</field> <field name="context">{'default_customer': True, 'tree_view_ref': 'my_module.view_all_customers_tree'. 'form_view_ref': 'my_module.view_all_customers_form' }</field> <field name="limit">20</field> </record> |
向表单视图添加内容和组件
前面一节展示了如何为一个动作选择指定的视图。现在我们将演示如何让表单视图更为有用。本节中,我们将使用在指定视图中打开动作一节中所定义的表单视图。
如何实现…
- 定义表单视图的基本结构:
123456789<record id="form_all_customers" model="ir.ui.view"><field name="name">All customers</field><field name="model">res.partner</field><field name="arch" type="xml"><form><!--form content goes here --></form></field></record> - 要添加一个通常用于动作按钮和阶段管道的头部栏,在表单中进行添加:
12345<header><button type="object" name="open_commercial_entity"string="Open commercial partner"class="btn-primary" /></header> - 为表单添加字段,使用group标签在视图上对它们进行组织:
1234<group string="Content" name="my_content"><field name="name" /><field name="category_id" widget="many2many_tags" /></group>
现在表单中应该会显示带有一个按钮和两个垂直排列字段的顶栏。
运行原理…
我们先来看ir.ui.view模型中的arch字段。首先,注意视图在XML中进行定义,因此你需要向arch字段传递type=”xml”属性,否则会给解析器带来困扰。同时还要求你的视图定义必须包含组织良好的XML,否则在加载这个代码段时会出现问题。
下面我们来逐一学习前面所使用的标签并对其它可用的标签进行总结。
表单
在定义表单视图时,arch字段的第一个元素必须为form元素。内部借助它来获取记录的type字段,因此不建议设置该字段。但在老代码还是可能会看到这种写法。
form元素自身可以有两个老属性,分别是string和。在Odoo的早前版本中,它们用于决定在面包屑导航中所看到的标题以及用于区分7.0之前和之后所编写的表单,但两者都可被视为淘汰。面包屑中的标题现在由模型的name_get函数提取,而版本则假定为7.0及之后的版本。
除下面的元素以外,你还可以在form标签内使用自定义的HTML。算法为对Odoo未知的元素均被视作普通HTML并直接传递给浏览器。要非常小心,因为你填入的HTML会与Odoo元素生成的HTML代码进行交互,可能会让渲染出现偏差。
Header
这个元素是显示在表单头部的各元素的容器,它被渲染为一个白条。通常会像示例中一样将动作按钮放在这里。而如果你的模型有state字段的话,可以选择一个状态栏。
按钮
button元素用于允许用户触发某一动作。参见向表单添加按钮一节获取更多信息。
组
<group>元素是Odoo的主要元素,用于组织内容。<group>元素中的字段以它们的标题进行渲染,并且相同组中的所有字段进行了排列,这样在视觉上可以看出它们属于相同组。还可以内嵌<group>元素,这会让Odoo将所包含字段以相邻列进行渲染。
通常,应使用<group>机制来在表单视图中显示所有字段并仅在必要时才考虑其它元素,如<notebook>, <label>, <newline>等。
如果为组分配了string属性,它的内容会在组中渲染为标题。
也应培养为字段的每个逻辑组分配name的习惯。这一名称对用户不可见,但在下面小节中重载视图时会很有帮助。在表单定义中保持该名称唯一,来避免对不知引用哪一个组的混淆。不要使用string属性来进行实现,因为string值会因为翻译的原因而最终发生改变。
字段
为实际显示和控制数据,表单视图应包含一些字段元素。它们有一个强制属性,name,引用模型中的字段名称。早前,我们让用户可以编辑partner的名称和分类。如果我们只想展示其中的字段,而不让用户编辑,可以将readonly属性设置为1或True。这个属性实际可以包含一个小的Python代码子集,所以readonly=”2>1″会让该字段只读。对于invisible属性也是如此,此时会从数据库中读取值,在不对用户展示。稍后我们会来看它在什么场景中使用。
你一定注意到了分类字段的widget属性。它定义了字段中的数据向用户展示的方式。每种类型的字段都有一个标准组件,因此无需显式的指定widget。但有些类型提供了多种展现方式,这时你可能会选择非默认的方式。可用组件的完整列表超出了本节的范畴,请参照Odoo源代码来进行尝试。参见第十六章 网页客户端开发来了解如何制作自己的组件的相关知识。
Notebook和页面
如果模型字段过多,那么可以使用<notebook>和<page>标签来创建选项卡。<notebook>标签中的每个<page>会新建一个选项卡,页面中的内容为选项卡的内容。下面的示例中所创建的带有2个选项卡、每个选项卡3个字段:
1 2 3 4 5 6 7 8 9 10 11 12 |
<notebook> <page string="Tab 1"> <field name="field1"/> <field name="field2"/> <field name="field3"/> </page> <page string="Tab 2"> <field name="field4"/> <field name="field5"/> <field name="field6"/> </page> </notebook> |
<page>标签中的string属性为选项卡的名称。在<notebook>标签中仅能使用<page>标签,但在<page>标签中可以使用任意其它元素。
通用属性
在大多数元素(包含group, field和button)中,都可以设置attrs和groups属性。attrs属性会在使用attrs的动态表单元素一节中进行讨论,groups属性让我们公向某些分组的成员显示某些元素。简单地说,组的XML ID(多个组由逗号分隔)是属性,元素对非指定组内的成员均不可见。
其它标签
有些情况下可能需要不遵守严格的分组布局。例如,如果希望记录的name字段以标题进行渲染,字段的标签会干涉及外观。这种情况下,不要将字段放到group元素中,而是使用普通的HTML h1元素。然后在h1元素前,为字段名添加一个label元素及for属性:
1 2 |
<label for="name" /> <h1><field name="name" /></h1> |
这会将字段的内容渲染为大标题,但字段名以小号类型在大标题上出现。这基本上是标准的partner表单的形式。
如果需要在分组内有换行,可以使用newline元素。内容为空:
1 |
<newline /> |
另一个有用的元素是footer。在以弹窗打开表单时,在这里放动作按钮会比较好。它也会以单独栏进行渲染,与header元素类似。
小贴士:不要对XML节点使用string属性(或任何其它的可翻译属性),这会在其它语言中因继承前应用翻译的原因打破视图重载。
扩展知识…
因表单视图基本上是带一些插件的HTML,Odoo还对CSS类做了一些扩展使用。其中有两个非常有用的是oe_read_only和oe_edit_only。这两个类会让元素仅在读取/浏览模式或仅在编辑模式下可见。要让label仅在编辑模式下可见,使用如下代码:
1 |
<label for="name" class="oe_edit_only" /> |
另一个非常有用的类是oe_inline,可以在字段中使用它来将它们渲染为行内元素,来避免引起不必要的换行。在将字段嵌入文本或其它标记标签时使用该类。
此外,form元素可包含 create, edit和delete属性。如果将其中的属性的设为false,对该表单相应的动作就不可用。如未进行显式的设置,动作的可用性会依据用户的权限来决定。注意这里的目的仅是针对UI层面,不要使用它们来进行权限控制。
其它内容
组件和视图已经拥有了大量的功能,但迟早你会碰到已有组件和视图无法满足的要求。参见下面的小节来创建自己的视图和组件:
- 参见第十六章 网页客户端开发中创建自定义组件一节来定义你自己的组件。
- 参见第十六章 网页客户端开发中新建一个视图一节来创建你自己的视图。
向表单添加按钮
我们在前面的表单视图中添加了一个按钮,但有好几种不同类型的按钮可供使用。本节将添加另一个按钮。它也会将如下代码放到本节的header元素中。
如何实现…
添加一个引用动作的按钮:
1 2 3 |
<button type="action" name="%(base.action_partner_category_form)d" string="Open partner categories" /> |
运行原理…
按钮的type属性决定其它字段的语法,因此我们先来看看可用的值:
- action:这会让按钮调用一个 ir.actions.*命名空间中所定义的动作。name属性需要包含该动作的数据库ID,你可以方便地让Odoo通过包含所需动作XML ID 的Python格式化字符串来进行查找。
- object:它调用当前模型的一个方法。name属性包含函数名称。该函数应带有@api.multi 标记并作用当前浏览的记录。
string属性用于分配用户所看到的文本。
小贴士:曾经还有第三个值workflow,它向当前已淘汰并删除的工作流引擎发送信号。如果你在从老版本迁移代码时看到了,可能会需要将其替换为一个方法调用。
扩展知识…
使用btn-primary CSS类来渲染按钮为高亮,btn-default来将其渲染为普通按钮。它通常用于向导中的取消按钮或提供一个视觉上不明显的二级动作。设置oe_link类会让按钮看起来像一个链接。
对object类型的按钮调用会返回描述一个动作的字典,然后在客户端进行执行。这样你可以实现多屏向导或是只打开另一条记录。注意点击按钮总是会让客户端在运行该方法前发送一个写入或创建的调用。
还可以通过替换string属性来把内容放在button标签中。这通常用于按钮框,在定义文档样式表单一节中会讲到。
向表单和动作传递参数 – 上下文
内部Odoo中的每个方法可以访问一个名为context的字典,这是从每个动作中附加带到执行该动作的方法中的。UI层面也可以进行访问,有多种方式来在上下文中设置值。本节中,我们将通过使用语言、默认值和隐式过滤器来探讨使用这一机制的几个应用。
准备工作
虽然不是绝对必要,如未安装法语可进行安装来让本节变得更有意思。参见第十二章 国际化来了解如何实现。如果你使用的是法语的数据库,则将fr_FR修改为其它的语言;en_US用作英语。还有在对的一个客户点击Active(悬浮时变为Archive)按钮来对其存档并验证下这个成员是否不出现在列表中。
如何实现…
- 创建一个新动作,与添加菜单项和窗口动作一节很相似:
1234<act_window id="action_all_customers_fr" name="Tous les clients"res_model="res.partner" domain="[('customer', '=', True)]"context="{'lang': 'fr_FR', 'default_lang': 'fr_FR','active_test': False}" /> - 添加一个调用该动作的菜单。这部分留作读者练习。
在你打开该菜单时,视图中显示为法语,而在你创建一个新成员时,法语将作为预选语言。一个不太明显的不同时你还会看到未激活的成员记录。
运行原理…
上下文字典来自几个数据源。首先,读取当前用户记录的一些值(lang和tz为用户的语言和时区)。然后,我们有一些插件添加它们自己使用的一些键。此外,UI添加当前使用的模型和记录作为键(active_id, active_ids, active_model)。还有在在指定视图中打开动作一节中我们学习到了可以在动作中添加自己的键。这些会合并在一起并传递给背后的服务端函数,以及客户端 UI。
因此,通过设置lang上下文键,我们将展示语言强制为法语。你会注意到这并不会修改整体的UI语言,因为仅有我们所打开的列表视图处理这个上下文范围内。其它的UI已由用户原有语言中所包信的其它上下文所载入。但是,如果你在这个列表视图中打开一个记录,它也会以法语进行展示,并且如果你在该表单中打开一个关联记录或者按下执行一个动作的按钮,都会使用法语。
通过设置default_lang,我们为这个上一文域中所创建的每条记录设置一个默认值。通用模式是default_$fieldname: my_default_value,这种情况下让我们为新创建成员设置了默认值。假定我们的菜单是有关客户的,设置default_customer: True来让Customer字段默认勾选就讲得通了。但是,这是res.partner的模型级别的默认值,因此它不会修改任何值。对于标量字段,语法和Python代码所编写的一致:字符串放在引号中,数值字段保持不变,布尔字段为True 或 False。对于关联字段其语法要更为复杂一些;参见第七章 模块数据来学习如何进行编写。注意上下文中设置的默认值会覆盖模型定义中设置的默认值,因此在不同场景下有不同的默认值。
最后一个键是active_test,它具有特殊的语法。对每个带有名为active字段的模型,Odoo会自动过滤掉该字段为False的记录。这也是取消勾选该字段的成员会在列表中消失的原因。通过设置该键,我们可以取消这种行为。
ℹ️这对于 UI 的独立控制非常有用,但在需要确保操作应用于所有记录而非 active 记录时在 Python 代码中会更为有用。
扩展知识…
在定义上下文时,你需要访问一些变量,最重要的一个变量是uid,它运行为当前用户的ID。你会需要用到它来设置默认过滤器(参见下一节在记录列表上定义过滤器 – 域)。再者,你可以访问context_today函数和current_date变量,前者为代表当前日期的date对象,以用户的时区进行显示,后者是以UTC显示的当前日期,格式为YYYY-MM-DD。要将date字段的默认值设置为当前日期,使用current_date,而对默认的过滤器使用context_today()。
此外,你可以通过Python的datetime, time和relativedelta子集来进行一些日期运算。
小贴士:大部分的域在客户端运行。出于安全的考虑限制服务端域的运行。在引入了客户端运行时,不致使系统崩溃的最好选择是在JavaScript中实现部分Python功能。在Odoo中构建了一个小型JavaScript Python解释器,能够很好地运行一些简单表达式,通常这就足够用了。
小贴士:当心在<act_window />快捷标签中上下文变量的使用。它们在安装时运行,和你所想要的相差甚远。如果需要在上下文中使用变量,使用<record />的写法。
我们可以在动作中添加上下文键相同的方式添加按钮。这会让按钮调用的函数或动作在给定的上下文中运行。
大部分的表单元素属性以Python也可访问上下文字典的方式运行。invisible和readonly属性即为这种情况。因此,在你想要元素有时在表单中显示,有时又不显示时,设置invisible属性为context.get(‘my_key’)。对于产生字段不可见的动作,设置上下文键为my_key: True。这种策略让我们无需为不同情况进行重写即可调整表单。
你也可以在关联字段上设置上下文,它会影响到字段载入的方式。通过设置form_view_ref或tree_view_ref键为视图的完整XML ID,你可以为该字段选择一个具体的视图。在你对同一对象有多个相同类型的视图时这会很有必要。没有这个键时,你以最小序号获取该视图,很多情况这都不是所想要的。
其它内容
- 上下文还用于设置默认搜索过滤器。可以在本章定义搜索视图一节中学习有关默认搜索过滤器的知识。
在记录列表上定义过滤器 – 域
我们已经看到了在第一个动作中域的第一个示例,即[(‘customer’, ‘=’, True)]。通常,我们需要从动作中显示一个所有可用记录的子集,或仅允许可用记录的子集为many2one关联的目录。在Odoo中描述这些过滤器的方式是使用域。本节描述如何使用域来显示一部分所选成员。
如何实现…
要通过动作显示成员的一个子集,需要执行如下步骤:
- 为不讲法语的客户添加一个动作:
123456789<record id="action_my_customers"model="ir.actions.act_window"><field name="name">All my customers who don't speak French</field><field name="res_model">res.partner</field><field name="domain">[('customer', '=', True), ('user_id', '=', uid),('lang', '!=', 'fr_FR')]</field></record>i - 为客户或供应商添加一个动作:
12345678<record id="action_customers_or_suppliers"model="ir.actions.act_window"><field name="name">Customers or suppliers</field><field name="res_model">res.partner</field><field name="domain">['|', ('customer', '=', True),('supplier', '=', True)]</field></record> - 添加调用这些动作的菜单。这部分留作读者练习。
运行原理…
域的最简单形式为一个带有三个元素元组的列表,在第一个元素中包含要操作模型的字段名,第二个元素为运算符字符串,第三个值为该字段需要进行检查的值。这是我们在第一个动作中所做的,它被解释为:所有这些条件需要作用于我们所需的记录。它实际上是一个简写,因为域知道那两个前缀运算符& 和–,&是默认值。因此规范化的形式第一个域应写成这样:
1 |
['&', '&', ('customer', '=', True), ('user_id', '=', uid), ('lang', '!=', 'fr_FR')] |
虽然对于更大的表达式有些难于阅读,前缀运算符的优势在于它们的作用域被严格定义了,让你不用担心运算符优级级和括号的使用。有两个表达式,第一个&应用于’&’, (‘customer’, ‘=’, True), (‘user_id’, ‘=’, uid) 来作为第一个操作数, (‘lang’, ‘!=’, ‘fr_FR’)作为第二个操作数。然后,第二个&应用于(‘customer’, ‘=’, True)来作为第一个操作数,而(‘user_id’, ‘=’, uid) 作为第二个操作数。
在第二个动作中,我们因需要 | 运算符而要写出完整形式。
还有一个 ! 运算符用于取反,但既然有逻辑等式和像 !=和 not in 这样的取反比较运算符,实际上并没有必要。注意这是一个一元前置运算符,因此仅用于域中所接的表达式而不是其所跟的所有内容。
注意在编写窗口动作或其它客户端域时右边的操作项不需要固定值。可以使用在向表单和动作传递参数 – 上下文一节中所描述的最小化 Python代码,因此可以编写类似上周修改或仅自己的客户等过滤器。
扩展知识…
前述的域仅用于模型自身的字段,但我们常常需要根据所关联记录的属性来进行过滤。这样,你可以使用@api.depend定义或关联字段中所使用的标记法:创建一个当前模型对所需要过滤模型的点号路径。搜索销售人员是以 G 字母开头的组成员的客户,可以使用[(‘user_id.groups_id.name’, ‘=like’, ‘G%’)] 域。路径可以很长,因此你只需要确保当前模型和所需过滤模型之间存在关联字段即可。
运算符
下表列出了可以使用的运算符及它们的语法:
运算符(等价符号) | 语法 |
---|---|
!ERROR! A2 -> Formula Error: Unexpected , | 第一个为精准匹配,第二为不等于,最一个是已淘汰的不等于标记。 |
in, not in | 这会检查值是否在右侧运算项列表中。以 Python 列表的形式给出:[('uid', 'in', [1, 2, 3])]或[('uid', 'not in', [1, 2, 3])]。 |
小于,小于等于 | |
>, >= | 大于,大于等于 |
like, not like | 检查右侧运算项是否包含在值中(子字符串)。 |
ilike, not ilike | 与上面一个相同,但忽略大小写。 |
!ERROR! A8 -> Formula Error: Unexpected , | 可以在这里搜索模式:%匹配任意字符串,_匹配单个字符。这与 PostgreSQL中的 like 等价。 |
child_of | 对带有 parent_id 字段的模型,它搜索右侧运算项的子项。右侧运算项包含于结果中。 |
!ERROR! A10 -> Formula Error: An unexpected error occurred | 如果右侧运算项为 false 运行结果为 true;否则它和=相同。这用于由程序生成的域,在设置值时用值进行过滤,否则忽略该值。 |
使用域进行搜索的隐患
对于传统字段这些都没有问题,但在搜索非存储函数字段时这是一个广为人知的问题。通过第五章 应用模型中所讲到的在代码中加入搜索函数即可进行处理。
另一个困扰开发人员的问题是Odoo对于使用否定运算符搜索one2many和many2many字段的处理。设置你有一个带有A的用户并使用 [(‘category_id.name’, ‘!=’, ‘B’)]搜索。你的用户如预期会显示在结果中,但在你给用户加上 B 标签时,它还是出现在结果中,因此对于搜索算法来说,有一个关联的记录(本例中为 A)并不符合这一标准。这时你删除 A 标签让 B 成为其唯一标签,可以过滤掉该用户。如果你再删除 B 标签,该用户已无标签,它还是会被过滤掉,因为关联记录上的条件预设了该记录的存在。但在其它情况中,这是你所希望的做法,所以你不能修改标准行为。如果要采取不同行为,提供一个解析为你所需要方式的搜索函数。
ℹ️人们在编写 XML 文件并涉及到域时经常会忘记这点。需要对小于运算符进行转义。搜索指定日期前所创建的记录需要在 XML 中写成[(‘create_date’, ‘<’, current_date)] 。
其它内容
如果需要操作不是由你在程序中创建的域,使用odoo.osv.expression中所提供的工具函数。is_leaf, normalize_domain, AND和OR函数让你可以人以 Odoo 相同的方式合并域。不要自行操作,因为有很多需要考虑到的极端情况,你很可能会忽略掉。
有关域的标准应用,参见定义搜索视图一节。
定义列表视图
在对表单视图花了一番功夫后,现在我们来快速地看下如何定义列表视图。内部有些地方称之为树状视图,另一些地方则称其为列表视图,但因为 Odoo 视图框架中还有另一种结构称为 tree,我们将保持使用列表视图。
如何实现…
- 定义列表视图:
123456789101112<record id="tree_all_customers" model="ir.ui.view"><field name="model">res.partner</field><field name="arch" type="xml"><tree decoration-bf="customer"decoration-danger="supplier"decoration-warning="customer and supplier"><field name="name" /><field name="customer" invisible="1" /><field name="supplier" invisible="1" /></tree></field></record> - 在动作中注册我们在本章添加菜单项和窗口动作一节中所创建的树状视图。
1234567<record id="action_all_customers_tree"model="ir.actions.act_window.view"><field name="act_window_id" ref="action_all_customers" /><field name="view_id" ref="tree_all_customers" /><field name="view_mode">tree</field><field name="sequence">5</field></record>
运行原理…
你已经可以知道这里做了些什么。我们定义了一个视图,类型为 tree,并使用ir.actions.act_window.view元素将其关联到我们的动作上。因此,唯一剩下要讨论的是 tree 元素及其语法。使用列表并没有很多的设计选项,因此该元素的唯一有效子元素是field和button元素。它们的语法与前述相同,不同点是涉及组件时的选项会更少:真正有意义的选项只有progressbar, many2onebutton和handle。前两者和其表单同名组件相同,handle仅针对列表视图。它针对整型字段并渲染为一个可拖拽手柄,让用户可通过将列表中的行拖拽到不同位置来更新字段值。它可用于序列或优先级字段。
这里新的内容是 tree 元素中的装饰属性。它包含在行中选择哪种字体及/或颜色的规则,设置的形式为decoration-$name=”Python code”。所有的匹配转换成相应的 CSS 类,因此前面的视图将既是供应商又是客户的用户渲染为棕色,客户渲染为加粗字段,而供应商为红色。在Python code这部分中,仅能使用在视图定义中的字段名称,所以我们还需要拉customer和supplier字段。这些设为隐藏,因为我们只需要数据而不用向用户展示这些额外的字段。可以使用的类有decoration-bf (加粗), decoration-it (斜体)以及bootstrap类decoration-danger, decoration-info, decoration-muted, decoration-primary, decoration-success和decoration-warning。
扩展知识…
对于数值字段,可以添加一个 sum 属性来让该字段以在属性中设置为提示的文本进行加总。不常见的属性有avg, min和max分别用于显示平均值,最小值和最大值。注意这4个属性仅用于当前显示的记录,因此可能需要调整动作的限制来让用户可即时看到所有记录(参见添加菜单项和窗口动作一节)。
tree元素有一些非常有趣的属性editable。如果将其设置为top或bottom,列表的行为完全不同。不进行添加,点击某行为打开该行的表单视图。带上后点击某行会让其在行内可编辑,将可见字段渲染为了表单字段。这对于本章后面定义内嵌视图一节中讨论的嵌套列表视图尤为有用。top或 bottom 选项关乎新行在列表的顶部还是底部添加。
默认记录根据所显示模型的_order属性进行排序。用户可通过点击列的头部来修改排序,但也可通过在tree元素中设置default_order属性来设置不同的初始排序。语法与_order中的相同。
ℹ️排序通常会让新开发者感到沮丧。因为Odoo让PostgreSQL来完成这一工作,你只能对PostgreSQL已知的字段排序并且这些字段需要在同一张数据表中。因此,如果你想要通过函数或关联字段进行排序,确保要设置store=True。如果要对继承自另一个模型的字段排序,声明一个存储的关联字段。
tree元素的create, edit和delete属性与我们在本章向表单视图添加内容和组件一节中所描述的作用相同。如果设置了editable属性它们还决定可进行的控制。
定义搜索视图
在打开列表视图时,你会注意到右上角的搜索字段。如果在这里进行输入,会获取到有关可进行搜索的推荐,还有一些预定义的过滤器可供选择。本节中将讲解如何定义这些推荐和选项。
如何实现…
- 定义搜索视图:
1234567891011121314151617<record id="search_all_customers" model="ir.ui.view"><field name="model">res.partner</field><field name="arch" type="xml"><search><field name="name" /><field name="category_id"filter_domain="[('category_id', 'child_of', self)]" /><field name="bank_ids" widget="many2one" /><filter name="suppliers" string="Suppliers"domain="[('supplier', '=', True)]" /><group expand="0" string="Group By"><filter string="Country" name="country"context="{'group_by':'country_id'}"/></group></search></field></record> - 设置动作来使用它:
1234567<record id="action_all_customers"model="ir.actions.act_window"><field name="name">All customers</field><field name="res_model">res.partner</field><field name="domain">[('customer', '=', True)]</field><field name="search_view_id" ref="search_all_customers" /></record>
在搜索框中输入内容时,即可通过名称、分类和银行账号字段来进行搜索。如果所搜索的为系统中银行账号的一个子字符串,甚至会弹出对完整银行账号的搜索。
运行原理…
对于name,我们仅仅是将该字段列出供用户搜索,设置语法中的其它保留为默认值,即对字符串字段的子字符串搜索。
对于分类我们做了一些有意思的操作。默认,搜索词用于many2many字段触发器,name_search在配合中为分类名的子串搜索。但根据分类结构,搜索你要找的分类或其子类会非常方便。思考一下主分类邮件订阅者,其子分类为周订阅、月订阅及其它订阅类型。使用前面的搜索视图定义来搜索邮件订阅者会一次性给出订阅出任意类型邮件的用户,这比搜索每种类型再合并结果要方便得多。
filter_domain属性可以包含任意域,因此不受在name属性中命名的相同字段的搜索限制,也不受限只能搜索单项。变量self是用户所填入的,也是你在这里所仅能使用的变量。
针对成员的默认搜索视图的更详细的示例如下:
1 2 3 4 5 6 |
<field name="name" filter_domain="[ '|', '|', ('display_name', 'ilike', self), ('ref', '=', self), ('email', 'ilike', self)]"/> |
这表示用户不用考虑搜索什么。他们只需输入一些字母然后按下 Enter 键,并且有可能所讲到过的字段包含我们要搜索的字符串。
对于bank_ids我们使用另一种技巧。字段的类型不仅决定对用户输入的默认搜索方式,还定义了Odoo呈现推荐的方式。还有既然many2one是唯一提供自动补全的字段,我们通过设置 widget 属性来强制Odoo这么做,虽然bank_ids是一个one2many字段。没有它,我们需要在没有补全推荐的情况下搜索该字段。对于many2many字段同样如此。注意带有many2one组件的所有字段在用户每下按下键盘时会触发对于该模型的搜索,不要过度使用。
应该将最常使用的放在最上面,因为第一个字段是在用户输入内容并按下 Enter 所进行的搜索。搜索框也可配合键盘使用,按向下键选择一条推荐,通过按向右键打开many2one的被全推荐。如果你告诉用户并注意维护搜索视图中的字段排序的话,这比先进行输入来得更有效率,只需要拿起鼠标选择一项即可。
filter元素创建一个按钮,将过滤器的domain属性添加到搜索域中。你应该添加一个逻辑内部名称和一个字符串属性来向用户描述过滤器。
<group>标签用于在Group by按钮下提供分组选项。本节中我们向基于country_id的组记录添加了选项。
扩展知识…
可以使用group标签来对过滤器分组,这会让它们比其它过滤器渲染得更近一些,但这也有语法上隐含意思。如果你将多个过滤器放在同一组中并激活一个以上的过滤器,它们会通过 | 运算符进行合并,而不在同一组中的过滤器和字段通过 & 运算符进行合并。有时,你可能会想要拆分过滤器,即在它们进行互斥过滤时,此时选择两者会导致空结果集。在同一组中,我们可使用separator元素实现同样效果。
注意如果用户为相同字段填入了多个查询,它们也会通过 | 合并,因此不需要有什么担心。
除field外,filter元素还可带有context属性,它的内容会与当前上下文合并,最终与搜索视图中的其它上下文属性合并。这对于支持分组的视图尤其必要(参见定义看板视图和定义图表和透视表视图小节),因为产生的上下文会决定通过group_by键分组的字段。我们会在相应章节中了解分组的细节,但上下文也有其它用途。例如,你可以编写一个函数字段返回依赖于上下文的不同值,然后通过激活过滤器修改值。
搜索视图自身也对上下文键响应。在创建记录时它与默认值的方式非常相似,可以通过上下文向搜索视图传递默认值。如果我们在前面的动作中设置了{‘search_default_suppliers’: 1} 上下文,在搜索视图中会预选中供应商过滤器。但这仅在过滤器有名称才能使用,因此应保持进行设置。使用search_default_$fieldname在搜索视图中为字段设置默认值。
此外,field和filter元素可带有表单视图中相同语法的groups属性,来让该元素仅对某些组可见。
其它内容
更多有关操作上下文的内容,参见向表单和动作传递参数 – 上下文一节。
说重度使用变音语言的用户可能在填写e时想要让Odoo搜索的是e, è, é和ê。PostgreSQL服务中有一项配置称为去重音(unaccent),Odoo 进行了特别的支持,但这在本书讨论的范畴之外。参见https://www.postgresql.org/docs/10/unaccent.html来了解理多有关去生重音的知识。
修改已有视图 – 视图继承
至此,我们都忽略了已有视图,声明的都是全新的视图。这在教学上讲得通,但实际是很少碰到为已有模型定义新视图的情况。而是对已有视图进行适当的修改,可能是显示一个在插件的模型中所添加的字段,或是根据自身或客户需求对它们进行自定义。
本节中,我们将修改默认的partner表单来显示记录的最后修改日期并通过修改搜索视图来让手机号字段可进行搜索。然后,我们会修改partner列表视图中一列的位置。
如何实现…
- 在默认表单视图中插入字段:
123456789<record id="view_partner_form" model="ir.ui.view"><field name="model">res.partner</field><field name="inherit_id" ref="base.view_partner_form" /><field name="arch" type="xml"><field name="website" position="after"><field name="write_date" /></field></field></record> - 向默认搜索视图添加字段:
12345678910<record id="view_res_partner_filter" model="ir.ui.view"><field name="model">res.partner</field><field name="inherit_id"ref="base.view_res_partner_filter" /><field name="arch" type="xml"><xpath expr="." position="inside"><field name="mobile" /></xpath></field></record> - 向默认列表视图添加字段:
123456789<record id="view_partner_tree" model="ir.ui.view"><field name="model">res.partner</field><field name="inherit_id" ref="base.view_partner_tree" /><field name="arch" type="xml"><field name="email" position="after"><field name="phone" position="move"/></field></field></record>
在更新模块之后,你应该可以在partner表单的website字段下看到一个Last updated字段。在搜索框中输入内容进行搜索时,会提示对 partner 进行手机号的搜索,在partner 列表视图中,可以看到电话和 email 的顺序发生了变化。
运行原理…
你可能也猜到了,这里重要的字段是inherit_id。我们需要向其传递希望修改的视图(所继承的)的XML ID。arch字段包含修改你所继承的视图中如何已有XML节点进行修改的指令。实际上你应该将整个过程看作简单的XML处理,因为语法在很靠后的地方才涉及。
arch字段中最简洁的指令是field元素的继承视图,带有必填属性name和position。因为每个字段在表单中仅能出现一次,name已唯一标识了字段。通过position属性,我们可以可以可以在field字段内放置任意内容,在我们所命名的字段之前,之中或之后均可。默认值为inside,但为方便代码阅读,你应当指定所需的位置。记住这里我们讨论的不是语法,这是有关XML树中相对于我们所命名字段的位置。稍后是如何渲染的完全是另一个话题。
第2步演示了一种不同的方法。xpath元素选取与expr属性中指定的XPath表达式所匹配的第一个元素。这里,position属性告知处理器在何处安放xpath元素中的内容。
第3步显示了如何更改一个元素的位置。这一选项在版本 12 中引入并很少被使用。在我们的示例中,通过position=move选项将phone字段移到了email字段之后。
XPath看起来好像很可怕,但对于选择你需要操作的节点是一种非常有效的工具。花点时间在仔细阅读一些简单的表达式是非常值得的。你可能会撞上context node(上下文节点)一词,一些表达式与其相对。在Odoo的视图继承中,它总是你所要继承视图的根元素。
对于在继承视图的arch字段中所看到的其它元素,处理器查找带有相同节点名和匹配属性(排除掉position属性,因为它是指令的一部分)的第一个元素。仅在这个组合很可能不是唯一的时候使用它,比如group元素与name属性进行的组合。
小贴士:注意你可以在arch元素中拥有所需数量的指令元素。我们在继承视图中仅使用了一个,因为当前我们并不需要进行其它修改。
扩展知识…
position属性有另外两个可用值:replace和attributes。使用replace会让所选元素被指令元素的内容所替换。最终如果没有任何内容的话所选元素会被删除。前面的列表或表单视图会让email字段被删除:
1 |
<field name="email" position="replace" /> |
ℹ️警告
删除字段会导致破坏其它继承视图以及一些不想要的负面效果,因此要尽量避免。如果真的需要删除字段,在运行顺序靠后的视图中进行(参见下一节视图继承中的运行顺序,获取更多相关知识。)
属性与前例有着非常不同的语法。处理器预设该元素包含一个名为attribute的属性元素。然后这些元素被用于设置所选元素的属性。如果要留意前面的警告,应当为email设置invisible属性为1:
1 2 3 |
<field name="email" position="attributes"> <attribute name="invisible">1</attribute> </field> |
attribute节点,可包含添加和删除属性,其中又包含加入空格分隔列表的待添加或侍删除值。这对于 class 属性非常有用,可以通过如下代码添加一个class(而非覆盖整个属性):
1 2 3 |
<field name="email" position="attributes"> <attribute name="class" add="oe_inline" /> </field> |
视图继承中的运行顺序
因为我们现在只有一个父视图和一个继承视图,不过碰到冲突的视图重载。在安装了几个模块之后,你会看到大量的partner表单的重载。如果它们在视图中修改的是不同的内容还没有问题,但总有需要了解重载运行机制来避免冲突的时候。
视图的直接后代以priority字段的升序顺序运行,因此priority低的视图先运行。继承的每一步应用于第一步的结果,因此如果priority为3的视图修改了一个字段,而另一个priority为5的视图删除了该字段,是不会有问题的。但如果优先级调过来的话就会出问题了。
我们还可以继承本身就是继承视图的视图。这时第二级继承视图应用于它所继承自的结果。因引,如果有4个视图,A 是独立的表单,B和C继承A,D 继承B,执行顺序为A, B, D, C。使用这种方法无需依赖优先级即可强制执行顺序,通过常更为安全。如果继承视图添加了一个字段并且你需要对该字段应用修改,对继承视图而不是那个独立视图进行继承。
ℹ️这种类型的视图总可以在原有视图的完整XML树上运行,并且会先应用前一个继承视图。
其它内容
以下各点提供了有关调整视图继承行为的一个高级技巧:
- 对于继承视图,有一个非常有用但不太被人了解的字段groups_id。该字段仅在请求父级视图的用户为所指定组的成员时才进行继承。这在调整不同访问权限的用户界面时会节省大量的时间,因为通过继承,我们可以使用比通过表单元素中的groups属性来基于组成员状况显示或不显示元素复杂得多的操作,
- 例如我们可以在用户是某个组的成员时移除一些元素(这是groups属性的反向操作)。还可以执行更为复杂的操作,比如根据组成员关系添加属性,试想一些简单的操作,比如对某些组让字段仅可读,或像对不同组使用不同组件等有意思的概念。
- 本节中所讲的有将原视图的mode字段设置为primary,而继承视图默认具有模式继承。我们在稍后会有看一个继承视图的mode设置为primary的情况,规则会稍有不同。
定义文档样式表单
本节中,我们来学习一些设计指南来呈现统一的用户体验。
如何实现…
- 使用header元素来开始表单:
12345<header><button type="object" name="open_commercial_entity"string="Open commercial partner"class="btn-primary" /></header> - 为内容添加一个sheet元素:
1<sheet> - 放入stat按钮,用于对记录存档:
12345678<div class="oe_button_box" name="button_box"><button name="toggle_active"type="object" class="oe_stat_button"icon="fa-archive"><field name="active" widget="boolean_button"options="{'terminology': 'archive'}"/></button></div> - 添加主要字段:
123456<div class="oe_left oe_title"><label for="name" /><h1><field name="name" /></h1></div> - 添加内容,如果有很字段的话可以使用notebook:
12345<group><field name="category_id" widget="many2many_tags" /><field name="email"/><field name="mobile"/></group> - 在sheet之后,如果可用添加chatter组件:
1234567</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>
运行原理…
头部应包含对当用记可见对象执行动作的按钮。使用btn-primary类来让按钮在视觉上更突出(在写本书时颜色为紫色),这对于引导用户在当前执行最应执行的动作会是一种很好的方式。试着将所有高亮的按钮放在非高亮的按钮的左侧并隐藏在当前状态(若有)中无关的按钮。如果该模型有状态,在头部中使用状态栏组件来进行显示。这会在头部中渲染为居右显示。
sheet元素以样式样式化单据的形式渲染,并且最重要的字段应放在用户第一眼看到的地方。使用oe_title和oe_left类来让它们在显著的位置进行渲染(在写本书时样式为以稍微调整的字体大小居左显示)。
如果有涉及用户当前所见记录的其它感兴趣的字段(比如partner表单上的用户发票),把它们放在一个带有oe_right和oe_button_box类的元素中,这会将按钮向右对齐。按钮自身可使用oe_stat_button类来强制按钮的统一样式渲染。也可以通过对icon属性设置font awesome 图标来自定义指定图标类。有关font awesome 的更多内容参见https://fontawesome.com/v4.7.0/icons/。
我们可以使用oe_chatter类和Chatter组件来在表单视图的底部获取默认的留言区。这时需要使用mail.thread mixin。我们将在第二十三章 在Odoo中管理email中进行详细学习。
ℹ️即便你不喜欢这种布局,也请使用上述的元素和类名,然后使用CSS和JavaScript来进行所要做调整。这会让你的用户界面与已有的插件相兼容并会与核心插件更好的集成。
使用attrs的动态表单元素
至此,我们只学习了根据用户组来修改表单(元素上的groups属性和继承视图中的groups_id字段)。本节我们将展示如何根据其它字段的值来修改表单视图。
如何实现…
- 在表单元素上定义一个名为attrs的属性:
12345<field name="parent_id"attrs="{'invisible': [('is_company', '=', True)],'required': [('is_company', '=', False)]}" /> - 确保你所引用的所有字段都在表单中可用:
1<field name="is_company" invisible="True" />
如果用户是公司的话会让parent_id字段不可见,而在非公司时又为必写字段。
运行原理…
attrs属性包含有invisible、required和readonly键的字段(这些都是可选的)。值为引用表单中所存在字段(也仅有为些,因此没有点号路径)的域,整个字典根据客户端Python规则运行,在本章中向表单视图添加内容和组件一节已有表述。因此,举例来说,你可以在右侧运算项中访问上下文。
扩展知识….
虽然这一机制对于标量字段非常明了,对于如何处理one2many和many2many字段则不太清晰。事实上在标准的Odoo中,对于这些字段我们在attrs属性中做不了什么。但如果你需要查看字段是否为空的话,可以在右侧运算项中使用[[6, False, []]]。
定义内嵌视图
在表单中显示one2many或many2many字段时,如未使用特殊组件则无需控制它如何渲染。同时对于many2one字段,有时要求能够影响所关联记录的打开方式。本节中,我们将学习如何为这些字段定义私有视图。
如何实现…
- 像平常一样定义字段,但不关闭标签:
1<field name="child_ids"> - 在标签中编写视图定义:
1234567891011<tree><field name="name" /><field name="email" /><field name="phone" /></tree><form><group><field name="name" /><field name="function" /></group></form> - 关闭标签:
1</field>
运行原理…
在Odoo加载表单视图时,首先会查看关联类型字段中是否有嵌套视图,前面已有指出。这些嵌套视图视图可以像我们前面所定义视图有完全相同的元素。仅在Odoo没有找到某些类型的嵌套视图时,它才使用这种类型的默认模型视图。
扩展知识….
虽然嵌套视图看上去是一个很棒的功能,但它让继承视图变得过于复杂。例如,在涉及到嵌套视图时,字段名不能保证唯一性,你通常会需要使用一些更复杂的XPath来在嵌套视图中选择元素。
因此,通常你应该更好地定义独立视图并使用form_view_ref和tree_view_ref键,这在本章在指定视图中打开动作一节中进行了讲解。
在表单视图边栏显示附件
在一些应用中,比如发票,需要根据文档填写数据。为让数据填写过程更为轻松,在Odoo 12中添加了一种新功能来在表单视图的边栏显示文档。
本节中,我们将学习如何背靠背的显示表单视图和文档:
TODO
ℹ️这一功能仅用于大显示器(>1534px),所以在你的视窗较小时,这一功能会隐藏。在内部该功能使用了一些响应式工具,因此该功能仅用于企业版。但你还是可以在模块中使用这段代码。Odoo会自动进行处理,因此如果在企业版中安装这个模块,会显文档,而在社区版中,它会进行隐藏且不会产生任何问题。
如何实现…
我们将启用这一功能来为res.partner 模型修改表单视图,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<record id="view_all_customers_form" model="ir.ui.view"> <field name="name">All customers</field> <field name="model">res.partner</field> <field name="arch" type="xml"> <form> <sheet> <group> <field name="name" /> <field name="email"/> </group> </sheet> <div class="o_attachment_preview" options="{types: ['image', 'pdf'], 'order': 'desc'}" /> <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> |
运行原理…
这一功能仅在模型有继承的mail.thread模型时才起作用。在表单视图边栏显示文件档,需要在chatter元素前添加一个带有o_attachment_preview类的空<div>。这样就好了,附加在chatter中的文档就会在表单视图的边栏显示。
默认,pdf和image类型的文档会按照日期升序显示。可通过提供附加选项来改变这一行为,选项如下:
- type:需要传递一个想要允许的文档类型列表。仅能使用两个值:pdf和image,例如,如果仅希望显示pdf类型的文件,可传递{‘type’: [‘pdf’]}。
- order:可用的值有asc和desc。这些让我们可以使用文档创建日期的升序或子程序来显示文档。
扩展知识….
大多数情况下,你会希望在记录的初始状态的边栏显示文档。如果想要根据域来隐藏附件预览,可以对<div>标签使用attrs来隐藏预览。
看下如下示例,它会在state字段不为draft时隐藏PDF预览:
1 2 |
<div class="o_attachment_preview" attrs="{'invisible': [('state', '!=', 'draft')]/> |
定义看板视图
到此,我们向读者展示了一系列可以打开显示表单的记录。虽然这些列表对于展示大量信息非常有效,但因缺乏设计的可能性而稍显无趣。本节中,我们会来学习看板视图,可让我们心更美观的方式展示记录列表。
如何实现…
- 定义kanban类型的视图:
1234<record id="view_all_customers_kanban" model="ir.ui.view"><field name="model">res.partner</field><field name="arch" type="xml"><kanban> - 列出你将在视图中使用的字段:
123<field name="name" /><field name="supplier" /><field name="customer" /> - 实现设计:
1234567891011121314151617181920212223<templates><t t-name="kanban-box"><div class="oe_kanban_card"><a type="open"><field name="name" /></a><t t-if="record.supplier.raw_value orrecord.customer.raw_value">is<t t-if="record.customer.raw_value">a customer</t><t t-if="record.customer.raw_value andrecord.supplier.raw_value">and</t><t t-if="record.supplier.raw_value">a supplier</t></t></div></t></templates> - 关闭标签:
123</kanban></field></record> - 将该视图添加到你的动作当中。这部分将留给读者作为练习。可以在GitHub的示例文件中找到完整示例:https://github.com/alanhou/odoo12-cookbook
运行原理…
在第2步中我们需要给出一个字段列表来在稍后访问它们。templates元素的内容必须是带有为kanban-box设置的t-name的单个t元素。
该元素中所编写内容会在每条记录中重复,带有针对 t 元素的特殊语法及t-*属性。有关它的细节,参见第十六章 网页客户端开发中使用客户端QWeb模板一节,因为在技术上看板视图是一个QWeb模板的应用。
有一些特别针对看板视图的修改。你可以在运行时使用变量read_only_mode、record和widget。字段可通过record.fieldname来进行访问,它是一个带有value和raw_value属性的对象,value是格式化为用户可以看得懂的字段值,raw_value是直接从数据库中取的值。
ℹ️Many2many字段在这里有个例外。通过变量record你只能获取ID列表。要展示用户为可读的内容,应使用field元素。
注意在模板顶部的链接中有 type 属性。该属性让 Odoo 以浏览模式(open)或编辑模式(edit)生成一个打开记录的链接,或者删除该记录(delete)。type属性也可以是对象或动作,会渲染调用模型的函数或动作的链接。两种情况下,你都需要为表单视图中的按钮补充属性,如本章向表单添加按钮一节中所述。除 a 元素外,还可以使用button元素,这里type属性的语法相同。
扩展知识….
有一些值得一提的帮助函数。如需为元素生成一个伪随机颜色,使用kanban_color(some_variable) 函数,它将返回一个设置background和color属性的CSS类。这通常用于t-att-class元素。
如果想要显示一个存储在二进制字段中的图像,使用kanban_image(modelname, fieldname, record.id.raw_value),如果在字段列表中包含了该字段并该字段已设置它返回一个数据URI,若字段未设置返回占位符,或如未在字段列表中包含该字段则返回一个由Odoo提供字段内容流媒体的 URL。如果你需要同步显示大量记录或图像很大时请不要在字段列表中包含该字段。通常在img元素的t-att-src属性中使用它。
小贴士:在看板视图中进行设计很不人性化。通常更好的做法是使用HTML类型的函数字段生成HTML,并通过Qweb视图生成这一HTML。这种方式来使用的是QWeb,但在服务端,你需要操作大量数据时会方便很多。
根据状态在列中显示看板卡片
本节向读者展示如何设置看板视图,在其中可将记录从一列拖拽到另一列,因而将所操作的记录移到另一种状态。
准备工作
从这里开始,我们将使用project模块,因为它定义的模型比那些base模块中定义的模型更适用于基于日期和基于状态的视图。所以在开始之前先将project添加到你的插件的依赖列表中。
如何实现…
- 为任务定义一个看板视图:
12345678910111213141516171819<record id="kanban_tasks" model="ir.ui.view"><field name="name">project.task.kanban</field><field name="model">project.task</field><field name="sequence">20</field><field name="arch" type="xml"><kanban default_group_by="stage_id"><field name="stage_id" /><field name="name" /><templates><t t-name="kanban-box"><div class="oe_kanban_cardoe_kanban_global_click"><field name="name" /></div></t></templates></kanban></field></record> - 使用该视图添加菜单和动作。这部分留作读者练习。
运行原理…
看板视图支持分组,让我们可以在相同列中显示共同带有一个分组字段的记录。这通常用于state或stage_id字段,因为它让用户可以仅通过拖拽到另一个列中修改该字段的值。在kanban元素设置default_group_by属性为你想要分组的字段名来使用这一功能。
要控制看板分组的行为,在Odoo中有一些可用的选项:
- group_create:该选项用于在分组的看板中隐藏或显示Add a new column选项。默认值为true。
- group_delete:该选项在看板组上下文菜单中启用或禁用Column delete选项。默认值为true。
- group_edit:该选项在看板组上下文菜单中启用或禁用Column edit选项。默认值为true。
- archivable:该选项在看板组上下文菜单中启用或禁用存档和恢复记录的选项。仅用于存在active布尔字段的模型。
- quick_create:通过这一选项,可以直接从看板视图中创建记录。
- quick_create_view:quick_create默认仅在看板中显示name字段。但通过quick_create_view选项,可以给出最小化表单视图的引用 来在看板中显示。
- on_create:如果想要在新建记录时使用quick_create并且不想要将用户重定向到表单视图,可以给出向导的引用来在点击Create按钮时打开向导。
扩展知识….
如未在单独属性中定义,搜索过滤器可通过对字段名设置名为group_by的上下文键来进行分组。
定义日历和甘特视图
本节将带你学习如何以可视化的方式来记录中显示和编辑有关日期和时长的信息。
如何实现…
按照如下步骤来为project.task模型添加一个日历视图:
- 定义一个calendar视图:
12345678910<record id="view_project_task_calendar" model="ir.ui.view"><field name="model">project.task</field><field name="arch" type="xml"><calendar date_start="date_start" date_stop="date_end"color="project_id"><field name="name" /><field name="user_id" /></calendar></field></record> - 使用该视图添加菜单和动作。这部分留作读者练习。
运行原理…
日历视图需要在date_start和date_stop属性中传递字段名来表明在构建可视化展示时查找哪些字段。仅使用带有Datetime或Date类型的字段,其它类型的字段会无法运行并生成错误。date_start是必填项,但可留空date_stop并使用date_delay属性来进行取代,它应为按小时表示时长的浮点字段。
日历视图让我们可以给带有相同值的字段同样的颜色(可自行指定)。使用这一功能,将color属性设置为所需的字段名。本例中,我们可以快速地看出哪些任务属性于相同的项目,因为我们将project_id设置为了决定颜色分组的字段。
在calendar元素体中所命名的这些字段在代表由逗号分隔的时间间隔所涵盖的区间中显示。
扩展知识….
日历视图有一些有用的属性。如果你想要在弹窗而非标准表单视图中打开日历项,请将event_open_popup设置为1。默认只需填写文件来创建新项,在内部调用模型的name_create函数来实际创建记录。如果想要禁用这一行为,将quick_add设置为0即可。
如果你的模型涵盖一整天,将涵盖一整天记录的字段名的all_day设置为true,否则设置为 false。
甘特视图
ℹ️甘特视图属于企业版,所以如果你使用的是Odoo社区版则无法使用它。如果要在社区版中使用,可以使用OCA的web仓库中具有相似功能的模块web_timeline。该模块引入了一种视图,类似于甘特图的展示。
在模块中使用gantt视图,需要在声明文件中添加web_gantt依赖。web_gantt可在企业版中使用。如果你在使用企业版,可以添加甘特视图如下:
1 2 3 4 5 6 7 8 9 10 |
<record id="view_project_task_calendar" model="ir.ui.view"> <field name="model">project.task.gantt</field> <field name="model">project.task</field> <field name="arch" type="xml"> <gantt date_start="date_assign" date_stop="date_deadline" default_group_by="user_id"> </gantt> </field> </record> |
在企业版的gantt视图中,可以使用如下选项:
- date_start:该选项的值应为在有甘特记录的开始日期的date或datetime类型字段。
- date_stop:该选项的值应为在有甘特记录的结束日期的date或datetime类型字段。
- date_delay:如果不想使用date_stop,则可以使用这一选项。它用于计算gantt记录的时长。
- duration_unit:该选项的值应为:minute, hour (默认值), day, week, month或year。
- default_group_by:这是gantt记录分组的字段名。
定义图表和透视表视图
本节中,我们将学习Odoo的 BI(商业智能)视图。它们是用于展示数据的只读视图。
准备工作
这里我们仍将使用project模块。可以配置graph和pivot视图来获取不同的数据。本例中我们仅来查看已分配的用户。我们将生成图表和透视表视图来查看根据用户的任务用户。同时,终端用户可以通过修改视图选项来生成他们所选的数据。
如何实现…
- 使用柱状图定义图表视图:
123456789<record id="view_project_tasks_graph" model="ir.ui.view"><field name="model">project.task</field><field name="arch" type="xml"><graph type="bar"><field name="user_id"/><field name="stage_id"/></graph></field></record> - 定义透视表视图:
12345678910<record id="view_project_tasks_pivot" model="ir.ui.view"><field name="model">project.task</field><field name="arch" type="xml"><pivot><field name="user_id" type="row"/><field name="project_id" type="col"/><field name="stage_id" type="col"/></pivot></field></record> - 使用该视图添加菜单和动作。这部分留作读者自行练习。
如果一切顺利,应该可以看到显示分配给哪个用户哪些任务的图表以及这些任务的状态。
运行原理…
graph元素中的type属性决定了图表视图的初始模式。可用的值有bar, line和chart,但默认值是bar。图表视图交互性很强,因此用户可以在不同模式间切的并添加和删除字段。透视表视图有它们自己的根元素pivot。
field元素告诉Odoo在哪个轴上显示什么。对于所有的视图模式,你至少需要一个带有row类型的字段以及一个带有measure类型的字段来显示有用的信息。row类型的字段决定分组,而那些measure类型的字段表示要显示的值。线状图仅支持每种类型的一个字段,而图表和柱状图可很好地处理带有一个度量的两个group字段。透视表视图支持你所定义的任意数量的group和measure。切换到不支持你所定义的group和measure数量字段的模式并会导致崩溃,只是有些字段会被忽略,呈现的结果会缺乏意义。
扩展知识….
对于所表图表类型,Datetime字段都不太好分组,因为你很少会碰到桢的字段值。因此,如果有row类型的Datetime字段时,同时指定带有如下值的interval属性:day, week, month, quarter或year。这会以给定的间隔进行分组。
透视表还支持对列的分组。对相应的字段使用col类型。
ℹ️类似于排序,分组也重度依赖于PostgreSQL。因此这里应用规则的字段必须在数据库的当前表中存在方能使用。
定义搜集所需所有数据的数据库视图,并在该视图上定义模型来让所有的需要的字段可用是常见的实践。
根据视图和分组的复杂度,构建图表可能会是一个开销很大的操作。这时考虑将auto_search设置为False,这样用户可以先调整所有参数,然后才触发搜索。
定义流存(cohort)视图
对于记录的存留分析,在 Odoo 12中添加了新的cohort视图。cohort视图用于发现指定时间段的记录的生命周期。通过cohort视图可以看到指定时间任意对象的流失率和存留率。
准备工作
流存视图是Odoo企业版的一部分,因此在社区版中无法使用。如果你使用的是企业版,需要在模块的声明文件中添加web_cohort。本例中我们将创建视图来查看任务的流存分析。
如何实现…
按照如下步骤来为project.task模型添加cohort视图:
- 定义一个cohort视图:
12345678910<record id="view_project_tasks_graph" model="ir.ui.view"><field name="name">project task cohort</field><field name="model">project.task</field><field name="arch" type="xml"><cohort date_start="date_start"date_stop="date_deadline"interval="month"string="Task Cohort" /></field></record> - 使用该视图添加菜单和动作。这部分留作读者自行练习。
运行原理…
要创建流存视图,需要传递date_start和date_stop。它们在视图中用于决定任意记录的时间区间。例如,如果你管理服务的订阅,订阅的开始时间为date_start,订阅过期的日期则为date_stop。
默认,流存视图会以月为间隔在存留模式中显示。你可以使用给定的选项来获取流存视图中不同的行为:
- mode:可以使用两种模式的cohort:retention(默认值:存留)或churn(流失)。retention模式初始为100%,随时间减少,而churn模式初始为0%,随时间增加。
- timeline:该选项接收两个值:forward (默认值)或backward。在部分情况下,你需要使用向前的时间轴。但如果date_start是未来的时间,会需要使用回退的时间轴。使用回退时间轴的一个示例为活动参与人员的注册,这时活动日期在未来而注册日期为过去。
- interval:默认流存按月分组,但可以在interval选项中进行修改。月之外,cohort还支持按日、按周和按年的时间间隔。
- measure:类似图表和透视表,measure用于显示给定字段的总计值。如果没有给定任何选项,cohort会显示记录的条数。
定义仪表盘视图
Odoo 12中引入了一种新的视图,名为仪表盘。它用于在同屏中显示多个视图以及不同的业务KPI。
准备工作
仪表盘视图是Odoo企业版的一部分,因此在社区版中无法使用。如果使用的是企业版,需要在模块的声明文件中添加web_dashboard依赖。
本例中,我们将展示一些KPI和已有视图。我们会在同屏中显示图表和透视表视图,因此如果还没有创建透视表和图表视图的话,请参照定义图表和透视表视图一节进行创建。我们将在仪表盘视图中使用这些视图的ID。
如何实现…
- 定义一个dashboard视图:
1234567891011121314151617181920212223242526272829303132<record id="view_project_tasks_dashboard" model="ir.ui.view"><field name="name">project task dashbaord</field><field name="model">project.task</field><field name="arch" type="xml"><dashboard><view ref="my_project.view_project_tasks_graph" type="graph" /><group><aggregate name="all_task"string="Total Tasks"group_operator="count"field="id" measure="__count__"/><aggregate name="progress_task"string="In Progress Tasks"domain="[('stage_id.name', 'ilike', 'In Progress')]"group_operator="count"field="id" measure="__count__"/><aggregate name="done_task"string="Completed Tasks"domain="[('stage_id.name', 'ilike', 'Done')]"group_operator="count" field="id"Chapter 10[ 335 ]measure="__count__"/><formula name="price_average"string="Overall Progress"value="record.done_task / record.all_task"widget="percentage"/></group><view ref="my_project.view_project_tasks_pivot" type="pivot"/></dashboard></field></record> - 使用该视图添加菜单和动作。这部分留作读者自行练习。
运行原理…
通过仪表盘视图,可以用总计和公式显示KPI。可以在同屏上展示多个视图。如果看视图的定义,你会看到我们添加的两个视图:一开始的图表视图和最后的透视表视图。显示这些视图,仅需使用带有 XML 引用和视图类型的标签。
我们通过<aggregate>标签展示了不同的KPI,包括总任务,进行中的任务和完成的任务。该标签将展示搜索视图的当前域中记录的总计结果。在<aggregate>标签中,可以使用可选的domain属性来显示指定记录集合的总计。默认总计函数显示记录的条数,但可通过使用group_operator属性来指定一个SQL总计函数,如avg或max。
有时无法使用<aggregate>展示KPI,需要一些其它的计算。借助于<formula>,我们可以为任意KPI定义公式。本例中,我们展示了所有条件的进度并使用了可选属性widget来将该值展示为百分比。
扩展知识….
另一个有用的元素是<widget>标签。通过它可以用户界面中显示你所选的数据。在第十六章 网页客户端开发中,我们将学习如何创建自定义组件。