> Odoo > Odoo 14开发者指南第九章 后端视图 全书完整目录请见:Odoo 14开发者指南(Cookbook)第四版
在此前的章节中,我们学习了Odoo的服务端和数据库层面的知识。本章中将学习Odoo的UI层面的知识。我们将学习如何创建不同类型的视图。除视图外,本章还会讲解组件,如动作按钮、菜单和微件,有助于我们将应用变得对用户更友好。完成本章后,读者将可以设计Odoo后台的UI。注意本章并不包含Odoo的网站部分,会有单独的章节进行讲解。
本章中,我们将讲解如下小节:
添加菜单项和窗口动作
打开指定视图的动作
向表单视图添加内容和微件
向表单添加按钮
向表单和动作传递参数 – 上下文
在记录列表上定义过滤器 – 域
定义列表视图
定义搜索视图
添加搜索过滤器侧边栏
修改已有视图 – 视图继承
定义文档类型表单
使用attrs的动态表单元素
定义内嵌视图
在表单视图边栏显示附件
定义看板视图
根据状态在列中显示看板卡片
定义日历视图
定义图表和透视表视图
定义留存(cohort)视图
定义仪表盘视图
定义甘特视图
定义活动视图
定义地图视图
技术准备
贯穿本章,我们将假定你的数据库中安装了base插件并且有一个空的插件模块,可以添加各节中的XML代码到插件声明文件所引用的数据文件中。参见第三章 创建Odoo插件模块 ,获取更多有关应用插件中修改的信息。
本章的技术要求包含一个在线Odoo平台。
本章中所使用的所有代码都在可GitHub仓库中下载:https://github.com/alanhou/odoo14-cookbook
添加菜单项和窗口动作
为用户添加新功能最明显的方式是通过添加菜单项。在点击菜单项时,会发生一些操作。本节将带你学习如何定义这些操作。
我们将创建一个顶级菜单及子菜单,它会打开一个所有客户的列表。
这也可使用网页用户界面借由settings菜单来实现,但更推荐使用XML数据文件,因为在创建我们的插件模块时需要使用此方法。
准备工作
本节中,我们需要一个依赖于account模块的模块,因为account模块对res.partner模型添加了一些将字段用于区分客户和供应商记录。因此,如果使用已有模块,请在声明文件中添加对account的依赖。或者可以直接通过GitHub仓库 获取初始模块。
如何实现…
在我们插件模块的XML数据文件中,执行如下步骤:
定义待执行的动作:
< act_window id ="action_all_customers"
name ="All customers"
res_model ="res.partner"
view_mode ="tree,form"
domain ="[('customer', '=', True)]"
context ="{'default_customer': True}" />
创建顶级菜单,如下:
< menuitem id ="menu_custom_top_level"
name ="My App menu"
web_icon ="my_module,static/description/icon.png" />
在菜单中引用我们的动作:
< menuitem id ="menu_all_customers"
parent ="menu_custom_top_level"
action ="action_all_customers"
sequence ="10" />
如果我们现在升级模块的话,会看到一个可打开名为All Customers子菜单的顶级菜单My App menu。点击该菜单项会打开一个所有客户的列表。
运行原理…
第一个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:这会设置列表视图中可见的记录默认数量。本例,我们将其限定为20,但如果没有为limit赋值,Odoo会使用默认值80。
📝小贴士: 在老代码中,你会经常看到tree视图模式。这是截至并包含Odoo 11中所使用的内部名称。版本12还接收该值,但会等同于list来进行处理。
接下来,我们创建顶级菜单到可点击的末梢菜单项的菜单项等级。menuitem元素最重要的属性如下:
act_window和menuitem是隐藏所进行操作的快捷XML标签。如果不想使用快捷XML标签,那么可以通过该<record>标签创建一个ir.actions.act_window和ir.ui.menu模型的记录。例如,如果想要通过<record>加载act_window,可以这么做:
< 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快捷标签中不支持。要使用这些属性,我们需要使用带有如下字段的record元素:
res_id:如果打开一个表单,你可以通过在这里设置其ID来打开指定记录。这对于多步骤向导或者在你需要经常查看或编辑指定记录时都非常有用。
search_view_id:这会指定一个具体的搜索视图来用作树状和图表视图。
auto_search:默认为True。如果对象的搜索非常耗时且/或消耗大量资源的话将其设置为False。通过这种方式,用户可以重审搜索参数并在满意时按下Search。使用默认值搜索会在动作打开时立即触发。
记住左上角的菜单(或是企业版中应用的图标)及顶栏中的菜单都是由菜单项所组成。唯一的不同的是左上角中菜单没有父级菜单,但顶栏上的这些菜单有顶栏的相应菜单项作为父级。在左栏中,等级结构更为明显。
同时应记住出于设计原因,如果第二级菜单有子菜单时第一级菜单会打开下拉菜单。Odoo会根据子菜单项的排序打开第一个菜单项动作。
参照如下内容来学习有关菜单和视图的更多知识:
ir.actions.act_window是最常见的动作类型,但菜单可引用任意类型的动作。技术上而言链接一个客户端动作、服务端动作或在ir.actions.*命名空间中所定义的任意其它模型都毫无分别。只是在后台由什么动作组成上会存在差别。
如果你只需要调用具体动作稍微多一点的灵活性,使用返回窗口动作的服务端动作。如果你需要完全的灵活性,使用客户端动作(ir.actions.client),它允许有一个完全自定义的用户界面。但是,仅在没有其它选择时才考虑使用它,因为这样会损失掉大量的Odoo便捷帮助工具 。
其它内容
有关所有视图过滤器的更深入讲解参见本章的在记录列表上定义过滤器 – 域 一节
打开指定视图的动作
在未指定时窗口动作自动决定所使用的视图,但有时,我们希望一个动作打开具体某一视图。
我们会为res.partner模型创建一个基本表单视图,并且随后新建一个具体打开该表单视图的窗口动作。
如何实现…
定义partner的最小化树状和表单视图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
< 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 >
更新添加菜单项和窗口动作 一节中的动作来使用新的表单视图:
< 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属性的record元素。record元素上的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>。如果想要使用自定义视图,可以遵循给定的语法:
< 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 >
上例只是act_window的另一种书写方式,在Odoo的基础代码中,会发现存在这两种类型的动作。
向表单视图添加内容和微件
前面一节展示了如何为一个动作选择指定的视图。现在我们将演示如何让表单视图更为实用。本节中,我们将使用在指定视图中打开动作 一节中所定义的表单视图。在表单中视图中添加微件和内容。
如何实现…
定义表单视图的基础结构:
< 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 >
< ! --form content goes here -->
< /form >
< /field >
< /record >
头部栏通常用于放置动作按钮和进度管道,在表单中使用如下代码添加一个头部栏:
< header >
< button type ="object" name ="open_commercial_entity"
string ="Open commercial partner"
class ="btn-primary" />
< /header >
在表单中添加字段,使用group标签进行视觉上的组织:
< group string ="Content" name ="my_content" >
< field name ="name" />
< field name ="category_id" widget ="many2many_tags" />
< /group >
现在表单中应该会显示带有一个按钮的顶栏以及两个垂直对齐的字段,如下图所示:
图9.1 – 表单视图截图
运行原理…
我们先来看ir.ui.view模型中的arch字段。首先,注意视图使用用XML进行定义,因此需要向arch字段传递type=”xml”属性,否则会让解析器不知所措。同时还要求视图定义中必须包含良好的XML结构,否则在加载这个代码段时会出现问题。
下面我们来逐一学习前面所使用的标签并对其它的可用标签进行总结。
form
在定义表单视图时,arch字段的第一个元素必须为form元素。内部通过它来获取记录的type字段,
除下面的元素以外,还可以在form标签内使用自定义的HTML。对Odoo未知的元素算法均会视为普通的HTML并直接传递给浏览器。要谨慎使用,因为所填充的HTML会与Odoo元素生成的HTML代码进行交互,可能会让渲染出现偏差。
header
这个元素是显示在表单头部中元素的容器,它被渲染为一个白条。通常会像示例中一样将动作按钮放在这里。此外,如果模型有state字段的话,也可以选择状态栏。
button
button元素用于允许用户触发某一动作。参见向表单添加按钮 一节获取更多信息。
group
<group>元素是Odoo的主要元素,用于组织内容。<group>元素中的字段以它们的标题进行渲染,并且相同组中的所有字段进行了对齐,这样在视觉上可以看出它们属于相同组。还可以内嵌<group>元素,这会让Odoo将所包含字段以相邻的列进行渲染。
通常,应使用<group>组织方式来在表单视图中显示所有字段,仅在必要时才考虑其它元素,如<notebook>, <label>, <newline>等。
如果对组赋值了string属性,它的内容会渲染为组的标题。
也应培养为字段的每个逻辑组赋值name的习惯。这一名称对用户不可见,但在下面小节中重载视图时会很有帮助。在表单定义中保持该名称唯一,可避免引用时分不清哪一组的混淆。不要使用string属性来做区分,因为string值会因为翻译的原因而最终发生改变。
field
为实际显示和操作数据,表单视图应包含一些field元素。示例如下:
< field name ="category_id" widget ="many2many_tags" readonly ="1" />
它们有一个必填属性,name,指代模型中的字段名称。早前,我们让用户可以编辑partner的分类。如果我们想禁用字段的编辑功能,可以将readonly属性设置为1或True。这个属性实际可以包含一小段Python代码,所以readonly=”2>1″也会让该字段只读。对于invisible属性也是如此,此时会从数据库中读取值,而不对用户展示。稍后我们会了解其使用场景。
你一定注意到了分类字段的widget属性。它定义了字段中的数据向用户展示的方式。每种类型的字段都有一个标准微件,因此无需显式的指定widget。但有些类型提供了多种展现方式,这时你可能会选择非默认的那种。完整的可用微件列表超出了本节的范畴,请参照Odoo源代码来进行尝试。参见第十四章 CMS网站开发 来了解如何制作自己的微件。
Notebook和page
如果模型字段过多,那么可以使用<notebook>和<page>标签来创建选项卡。<notebook>标签中的每个<page>会新建一个选项卡,页面中的内容为选项卡的内容。下例中创建2个选项卡、每个选项卡3个字段:
< 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属性。示例如下:
< field name ="category_id"
attrs ="{'readonly': [('state', '='. 'done')]}"
groups ="base.group_no_one" />
attrs属性会在使用attrs的动态表单元素 一节中进行讨论,groups属性让我们可仅向某些分组成员显示一些元素。简单地说,组的完整XML ID(多个组由逗号分隔)是属性,元素对非指定组内的成员均不可见。
其它标签
有些情况下可能想规避严格的分组布局。例如,如果希望记录的name字段以标题进行渲染,字段的标签会干涉到外观。这种情况下,不要将字段放到group元素中,而是使用普通的HTML h1元素。然后在h1元素前,为字段名添加一个label元素及for属性:
< label for ="name" />
< h1 > < field name ="name" /> < /h1 >
这会将字段的内容渲染为大标题,但字段名以小号类型在大标题之上出现。这基本上就是标准的partner表单的形式。
如果需要在分组内有换行,可以使用newline元素。内容为空:
另一个有用的元素是footer。在以弹窗打开表单时,在这里放动作按钮会比较好。它也会以单独栏进行渲染,与header元素类似。
表单视图还有一些特殊的微件,如web_ribbon。可以配合<widget>标签使用,如下:
< widget name ="web_ribbon" title ="Archived" bg_color ="bg-danger" attrs ="{'invisible': [('active', '=', True)]}" />
可以使用attrs来根据条件隐藏或显示丝带角标。不清楚attrs也不必担心。在本章的使用attrs的动态表单元素 一节中会讲到。
译者注: 丝带角标?这算什么烂翻译?哈哈,这个ribbon和 Excel中又不同,英语哪里比得我们汉语博大精深,一个词到处用。Ribbon对于西方世界想必有神圣的意义,Pink Ribbon, Red Ribbon…,不清楚的问谷哥、度娘,这里还是上图为妙:
小贴士: 不要对XML节点使用string属性(或任何其它的可翻译属性),这会在其它语言中因继承前应用翻译的原因破坏视图的重载。
扩展知识…
因表单视图基本上是带一些插件的HTML,Odoo还对CSS类做了一些扩展使用。其中有两个非常有用的是oe_read_only和oe_edit_only。这两个类会让元素分别在只读模式或仅在编辑模式下可见。例如,要让label仅在编辑模式下可见,用如下代码:
< label for ="name" class ="oe_edit_only" />
另一个非常有用的类是oe_inline,可以在字段中使用它来将内容渲染为行内元素,以避免引起不必要的换行。在将字段嵌入文本或其它标记标签时使用该类。
此外,form元素可包含 create, edit和delete属性。如果将其中的属性的设为false,该表单相应的动作就不可用。如未进行显式的设置,动作的可用性会由用户的权限来决定。注意这里的目的仅是针对UI层面,不要使用它们来进行权限控制。
其它内容
微件和视图已经提供了大量的功能,但迟早你会碰到已有组件和视图无法满足的需求。参见下面的小节来创建自己的视图和微件:
向表单添加按钮
按钮在表单视图中用于处理用户动作。我们在前面的表单视图中添加过一个按钮,但有好几种不同类型的按钮可供使用。本节将添加另一个按钮,用于让用户打开其它视图。将下面的代码同样放到header元素中。
如何实现…
添加一个引用动作的按钮:
< 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属性包含函数名称。
string属性用于赋值用户所看到的文本。
扩展知识…
使用btn-primary CSS类来渲染高亮的按钮,用btn-default来将其渲染为普通按钮。普通按钮通常在向导中用作取消按钮或提供一个视觉上不明显的二级动作。设置oe_link类会让按钮看起来像一个链接。也可以使用其它bootstrap按钮类来获取不同的按钮颜色。
对object类型按钮的调用会返回一个描述动作的字典,然后在客户端进行执行。这样可以实现多屏向导或是仅打开另一条记录。
📝注意点击按钮总是会让客户端在运行该方法前发送一个写入或创建的调用。
还可以通过替换string属性来把内容放到button标签中。这通常用于按钮框,在定义文档样式表单 一节中会讲到。
向表单和动作传递参数 – 上下文
Odoo内部的每个方法都可以访问一个名为context的字典,这是由动作带到分发该动作的方法中的。UI层面也可以进行访问,有多种通过设置上下文中值方式来进行更改。本节中,我们将通过使用语言、默认值和隐式过滤器来探讨使用这一机制的几种应用场景。
准备工作
虽然不是特别必要,如未安装法语可安装一下来增加本节的趣味性。参见第十一章 国际化 来了解如何实现。如果你本来使用的就是法语数据库,则将fr_FR修改为其它的语言;en_US用作英语(zh_CN为简体中文)。另外,对一个客户点击Active(悬浮时变为Archive)按钮来进行存档,验证下这个成员是否不再出现在列表中。
如何实现…
新建一个动作,与添加菜单项和窗口动作 一节很相似(好吧,爱国的我们还是使用中文好了,比利时人才爱法语 – Je suis Alan, coment allez-vous?):
< act_window
id ="action_all_customers_cn"
name ="所有客户"
res_model ="res.partner"
domain ="[('customer_rank', '>', 0)]"
context ="{'lang': 'zh_CN', 'default_lang': 'zh_CN', 'active_test':False, 'default_customer_rank':1}"
/>
添加一个调用该动作的菜单。这部分留作读者练习。
在打开该菜单时,视图会显示为中文,而你创建一个新成员时,中文也将作为预选的语言。一个不太明显的区别是我们还是会看到未激活(存档)的成员记录。
运行原理…
上下文字典有几个数据源。首先,读取当前用户记录的一些值(lang和tz为用户的语言和时区)。然后,有一些插件会添加它们自己使用的一些键。此外,UI添加当前使用的模型和记录作为键(active_id, active_ids, active_model)。还有在在指定视图中打开动作 一节中我们学习到了可以在动作中添加自己的键。这些会合并到一起并传递给背后的服务端函数,以及客户端UI。
因此,通过设置lang上下文键,我们将展示语言强制为汉语。你会注意到这并不会修改整体的UI语言,因为仅仅是我们所打开的列表视图位于这一上下文作用域内。其它的UI已由用户原有语言中所包含的其它上下文所载入。但是,如果你在这个列表视图中打开一个记录,它也会以汉语进行展示,并且如果你在该表单中打开一条关联记录或者按下执行动作的按钮,都会显示为汉语。
通过设置default_lang,我们为这个上一文域中所创建的每条记录设置了一个默认值。通用模式是default_$fieldname: my_default_value,这样我们为新创建的成员设置了默认值。假定我们的菜单是有关客户的,设置了default_customer_rank: 1来作为客户的默认排名。但是,这是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 />快捷标签中当心context变量的使用。它们在安装时运行,和你所想要的相差甚远。如果需要在上下文中使用变量,请用<record />。
我们还可以对按钮添加不同的上下文。和在动作中添加上下文键的方式相同。这样按钮调用的函数或动作会在给定的上下文中运行。
大部分以Python运行的表单元素属性也可访问上下文字典。invisible和readonly属性即为这种情况。因此,在希望元素有时在表单中显示,有时又不显示时,设置invisible属性为context.get(‘my_key’)。对于导致字段不可见的动作,设置上下文键为my_key: True。这种策略让我们无需对不同情况进行重写即可调整表单。
也可以在关联字段上设置上下文,它会影响到字段载入的方式。通过将form_view_ref或tree_view_ref键设置为视图的完整XML ID,可以为该字段选择指定的视图。在同一对象有多个相同类型的视图时这会很有必要。没有这个键时,获取到最小序号的视图,很多时候不需要这一效果。
其它内容
上下文还用于设置默认搜索过滤器。可以在本章定义搜索视图 一节中学习有关默认搜索过滤器的知识。
更多默认设置参见下一节:在记录列表上定义过滤器 – 域 。
学习如何安装中文,参见第十一章 国际化 。
可参见第六章 管理模块数据 学习关联字段的编写语法。
在记录列表上定义过滤器 – 域
我们已经在本章第一节中学习到了作用域的示例,即[(‘customer’, ‘=’, True)]。通常,我们需要通过动作显示所有可用记录的一个子集,或仅允许记录的一部分作为many2one的关联对象。在Odoo中描述这些过滤器的方式就是使用域。本节描述如何使用域来显示一部分成员。
如何实现…
要通过动作显示成员的一个子集,需要执行如下步骤:
为不讲法语的客户添加一个动作:
< record id ="action_my_customers" model ="ir.actions.act_window" >
< field name ="name" > 我不说法语的所有客户< /field >
< field name ="res_model" > res . partner < /field >
< field name ="domain" >
[ ( 'type' , '=' , 'contact' ) , ( 'user_id' , '=' , uid ) , ( 'lang' , '!=' , 'fr_FR' ) ]
< /field >
< /record >
为客户或供应商添加一个动作:
< record id ="action_no_email_or_phone" model ="ir.actions.act_window" >
< field name ="name" > 没有email 或电话的客户< /field >
< field name ="res_model" > res . partner < /field >
< field name ="domain" > [ '|' , ( 'phone' , '=' , False ) , ( 'email' , '=' , False ) ] < /field >
< /record >
添加调用这些动作的菜单。这部分留作读者练习。
运行原理…
域的最简单形式为一个带有三个元素元组的列表,第一个元素中包含要操作模型的字段名,第二个元素为运算符,前两个都采用字符串的形式,第三个值为该字段需要进行检查的值。上例中就是这么做的,可解释为:所有这些条件应作用于我们所需的记录。这实际上是一个简写,因为域知道那两个前置运算符& 和 |,其中&是默认值。因此规范化的形式第一个域应这样写:
[ '&' , '&' , ( 'type' , '=' , 'contact' ) , ( 'user_id' , '=' , uid ) , ( 'lang' , '!=' , 'fr_FR' ) ]
译者注:在 xml 中以上写法会报错,需将&转义,写为
[ '&' , '&' , ( 'type' , '=' , 'contact' ) , ( 'user_id' , '=' , uid ) , ( 'lang' , '!=' , 'fr_FR' ) ]
另在测试建议加上–dev=all,译者在测试时出现过设置为法语但未被过滤的情况,添加–dev=all 则正常,是未正常更新或缓存原因尚不确定
虽然更长的表达式有些难于阅读,但前置运算符的优势在于它们的作用域进了严格的定义,让我们不必担心运算符优级级和括号的使用。有两个表达式,第一个&对应’&’, (‘type’, ‘=’, ‘contact’), (‘user_id’, ‘=’, uid) 来作为第一个运算项, (‘lang’, ‘!=’, ‘fr_FR’)作为第二个运算项。然后,第二个&对应的是(‘type’, ‘=’, ‘contact’)作为第一个运算项,而(‘user_id’, ‘=’, uid) 作为第二个运算项。
第二步中,我们要写出完整形式,因为用到了 | 运算符。
举个例子,假设我们需要一个这样复杂的域: [‘|’, (‘user_id’, ‘=’, uid), ‘&’, (‘lang’, ‘!=’, ‘fr_FR’), ‘|’, (‘phone’, ‘=’, False), (’email’, ‘=’, False)]。参见下图了解域是如何运行的:
图9.2 – 作用域的运行
还有一个 ! 运算符用于取反,但既然有逻辑等式和像 !=和 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 字段的模型,它搜索右侧运算项的子项。右侧运算项包含于结果中。
=? 如果右侧运算项为 false 运行结果为 true;否则它和=相同。这用于由程序生成的域,在设置值时用值进行过滤,否则忽略该值。
表 9.1
注意某些运算符仅用于特定的字段和值。例如,[(‘category_id’, ‘in’, 1)]无效,会报错,而[(‘category_id’, ‘in’, [1])]则是有效的作用域。
使用域进行搜索的隐患
对于传统字段这些都没有问题,但在搜索非存储函数字段时这是一个广为人知的问题。通过第四章 应用模型 中所讲到的,在代码中加入搜索函数即可处理这个问题。
另一个困扰开发人员的问题是Odoo对于使用否定运算符搜索one2many和many2many字段的处理。假设有一个带有A标签的用户并使用 [(‘category_id.name’, ‘!=’, ‘B’)]搜索。会照预期在结果中显示该用户,但在给该用户加上 B 标签时,它还是出现在结果中,因为对于搜索算法来说,有一条关联的记录(本例中为 A)并不符合这一条件。这时删除 A 标签, B 成为其唯一标签,可以过滤掉该用户。如果再删除 B 标签,该用户已无标签,它还是会被过滤掉,因为关联记录上的条件预设的是存在该记录。但在其它情况中,这是所希望的结果,所以不应修改标准行为。如果要采取不同做法,添加一个照你所需方式运行的搜索函数。
📝重要信息 :人们在编写 XML 文件并用到域时经常会忘记这点。需要对小于运算符进行转义。搜索指定日期前所创建的记录需要在 XML 中写成[(‘create_date’, ‘<’, current_date)] 。
Odoo中大量使用到了作用域。你会发现它在Odoo中无处不在,用于搜索、过滤、安全规则、搜索视图、用户动作等等。
如果需要操作不是由程序创建的域,使用odoo.osv.expression中所提供的工具函数。is_leaf, normalize_domain, AND和OR函数让我们可以按 Odoo 相同的方式合并域。不要自行操作,因为有很多需要考虑到的极端情况,很可能会注意不到。
其它内容
有关域的标准应用,参见定义搜索视图 一节。
定义列表视图
在对表单视图花了一番功夫后,现在我们来快速地看下如何定义列表视图。内部有些地方称之为树状视图,另一些地方则称其为列表视图,但因为 Odoo 视图框架中还有另一种结构称为 tree,我们将保持列表视图的叫法。
如何实现…
定义列表视图:
< record id ="tree_all_contacts" model ="ir.ui.view" >
< field name ="model" > res . partner < /field >
< field name ="arch" type ="xml" >
< tree decoration -bf ="customer_rank > 0"
decoration -danger ="customer_rank > 0 and supplier_rank > 0" decoration -warning ="supplier_rank > 0" >
< field name ="name" />
< field name ="user_id" widget ="many2one_avatar_user" />
< field name ="state_id" optional ="hide" />
< field name ="country_id" optional ="show" />
< field name ="customer_rank" invisible ="1" />
< field name ="supplier_rank" invisible ="1" />
< /tree >
< /field >
< /record >
在本章添加菜单项和窗口动作 一节中所创建的动作中注册一个列表视图。
< record id ="action_all_contacts" model ="ir.actions.act_window" >
< field name ="name" > All Contacts < /field >
< field name ="res_model" > res . partner < /field >
< field name ="view_mode" > tree , form < /field >
< field name ="context" > { 'tree_view_ref' : 'my_module.tree_all_contacts' } < /field >
< field name ="limit" > 20 < /field >
< /record >
添加菜单调用这些动作。这部分留作读者练习。
安装/升级模块。之后,就可以看到客户的列表视图了。根据不同的条件各行也会显示不同的样式。
译者注:对于列表视图部分的颜色显示,为便于查看,译者做了一下调换,另 Odoo 13开始移除了 customer和 supplier 布尔字段,所以这里使用的是 customer_rank 和 supplier_rank,在 name_search 中通过res_partner_search_mode上下文来实现搜索:
context ={ 'res_partner_search_mode' : 'supplier' }
运行原理…
我们已经可以知道这里发生了什么。我们定义了一个视图,类型为 tree,并使用ir.actions.act_window.view元素将其关联到我们的动作上。因此,唯一剩下要讨论的是 tree 元素及其语法。使用列表并没有很多的设计选项,该元素仅有的有效子元素是field和button元素。也可以在列表视图中使用一些微件,本例中,我们使用了many2one_avatar_user这个小部件。列表视图支持一个特殊的微件handle。只针对列表视图,用于整型字段,渲染为一个可拖拽手柄,让用户可通过将列表中的行拖拽到不同位置来更新字段值。用于排序或优先级字段。
通过使用optional,可以有选择地显示字段。对字段添加optional属性让用户可以随时在UI界面中显示或隐藏某列。本例中,我们对country和state字段使用了该属性。
这里的新内容是 tree 元素中的decoration属性。它包含在行中选择哪种字体及/或颜色的规则,设置的形式为decoration-$name=”Python code”。所有的匹配转换成相应的 CSS 类,因此前面的视图将既是供应商又是客户的用户渲染为红色,客户渲染为加粗字段,而供应商为黄色。在Python代码中,仅能使用在视图定义中的字段名称,因此我们还需要customer_rank和supplier_rank字段。这些字段设为隐藏,因为我们只是需要数据而不用向用户展示这些字段。可以使用的类有decoration-bf (加粗), decoration-it (斜体)以及bootstrap类decoration-danger, decoration-info, decoration-muted, decoration-primary, decoration-success和decoration-warning。
扩展知识…
对于数值字段,可以添加一个 sum 属性来让该字段以在属性中设置为提示的文本进行加总。不太常见的属性有avg, min和max分,别用于显示平均值,最小值和最大值。注意这4个属性仅用于当前可见的记录,因此可能需要调整动作的limit 来让用户可即时看到所有记录(参见添加菜单项和窗口动作 一节)。
tree元素有一个非常有趣的属性editable。如果将其设置为top或bottom,列表的行为完全不同。没有它的话,点击某行会打开该行的表单视图。带上它点击某行会让其在行内可编辑,将可见字段渲染为了表单字段。这对于本章后面定义内嵌视图一节中讨论的嵌套列表视图尤为有用。top或 bottom 选项关乎新行在列表的顶部还是底部添加。
默认记录根据所显示模型的_order属性进行排序。用户可通过点击列的头部来修改排序,但也可通过在tree元素中设置default_order属性来设置不同的初始排序。语法与_order中的相同。
默认,记录按照所显示模型的_order属性进行排序。用户可通过点击列头来更换排序,但也可以在tree 元素中通过设置default_order 属性来设置不同的初始排序,语法与_order 中的相同。
ℹ️排序通常会让新开发者感到沮丧。因为Odoo通过PostgreSQL来完成这一工作,只能对PostgreSQL已知的字段排序并且这些字段需要在同一张数据表中。因此,如果想要通过函数或关联字段进行排序,确保要设置store=True。如果要对继承自另一个模型的字段排序,声明一个存储的关联字段。
tree元素的create, edit和delete属性与我们在本章向表单视图添加内容和微件 一节中所描述的作用相同。如果设置了editable属性它们还决定可进行哪些控制。
定义搜索视图
在打开列表视图时,会注意到右上角的搜索字段。如果在这里进行输入,可获取搜索推荐,还有一些预定义的过滤器可供选择。本节中将讲解如何定义这些推荐和选项。
如何实现…
定义搜索视图:
< 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_rank', '>', 0)]" />
< group expand ="0" string ="Group By" >
< filter string ="Country" name ="country" context ="{'group_by':'country_id'}" />
< /group >
< /search >
< /field >
< /record >
设置动作来使用它:
< record id ="action_all_contacts" model ="ir.actions.act_window" >
< field name ="name" > All Contacts < /field >
< field name ="res_model" > res . partner < /field >
< field name ="view_mode" > tree , form < /field >
< field name ="search_view_id" ref ="search_all_customers" />
< /record >
在搜索框中输入内容时,即可通过名称、分类和银行账号字段来进行搜索。如果所搜索的为系统中银行账号的一个子字符串,甚至会搜出完整银行账号对应的内容。
运行原理…
对于name,我们仅仅是将该字段列出供用户搜索,语法中的保留为默认,即对字符串字段的子字符串搜索。
对于分类我们做了一些有意思的操作。默认,搜索词作用于many2many字段触发器name_search,这时分类名的子串搜索。但根据分类结构,搜索你要找的分类或其子类可以非常方便。思考一下主分类邮件订阅者,其子分类为周订阅、月订阅及其它订阅类型。使用前面的搜索视图定义来搜索邮件订阅者会一次性给出订阅任意类型邮件的用户,这比搜索每种类型再合并结果要方便得多。
filter_domain属性可以包含任意作用域,因此不受在name属性中命名的相同字段的搜索限制,也不仅只能搜索单项。变量self是用户所填入的,也是在这里所唯一能使用的变量。
针对成员的默认搜索视图更详细的示例如下:
< field name ="name"
filter_domain ="[
'|', '|',
('display_name', 'ilike', self),
('ref', '=', self),
('email', 'ilike', self)]" />
这表示用户不用考虑搜索什么。他们只需输入一些字母然后按下 Enter 键,即有可能包含我们要搜索的字符串。
对于bank_ids字段我们使用另一种技巧。字段的类型不仅决定对用户输入的默认搜索方式,还定义了Odoo呈现推荐的方式。还有既然many2one是唯一提供自动补全的字段,虽然bank_ids是一个one2many字段,我们通过设置 widget 属性来强制Odoo这么做。没有它,我们需要在没有补全推荐的情况下搜索该字段。对于many2many字段同样如此。
📝注 :注意带有many2one微件的所有字段在用户每次按下键盘时会触发对于该模型的搜索,不要过度使用。
应该将最常使用的放在最上面,因为第一个字段是在用户输入内容并按下 Enter所进行的搜索。搜索框也可配合键盘使用,按向下键选择一条推荐,通过按向右键打开many2one的被全推荐。如果你告诉用户并注意维护搜索视图中的字段排序的话,这比先进行输入来得更有效率,只需要拿起鼠标选择一项即可。
filter元素创建一个按钮,将过滤器的domain属性添加到搜索域中。应该添加一个内部逻辑name 和一个string 属性来向用户描述过滤器。
<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来了解更多有关去重音的知识。
添加搜索过滤器侧边栏
Odoo提供了另一种展示搜索过滤器的方式,即搜索过滤器侧边栏。这个侧边栏在视图的侧边显示一个过滤器列表。搜索侧边栏对于经常使用搜索过滤器的用户非常有用。
准备工作
搜索侧边栏是搜索视图的一部分。因此,本节中,我们将继续使用前一小节中的my_module插件。我们将对前面设计的搜索视图添加侧边栏。
如何实现…
在搜索视图中添加<searchpanel> ,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
< 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_rank', '>', 0)]" />
< group expand ="0" string ="Group By" >
< filter string ="Country" name ="country" context ="{'group_by':'country_id'}" />
< /group >
< ! --Search Panel code -->
< searchpanel >
< field name ="user_id" icon ="fa fa-users" />
< field name ="category_id" icon ="fa fa-list" select ="multi" />
< /searchpanel >
< /search >
< /field >
< /record >
更新模块来应用修改。在更新后,就可以在视图的左侧看到搜索侧边栏了。
扩展知识…
添加搜索侧边栏需要在搜索视图中使用<searchpanel> 标签。需要在搜索边栏中添加字段来添加过滤器。
本例中,我们首先添加了user_id 字段。还需要对字段添加一个icon 属性。图标显示在过滤器标题的前面。在搜索边栏中添加了字段之后,会显示图标和标题,在它们下面,显示所有用户的列表。点击用户之后,列表视图中的记录会进行过滤,只会看到指定用户的联系人。在这个过滤器中,只会有一个处于激活状态,也就是说点击另一个用户过滤器时,之前的用户过滤器会被移除。如果希望激活多用户过滤器,可以使用属性select=”multi” 。使用该属性后,会在每个过滤选项前看到有复选框,就可以一次启用多个过滤器。我们对category_id 过滤器使用了select=”multi” 属性。这让我们可以一次选取、过滤多个分类。
📝注 :在对many2one 和many2many 字段使用侧边栏过滤器时要小心。如果关联模型记录过多的话,为避免性能问题仅会显示前200条记录。
其它内容
如果希望显示分组显示搜索边栏内容的话,可以对使用字段使用groupby属性。本例中,如果希望分类按照父级进行分组,可以对groupby 属添加parent_id 字段,如下:
< field name ="category_id" icon ="fa fa-list" select ="multi" groupby ="parent_id" />
这时在显示分类过滤器时会根据记录的父类分组。
修改已有视图 – 视图继承
至此,我们都忽略了已有视图,声明的都是全新的视图。这在教学上讲得通,但实际是很少碰到为已有模型定义新视图的情况。而是对已有视图进行适当的修改,可能是显示在插件的模型中所添加的字段,或是根据自身或客户需求对它们进行自定义。
本节中,我们将修改默认的partner表单来显示记录的最后修改日期并通过修改搜索视图来让手机号字段可进行搜索。然后,我们会修改partner列表视图中一列的位置。
如何实现…
在默认表单视图中插入字段:
< 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 >
向默认搜索视图添加字段:
< 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 >
向默认列表视图添加字段:
< 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 on字段。在搜索框中输入内容进行搜索时,会提示对成员进行手机号的搜索,在成员列表视图中,可以看到电话和email的顺序发生了变化。
运行原理…
第1步中我们对表单继承添加了一个基础结构。你可能也猜到了,这里重要的字段是inherit_id。我们需要向其传递希望修改的视图(所继承的)的XML ID。arch字段包含对所继承的视图中XML节点如何进行修改的指令。实际上应该将整个过程看作简单的XML处理,因为语法在很靠后的地方才涉及。
继承视图的arch字段中最有份量的指令是field元素,带有必填属性name和position。因为每个字段在表单中仅能出现一次,name已唯一标识了字段。通过position属性,我们可以在field字段内放置任意内容,在我们所命名的字段之前,之中或之后均可。默认值为inside,但为保持代码的易读性,应当指定所需的位置。记住这里我们讨论的不是语法,这是有关XML树中相对于我们所命名字段的位置。稍后是如何渲染的完全是另一个话题。
第2步演示了一种不同的方法。xpath元素选取与expr属性中指定的XPath表达式所匹配的第一个元素。这里,position属性告知处理器在何处安放xpath元素中的内容。
📝注 :如果希望根据CSS类创建XPath表达式,Odoo提供了一个特殊的函数hasclass。 例如,如果希望选取CSS类名为test_class 的
元素,表达式为expr=”//div[hasclass(‘test_class’)]”。
第3步展示了如何更改一个元素的位置。这一选项在版本 12中引入并很少被使用。本例中,通过position=move选项将phone字段移到了email字段之后。
XPath看起来好像很可怕,但对于选择需要操作的节点是一种非常有效的工具。花点时间在仔细阅读一些简单的表达式是非常值得的。你可能会撞上context node(上下文节点)一词,一些表达式与其相关。在Odoo的视图继承体系中,它总是所要继承视图的根元素。
对于在继承视图的arch字段中的其它元素,处理器查找带有相同节点名和匹配属性(排除掉position属性,因为它是指令的一部分)的第一个元素。仅在这个组合很可能不是唯一的时候使用它,比如group元素与name属性进行的组合。
📝重要贴士: 注意你可以在arch元素中加任意所需数量的指令元素。我们在继承视图中仅使用了一个,因为当前我们并不需要进行其它修改。
扩展知识…
position属性有另外两个值可以使用:replace和attributes。使用replace会让所选元素被指令元素的内容所替换。如果没有任何内容的话所选元素会被删除。在前面的列表或表单视图中会让email字段被删除:
< field name ="email" position ="replace" />
⚠️警告
删除字段会导致破坏其它继承视图以及一些不想要的负面效果,因此要尽量避免。如果实在需要删除字段,在运行顺序靠后的视图中使用(参见下一节视图继承中的运行顺序 ,获取更多相关知识。)
attributes与前例有着非常不同的语法。处理器预设该元素是name 属性、名为attribute的元素。然后用这些元素设置所选元素的属性。如果要遵照前面的警告,应当对email设置invisible属性为1:
< field name ="email" position ="attributes" >
< attribute name ="invisible" > 1 < /attribute >
< /field >
attribute节点可包含add和remove属性,其中又包含加入空格分隔列表的待添加或待删除值。这对于 class 属性非常有用,可以通过如下代码添加一个class(而非覆盖整个属性):
< field name ="email" position ="attributes" >
< attribute name ="class" add ="oe_inline" />
< /field >
这段代码对email字段添加一个oe_inline类。如果字段已有类属性,Odoo会使用separator属性的值拼接该值。
视图继承中的运行顺序
因为我们现在只有一个父视图和一个继承视图,不会碰到视图重载冲突的问题。在安装了几个模块之后,会看到大量的对partner表单的重载。如果它们在视图中修改的是不同的内容还没有问题,但总有需要了解重载运行机制来避免冲突的时候。
视图的直接后代以priority字段的升序顺序运行,因此priority低的视图先运行。继承的每一步作用于第一步的结果,因此如果priority为3的视图修改了一个字段,而另一个priority为5的视图删除了该字段,是不会有问题的。可如果优先级调过来的话就会出问题了。
我们还可以继承本身就是继承视图的视图。这时第二级继承视图应用于它所继承自的结果。因引,如果有4个视图A、B、C 和 D,A 是独立的表单,B和C继承A,D 继承B,执行顺序为A, B, D, C。使用这种方法无需依赖优先级即可强制执行顺序,通常这样更为安全。如果继承视图添加了一个字段并需要对该字段应用修改,对继承视图而不是那个独立视图进行继承。
📝重要信息 :这种类型的视图总可以在原有视图的完整XML树上运行,并且会先应用前一个继承视图的修改。
其它内容
以下各点提供了有关调整视图继承行为的一些高级套路:
对于继承视图,有一个非常有用但不太被人了解的字段groups_id。该字段仅在请求父级视图的用户为所指定组的成员时才进行继承。这在调整不同访问权限的用户界面时会节省大量的时间,因为通过继承,我们可以使用比通过表单元素中的groups属性来基于组成员状况显示或不显示元素复杂得多的操作,
例如我们可以在用户是某个组的成员时删除一些元素(这是groups属性的反向操作)。还可以执行更为复杂的操作,比如根据组成员关系添加属性,试想一些简单的操作,比如对某些组让字段只读,或像对不同组使用不同微件等更有意思的概念。
本节中所讲的有将原视图的mode字段设置为primary,而继承视图默认具有模式继承。我们在稍后会有学习一个继承视图的mode设置为primary的情况,规则会稍有不同。
定义文档样式表单
本节中,我们学习一些设计指南来呈现统一的用户体验。
如何实现…
在表单开头添加header元素:
< header >
< button type ="object" name ="open_commercial_entity" string ="Open commercial partner" class ="btn-primary" />
< /header >
为内容添加一个sheet元素:
放入stat按钮,用于显示发票总额并重定向到发票:
< div class ="oe_button_box" name ="button_box" >
< button type ="object" class ="oe_stat_button" icon ="fa-pencil-square-o" name ="action_view_partner_invoices" >
< div class ="o_form_field_o_stat_info" >
< span class ="o_stat_value" >
< field name ="total_invoiced" />
< /span >
< /div >
< /button >
< /div >
添加主要字段:
< div class ="oe_left oe_title" >
< label for ="name" />
< h1 > < field name ="name" /> < /h1 >
< /div >
添加内容,如果有很字段的话可以使用notebook:
< group >
< field name ="category_id" widget ="many2many_tags" />
< field name ="email" />
< field name ="mobile" />
< /group >
在sheet之后,添加chatter微件(如若可用):
< /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类来让按钮在视觉上更突出(在写本书时颜色为紫色),这对于引导用户在当前执行最符合逻辑的动作会是一种很好的方式。试着将所有高亮的按钮放在非高亮按钮的左侧并隐藏与当前状态(若有)中无关的按钮。如果该模型有状态,在头部中使用statusbar微件来进行显示。它会在头部居右显示。
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类和Chattert微件来在表单视图的底部获取默认的留言区。这时需要使用mail.thread mixin。我们将在第二十三章 在Odoo中管理Email 中进行详细学习。
📝重要提示 :即便你不喜欢这种布局,也请使用上述的元素和类名,然后使用CSS和JavaScript来进行所要做的调整。这会让你的用户界面与已有的插件相兼容并会与核心插件集成地更好。
其它内容
访问https://fontawesome.com/v4.7.0/icons/进一步了解Font Awesome
参见第二十三章 在Odoo中管理Email 学习有关mail.thread mixin更进一步的知识
使用attrs的动态表单元素
至此,我们只学习了根据用户组来修改表单(元素上的groups属性和继承视图中的groups_id字段)。本节我们将展示如何根据其它字段的值来修改表单视图。
如何实现…
在表单元素上定义一个名为attrs的属性:
< field name ="parent_id" attrs ="{
'invisible' : [('is_company', '=', True)],
'required' : [('is_company', '=', False)],
}" />
确保你所引用的所有字段都在表单中可用:
< field name ="is_company" />
如果用户是公司的话会让parent_id字段不可见,而在非公司时又为必填字段。
运行原理…
attrs属性包含有invisible、required和readonly键的字段(这些都是可选的)。值为所引用表单中存在字段(也仅有这些,因此没有点号路径)的作用域,整个字典根据客户端Python规则运行,在本章中向表单视图添加内容和微件 一节已有表述。比如你可以在右侧运算项中访问上下文。
扩展知识….
虽然这一机制对于标量字段非常明了,对于如何处理one2many和many2many字段则不太清晰。事实上在标准的Odoo中,对于这些字段我们在attrs属性中做不了什么。但如果你需要查看字段是否为空的话,可以在右侧运算项中使用[[6, False, []]]。
定义内嵌视图
在表单中显示one2many或many2many字段时,如未使用特殊微件则对其如何渲染不太可控。同时对于many2one字段,有时要求能够影响所关联记录的打开方式。本节中,我们将学习如何为这些字段定义私有视图。
如何实现…
像平常一样定义字段,但不关闭标签:
在标签中编写视图定义:
< tree >
< field name ="name" />
< field name ="email" />
< field name ="phone" />
< /tree >
< form >
< group >
< field name ="name" />
< field name ="function" />
< /group >
< /form >
关闭标签:
运行原理…
在Odoo加载表单视图时,首先会查看关联类型字段中是否有嵌套视图,前面已有指出。这些嵌套视图可以像我们前面所定义视图有完全相同的元素。仅在Odoo没有找到某些类型的嵌套视图时,它才使用这种类型的默认模型视图。
扩展知识….
虽然嵌套视图看上去是一个很棒的功能,但它让继承视图变得过于复杂。例如,在涉及到嵌套视图时,字段名不能保证唯一性,通常会需要使用一些更复杂的XPath来在嵌套视图中选择元素。
因此,通常应该更好地定义独立视图并使用form_view_ref和tree_view_ref键,这在本章在指定视图中打开动作 一节中进行了讲解。
在表单视图边栏显示附件(企业版)
在一些应用中,比如发票,需要根据文档填写数据。为让数据填写过程更为轻松,在Odoo 12中添加了一种新功能来在表单视图的边栏显示文档。
本节中,我们将学习如何背靠背地显示表单视图和文档:
图9.3 – 层叠附件和表单视图
📝重要信息 :这一功能仅用于大显示器(>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 >
更新模块以应用修改。需要通过记录聊天区上传一个PDF或图片。上传时,Odoo会在侧边显示该附件。
运行原理…
这一功能仅在模型继承了mail.thread模型时才起作用。在表单视图边栏显示文件档,需要在chatter元素前添加一个带有o_attachment_preview类的空<div>。这样就好了,附加在chatter中的文档就会在表单视图的边栏显示。
默认,pdf和图片文档会按照日期升序显示。可通过添加选项来改变这一行为,选项如下:
type:需要传递一个希望允许的文档类型列表。仅能使用两个值:pdf和image,例如,如果仅希望显示pdf类型的文件,可传递{‘type’: [‘pdf’]}。
order:可用的值有asc和desc。让我们可以使用文档创建日期升序或降序来显示文档。
扩展知识….
大多数情况下,你会希望在记录初始状态的边栏显示文档。如果想要根据域来隐藏附件预览,可以对<div>标签使用attrs来隐藏预览。
看如下示例,它会在state字段不为draft时隐藏PDF预览:
< div class ="o_attachment_preview"
attrs ="{ 'invisible' : [ ( 'state' , '!=' , 'draft' ) ] />
这就是在不需要时隐藏附件的方式。通常这个功能用于通过PDF填充数据,仅在草稿模式进行激活。
定义看板视图
到此,我们向大家展示了一系列可以打开显示表单的记录。虽然这些列表对于展示大量信息非常有效,但因缺乏设计感而稍显无趣。本节中,我们会来学习看板视图,可让我们以更美观的方式展示记录列表。
如何实现…
定义kanban类型的视图:
< record id ="view_all_customers_kanban" model ="ir.ui.view" >
< field name ="name" > All Customers < /field >
< field name ="model" > res . partner < /field >
< field name ="arch" type ="xml" >
< kanban >
列出在视图中使用的字段:
< field name ="name" />
< field name ="supplier" />
< field name ="customer" />
实现设计:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
< templates >
< t t -name ="kanban-box" >
< div class ="oe_kanban_card" >
< a type ="open" >
< strong >
< field name ="name" />
< /strong >
< /a >
< t t -if ="record.supplier_rank.raw_value or record.customer_rank.raw_value" >
is
< t t -if ="record.customer_rank.raw_value" >
a customer
< /t >
< t t -if ="record.supplier_rank.raw_value and record.customer_rank.raw_value" >
and
< /t >
< t t -if ="record.supplier_rank.raw_value" >
a supplier
< /t >
< /t >
< /div >
< /t >
< /templates >
关闭所有标签:
< /kanban >
< /field >
< /record >
将该视图添加到动作当中。这部分将留给读者作为练习。可以在GitHub的示例文件中找到完整示例:https://github.com/alanhou/odoo14-cookbook/tree/main/Chapter09/15_kanban_view
运行原理…
在第2步中我们需要给出一个字段列表来在稍后访问。templates元素的内容必须是t-name属性设置为kanban-box的单个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添加到插件的依赖列表中。
如何实现…
为任务定义一个看板视图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
< record id ="kanban_tasks" model ="ir.ui.view" >
< field name ="name" > project . task . kanban < /field >
< field name ="model" > project . task < /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_card oe_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视图:
< 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_assign" 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的 BI(商业智能)视图。它们是用于展示数据的只读视图。
准备工作
这里我们仍将使用project模块。可以配置graph和pivot视图来获取不同的统计数据。本例中我们仅查看已分配任务的用户。我们将生成图表和透视表视图来查看每个用户的任务。同时,终端用户可以通过修改视图选项来生成他们所选的统计数据。
如何实现…
使用柱状图定义图表视图:
< 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 >
定义透视表视图:
< 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根元素声明。graph元素中的type属性决定了图表视图的初始模式。可用的值有bar, line和chart,默认值是bar。图表视图交互性很强,因此用户可以在不同模式间切换并且还可以添加和删除字段。如要使用 type=”bar”,还可以使用stacked=”1″来在分组时显示叠放的柱状图。
field元素告诉Odoo在哪个轴上显示什么。对于所有的图表模式,至少需要一个带有row类型的字段以及一个带有measure类型的字段来显示有用的信息。row类型的字段决定分组,而那些measure类型的字段表示要显示的值。折线图每种类型仅支持一个字段,而图表和柱状图可很好地处理同一度量方式的两个分组字段。
透视表视图有其自己的根元素pivot。透视表视图支持定义的任意数量的group和measure字段。切换到不支持所定义的group和measure数量的模式并不会导致崩溃,只是有些字段会被忽略,呈现的结果会不够丰满。
扩展知识….
对于所有图表类型,Datetime字段都不太好分组,因为很少会碰到相同的字段值。因此,如果有row类型的Datetime字段时,同时指定带有如下值的interval属性:day, week, month, quarter或year。这会以给定的间隔进行分组。
透视表还支持对列的分组。对相应的字段使用col类型。
📝重要信息:类似于排序,分组也重度依赖于PostgreSQL。因此这里应用规则的字段必须在数据库的当前表中存在方能使用。
定义搜集所需所有数据的数据库视图,并对该视图定义模型来让所有需要的字段可用是常见的做法。
根据视图和分组的复杂度,构建图表可能会是一个开销很大的操作。这时考虑将auto_search设置为False,这样用户可以先调整所有参数,然后才触发一次搜索。
透视表视图还支持按列分组。对希望进行分组的字段使用col类型。
定义流存(cohort)视图(企业版)
对于记录的存留分析,在 Odoo 12中新增了cohort视图。流存视图用于发现指定时间段中记录的生命周期。通过流存视图可以看到指定时间任意对象的流失率和存留率。
准备工作
流存视图是Odoo企业版中的功能,因此在社区版中无法使用。如果你使用的是企业版,需要在模块的声明文件中添加web_cohort。本例中我们将创建视图来查看任务的流存分析。
如何实现…
按照如下步骤来为project.task模型添加cohort视图:
定义一个cohort视图:
< 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:可以使用两种模式: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。我们会在仪表盘视图中使用这些视图的 ID。
如何实现…
定义一个dashboard视图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
< record id ="view_project_tasks_dashboard" model ="ir.ui.view" >
< field name ="name" > project task dashboard < /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" 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 引用和视图类型的<view>标签。
我们通过<aggregate>标签展示了不同的KPI,包括所有任务,进行中任务和已完成任务。该标签将展示搜索视图当前域中记录的总计结果。在<aggregate>标签中,可以使用可选的domain属性来显示指定记录集合的总计。默认aggregate函数显示记录的条数,但可通过使用group_operator属性来指定一个SQL总计函数,如avg或max。
有时无法使用<aggregate>展示KPI,需要一些其它的计算。借助于<formula>,我们可以为任意KPI定义公式。本例中,我们展示了所有任务的进度并使用了可选属性widget来将该值展示为百分比。
扩展知识….
另一个有用的元素是<widget>标签。通过它可以在所选的用户界面中显示数据。在第十五章 网页客户端开发 中,我们将学习如何创建自定义微件。
定义甘特视图(企业版)
Odoo 13中新增了带有新选择的甘特视图。甘特视图对于查看整理进度及规划业务流程非常有用。本节中我们新建一个视图并学习其选项。
准备工作
甘特视图属于企业版,所以在社区版中无法使用。如果使用的是企业版,需要在模块的声明文件中添加对web_gantt的依赖。
本例中,我们将继续使用前面小节中的my_project模块。我们将对项目任务新建一个甘特视图。
译者注 :如果要在社区版中使用,可以使用OCA的web仓库中具有相似功能的模块web_timeline。该模块引入了一种视图,类似于甘特图的展示。
如何实现…
对任务模型定义甘特视图如下:
< record id ="view_project_tasks_gantt" model ="ir.ui.view" >
< field name ="name" > project task gantt < /field >
< field name ="model" > project . task < /field >
< field name ="arch" type ="xml" >
< gantt date_start ="date_assign" date_stop ="date_end" string ="Tasks" default_group_by ="project_id"
color ="project_id" progress ="sequence" >
< field name ="name" />
< field name ="stage_id" />
< /gantt >
< /field >
< /record >
使用该视图添加菜单和动作。这部分留作读者练习。
安装升级模块应用修改,在更新后,就可以看到项目任务的甘特视图了。
运行原理…
通过甘特视图,可以显示在同屏中显示整体规划。本例中,我们按项目分组对任务创建了甘特视图。正常我们需要两个属性来创建甘特视图:date_start和date_stop,还有一些可扩展甘特视图功能的功能。我们来看下这些选项:
date_start:定义甘特项的开始日期。必须为date或datetime字段。
date_stop:定义甘特项的结束日期。必须为date或datetime字段。
default_group_by:如要想根据字段对gantt项进行分组使用些属性。
color: 这个属性用于决定甘特项的颜色。
progress: 这个属性用于表明甘特项的进度。
decoration-* : 装饰属性用于根据条件显示甘特项的颜色。使用方式类似:decoration-danger=”state == ‘lost'”。其它的值有decoration-success, decoration-info, decoration-warning和 decoration-secondary。
scales: 如果只想使用甘特视图的某几个刻度可以使用scales属性。 例如,如果只想显示天和周的刻度,可以使用scales=”day,week”。
默认甘特视图项可以重置大小、可拖拽,但如果希望禁用该功能,可以使用edit=”0″属性。
扩展知识….
悬浮甘特视图某项时,会出现该项的名称和日期。如果想要自定义弹出内容,可以在甘特视图中像下面这样定义一个QWeb 模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
< record id ="view_project_tasks_gantt" model ="ir.ui.view" >
< field name ="name" > project task gantt < /field >
< field name ="model" > project . task < /field >
< field name ="arch" type ="xml" >
< gantt date_start ="date_assign" date_stop ="date_end" string ="Tasks" >
< field name ="name" />
< field name ="stage_id" />
< templates >
< div t -name ="gantt-popover" >
< ul class ="pl-1 mb-0 list-unstyled" >
< li >
< strong > Name : < /strong >
< t t -esc ="name" />
< /li >
< li >
< strong > Stage : < /strong >
< t t -esc ="stage_id[1]" />
< /li >
< /ul >
< /div >
< /templates >
< /gantt >
< /field >
< /record >
注意需要通过<field>标签在模板中增加希望添加的字段。
译者注:为便于演示效果,上例使用了date_stop=”date_deadline”和color=”stage_id”
定义活动视图
活动是Odoo应用的重要组成部分。用于对不同的业务对象调度待办动作。activity视图有助于我们查看状态并规划模板中所有活动。
准备工作
本例中,我们继续使用前面小节中的my_project模块。我们将对项目的任务新建活动视图。
如何实现…
对任务模型定义活动视图如下:
< record id ="view_project_tasks_activity" model ="ir.ui.view" >
< field name ="name" > project task activity < /field >
< field name ="model" > project . task < /field >
< field name ="arch" type ="xml" >
< activity string ="Tasks" >
< templates >
< div t -name ="activity-box" >
< div >
< field name ="name" display ="full" />
< field name ="project_id" muted ="1" display ="full" />
< /div >
< /div >
< /templates >
< /activity >
< /field >
< /record >
使用该视图添加菜单和动作。这部分留作读者练习。
运行原理…
活动视图走极简风,大部分内容都自动管理。只有用于自定义第一栏的选项。要在第一列中显示数据,需要创建带有activity-box名称的QWeb模板,就是这么简单。Odoo会处理其余的工作。
活动视图会在第一列显示模板,其它列按活动类型显示计划的活动。
定义地图视图(企业版)
Odoo 13新增了一个名为map的视图。从名字可知,用于显示带有标注的地图。这对于线下服务非常有用。
准备工作
本例中,我们将继续使用前面小节中的my_project模块。我们会对任务的客户新建地图视图。地图视图属于Odoo企业版,所以在社区版中无法使用。如果使用的是企业版,需要在模块的声明文件中添加web_map依赖。
Odoo使用https://www.mapbox.com/的API来在视图中显示地图。要在Odoo中查看地图,我们需要通过mapbox生成访问令牌。确保已生成访问令牌并在Odoo的配置中进行了设置。
如何实现…
对任务模型定义地图视图如下:
< record id ="view_project_tasks_map" model ="ir.ui.view" >
< field name ="name" > project task map < /field >
< field name ="model" > project . task < /field >
< field name ="arch" type ="xml" >
< map res_partner ="partner_id" >
< field name ="name" string ="Title " />
< field name ="partner_id" string ="Customer " />
< /map >
< /field >
< /record >
使用该视图添加菜单和动作。这部分留作读者练习。
译者注: Odoo 中已移除<marker-popup>标签,原文表述不太准确。参见https://github.com/odoo/odoo/pull/34655。
运行原理…
创建地图视图非常简单:只需一个引用res.partner模型的many2one字段。res.partner模型有一个address字段,由地图视图用于显示该地址的标注。我们需要使用res_partner属性在地图视图中对应出地址。本例中,我们使用了partner_id字段,因为在partner_id字段中设置了客户记录。
此外,可以自定义用户点击标注时在标注弹窗中显示的字段。要显示标注弹窗中的数据,需要使用<marker-popup>标签并在其中放置显示字段。
Odoo 14开发者指南第八章 高级服务端开发技巧 Odoo 14开发者指南第十章 权限安全