QWeb 是 Odoo 使用的模板引擎,它基于 XML 来生成 HTML 片断和页面。通过 QWeb可生成内容丰富的看板(Kankan)视图、报表和 CMS 网页。本文中我们将学习QWeb 语法以及如何使用 QWeb 来创建我们自己的看板视图和自定义报表。
本文主要内容有:
- 看板是什么?
- 设计看板视图
- QWeb 模板语言
- 看板视图的继承
- 添加自定义 CSS 和 JavaScript
开发准备
我们将继续使用第十章 Odoo 12开发之后台视图 – 设计用户界面完成的library_checkout插件模块。相应代码请见 GitHub仓库。本章完成后的代码也请参见GitHub仓库。
了解看板
Kanban 是一个日语词汇,字面意思榜单,与精益制造和准时化生产相关联,由丰田工业工程师大野耐一(Taiichi Ohno)引入。最近看板的概念应用于更多领域,并且随着敏捷方法的施行在软件工业内流行起来。
看板让我们能够可视化工作队列,它以列来进行组织,每列代表工作进程的一个阶段。工作项以放在看板对应列的卡片来表示。新的工作项从最左边的列开始,并开始向右移动直至最右边列,代表工作完成。
看板的简单化或视觉效果让其对简单的业务流程有着优异的支持。一个基本的看板示例包含三列,如下图所示:待办、在办和完成。当然它可以扩展为你需要的其它指定流程:
对许多业务用例,看板都是管理相应流程的更有效方式,与 Odoo 11之前的更重的工作流引擎形成鲜明对比。Odoo 在支持经典的列表和表单视图的同时还支持看板视图,这易于我们实施这种类型的视图。下面就让我们一起来学习如何使用看板视图。
看板视图
现在我们要为借阅模型添加一个看板视图。每个借阅是一个卡片,看板将会被组织成阶段列。在前面的文章中,我们已经添加了stage_id阶段字段。
此前在表单视图我们大部分时候使用 Odoo 独有的 XML 元素,比如<field>和<group>,有时也会使用 HTML 元素,如<h1>或<div>,但用得较少。在看板视图中则恰恰相反,展示模板基于 HTML,仅支持两个 Odoo 独有的元素:<field>和<button>。
最终呈现在网页客户端中的内容是由 QWeb 模板动态生成的。QWeb 引擎处理特殊的 XML 标签和属性来进行生成。这样可以很好地控制如何渲染内容,但也让视图设计更为复杂。看板视图设计灵活性很强,我们将尽力以直接易懂地方式介绍快速创建看板视图的知识。查看与所需相似的看板视图来获取创意然后创建自己的看板是一种不错的方法。
我们将学习两种使用看板视图的方式。一种是卡片列表,它用于联系人、产品、雇员通讯录或应用等。联系人看板视图长这样:
这两种布局的最大区别是卡片按列的组织方式。这通过 Group By 功能实现,与列表视图中相似。通常分组是通过stage字段实现。看板视图的一个非常有用的功能是可以在列之间拖放卡片,自动分配分组视图字段的对应值。从两个示例中的卡片我们可以看到一些分别。其实它们的设计非常灵活,设计看板卡片不只有一种方式。这两个示例为我们提供设计的一些基础。
联系人卡片基本组成有左侧的图像,主区域的加粗标题和紧随其后的一系列值。CRM 管道卡片结构更为复杂些。卡片主区域也有一个标题以及相关信息紧随其后,还有 footer 区。在该区域中,可看到左侧有一个优先级组件,后面带有一个活动指示,在右侧是一个负责用户的头像。上图中看不到,在鼠标悬停在右上角时还会有一个选项菜单。这个菜单让我们可以修改卡片的颜色提示等。
我们将使用这种更复杂的结构来作为借阅看板卡片的参照。
设计看板视图
我们将改进一直以来开发的library_checkout模型,为图书借阅添加看板视图。为此我们使用一个新文件library_checkout/views/checkout_kanban_view.xml。需要在__manifest__.py文件的 data 键最下方添加这个文件。在library_checkout/views/library_menu.xml文件中,可以看到借阅菜单项使用的窗口操作。需要对其修改来启用本文中添加的视图类型:
这里我们修改了菜单操作来在view_mode列表的最前面添加了kanban,来让它成为默认的视图模式。然后我们来添加kanban视图记录。与其它视图基本相同,除了 arch 字段内,最外层 XML元素为<kanban>。下一步创建实际使用的 XML 文件library_checkout/views/checkout_kanban_view.xml来放置这个惊艳的看板视图:
在使用看板视图前,我们需要为图书借阅模型添加几个字段。
优先级、看板状态和颜色
除阶段外,看板中还有一些常用和有用的字段:
- priority让用户组织他们的工作项,标记什么应优先处理
- kanban_state标记是否应移向下一阶段或因某种原因原地不动。在模型定义层中两者都是选择项字段。在视图层,对它们有特别的组件用于表单和看板视图。
- color用于存储看板卡片显示的颜色,并可通过看板视图中的颜色拾取器菜单设置
编辑library_checkout/models/library_checkout.py文件来在我们的模型中添加这些字段:
我们还应该在表单视图中添加这些字段,使用各自的特别组件。kanban_state字段就加在<div class=”oe_title”>之前并在按钮框之后:<field name=”kanban_state” widget=”state_selection” />。priority应添加在name 字段之前,包裹在<h1>元素中:<field name=”priority” widget=”priority” />。color字段一般不出现在表单视图中。
既然借阅模型已有我们所需使用的所有字段,我们可以来写看板视图了。
看板卡片元素
看板视图框架包含一个<kanban>外层元素和以下基础结构:
注意在元素中使用了default_group_by=”stage_id”属性,我们用它来让看板默认以 stage 分组,这也是看板通常的分组方式。在简单卡片列表的看板中,如联系人,我们不需要添加该属性,只需使用<kanban>标签即可。<kanban>元素支持以下属性:
- default_group_by设置默认列分组使用的字段
- default_order设置看板项默认使用的排序
- quick_create=”false”禁用了每列顶部的快速创建选项(大的加号符号),快速创建只需提供标题描述即可创建新项。false是 JavaScript 的语法,必须是小写字母。
- class为渲染看板视图的根元素添加 CSS 类。相关类是_kanban_small_column,让列比默认的更加紧湊。其它类可由我们模块的 CSS 文件来进行提供。
- group_create, group_edit, group_delete和quick_create_view可设置为 false 来禁用看板列上对应的操作。如group_create=”false”删除右侧添加新列的按钮。
- on_create用于创建用户点击左上角 Create 按钮时弹出的自定义简单表单视图窗口。应为相应的表单视图添加<module>.<xml_id>值。
然后我们的模板中使用了一组字段。确切地说,只有在 QWeb 表达式中明确使用的字段才需要在这里声明,用以保证从服务端抓取它们的数据。QWeb引擎在处理模板前,仅会在视图中查找 <field name=”…”>来从模型中获取数据。QWeb的属性通常使用不会被检测到的record.field引用方式。正因为如此,需在<templates>之前包含这些字段来让模板处理时有相应字段值可以使用。
ℹ️Odoo 11中的修改
引入了进度条组件。使用的时候在看板列的上方会出现一个颜色条,来提供该列各项的状态数据。在本文前面CRM Pipeline的示例图中可以查看。
<progressbar>有如下属性:
- field是对列中各项进行颜色分组的字段名
- colors是一个字典,将分组字段值与以下三种颜色分别进行映射:danger (红色), warning (黄色)或success (绿色)。
- sum_field是一个可选项,用于选取整列汇总的字段名。如未设置,会使用各项的计数值。
然后我们的<templates>元素包含一个或多个QWeb模板来生成要使用的 HTML 片断。必须要有一个名为kanban-box的模板,它渲染看板卡片。还可以添加其它模板,通常用于定义主模板中复用到的 HTML 片断。这些模板使用标准的 HTML 和 QWeb 模板语言。QWeb提供了一些特殊指令,用于处理动态生成最终展示的 HTML。
ℹ️Odoo 12中的修改
Odoo 现在使用 Twitter Bootstrap 4,此前版本中使用Bootstrap 3。这些样式在渲染 HTML 的地方通常都可使用,有关Bootstrap更多知识请见官方网站。
下面就来详细了解看板视图中所使用的QWeb模板设计。
看板卡片布局
看板卡片主内容区域在kanban-box模板内定义。这个内容区也可以有一个 footer 底部子容器。卡片右上角还可以添加按钮,点击后打开操作菜单的功能。对于footer区域,应在看板盒子模型底部使用<div>并添加oe_kanban_bottom CSS 类。还可以通过oe_kanban_bottom_left和oe_kanban_bottom_right CSS 类进一步分割为左、右 footer 区。此外,可通过Bootstrap的pull-left和pull-right类在卡片的任意位置(包括oe_kanban_bottom底部区域)添加向左或向右对齐元素。
以下是对看板卡片中QWeb模板的第一次迭代:
这就是看板卡片的整体结构。你可能注意到了在顶部<div>元素中使用了color字段来动态设置卡片颜色。在后面的部分中我们会讲解t-attf QWeb指令的细节。现在来为主内容区域添加内容:
这个模板中大部分都是常规 HTML,但也有渲染字段值的<field>元素和在常规表单视图按钮中使用的 type 属性,此处用在锚文本标签中。
在左部 footer 中插入优先级组件:
这里像我们在表单视图中做的那样添加了priority字段。还添加了一个计划活动的字段,使用kanban_activity特殊组件来显示即将开始活动的指示。
在右部footer中,放入看板状态组件和请求借阅的会员头像:
补充:原文件使用的 CSS 类oe_kanban_footer,oe_kanban_footer_left和oe_kanban_footer_right经测试不会进行左右对齐,参照 CRM 进行了如上修改
看板状态通过<field>元素和kanban_state_selection组件来进行添加。用户头像使用 HTML <img>标签插入。图像内容使用QWeb t-att-命令动态生成,后面会详细讲解。这里使用了kanban_image()帮助函数来获取src属性的值。kanban_image() Javascript函数从 Odoo 模型中获取表单并在网页中渲染。有以下属性:
- 获取图像的模型
- 包含图像的字段
- 获取的记录 ID
为看板卡片添加选项菜单
看板卡片可在右上角带有一个选项菜单。通常的操作有编辑或删除记录,但也可以为其添加和按钮调用的同样操作。还有一个设置卡片颜色的组件。以下是oe_kanban_content顶部添加的选项菜单的基础代码:
下拉菜单基本上是由带有<a>标签的<li> HTML 列表元素组成。Edit 和 Delete 这类选项需要满足指定条件下才会出现。这通过QWeb的t-if命令来实现。本文后续会详细讲解QWeb的命令。widget全局变量表示一个KanbanRecord() JS 对象,负责渲染当前看板卡片。有两个非常有用的属性:widget.editable和widget.deletable,让我们可以检查相应的操作是否可用。
可以看到如何根据记录字段值来显示或隐藏选项,Set as Done仅在未设置is_done 字段时才会显示。最后一个选项添加颜色拾取器组件来使用 color 数据字段选择或修改卡片背景色。因此,除<button>元素外,<a>也可用于运行Odoo 操作。
看板视图中的操作
在QWeb模板中,用于超链的<a>标签可带有一个 type 属性。它设置链接执行的操作类型,这样链接和常规表单中的按钮可进行同样的操作。和表单视图一样,操作类型可以是action或object,并应带有一个 name 属性来标识所要执行的具体操作。此外,还有以下操作类型可以使用:
- open打开相应的表单视图
- edit在编辑模式下直接打开相应的表单视图
- delete删除记录并从看板视图中删除该项
QWeb 模板语言
QWeb会查找模板中的特殊指令并替换掉动态生成的 HTML。这些指令是 XML 元素属性,可心用在<div>, <span>或<field>等有效标签或元素中。有时我们要使用QWeb指令但不希望放在模板的 XML 元素中。对这种情况,可以使用能带有 QWeb 指令(如t-if或t-foreach)的特殊元素<t>,该元组不会在最终产生的XML/HTML有任何输出。
QWeb指令常使用运算的表达式来根据当前记录值生成不同的结果。有两种不同的QWeb实现:客户端JavaScript和服务端Python。报表和网页使用服务端QWeb的 Python 实现。看板视图使用客户端JavaScript实现。也就是说看板视图中的QWeb表达式应使用JavaScript语法书写,而不是 Python。
在显示看板视图时,内部的步骤大致如下:
- 获取模板的XML进行渲染
- 调用服务端read()方法来获取模板中所涉及的字段数据
- 定位kanban-boxs模板并使用QWeb解析它来输出最终的HTML片断
- 在浏览器显示(DOM)中注入 HTML
以上在技术上并不精确,仅是用于理解看板视图中如何运作的脑图。下面我们将学习QWeb表达式运行并探讨可用的QWeb指令,通过示例改进借阅看板卡片。
QWeb JavaScript 运行上下文
许多QWeb指令使用表达式的运行来生成结果。在看板视图这类客户端的应用中,表达式应使用JavaScript书写。表达式在带有几个有用变量的上下文中进行运行。可用record 对象带有从服务端请求的字段来表示当前记录。字段值可通过raw_value或value属性来获取:
- raw_value是由服务端read()方法返回的值,因此在条件表达式中更适用
- value根据用户设置来格式化,用于在用户界面中的显示。常用于date/datetime, float/monetary和关联字段。
QWeb运行上下文还可在JavaScript网页客户端中引用。要擅用这些需要对网页客户端结构有很好的理解,但这里我们不会进行深入介绍。要进行引用 ,QWeb表达式运行中有以下标识符可以使用:
- widget是对当前KanbanRecord() 组件对象的引用 ,用于在看板卡片中渲染当前记录。它会暴露一些帮助函数供我们使用。
- record是widget.record的简写形式,使用点号标记来提供对可用字段的访问。
- read_only_mode表示当前视图是否为读模式(而非编辑模式)。它是widget.view.options.read_only_mode的简写形式。
- instance是对全部网页客户端实例的一个引用 。
值得一提的是有些字符是不能在表达式中使用的,比如小于号(<) 。这是因为在 XML 标准中,这些字符具有特殊含义,不应在 XML 内容中使用。反向的>=是一个有效替代方式,但通常是使用以下替代符号来进行不等式运算:
- lt是小于
- lte是小于等于
- gt是大于
- gte是大于等于
ℹ️前述的比较符号仅用于 Odoo,是引入来解决 XML 格式中的限制的。它们不是 XML 标准的一部分。
字符串替换动态属性– t-attf
我们的看板卡片使用t-attf QWeb指令来为顶级<div>元素动态设置一个类,这样卡片可根据 color 字段值来显示颜色。为此使用了t-attf- QWeb指令。t-attf-指令使用字符串替换动态生成标签属性。这让像 URL 地址或 CSS 类名这类较大字符串中的部分内容可动态生成。
该指令查找表达式代码块进行运行并替换结果。它们通过{{和}} 或#{和}来进行分隔。代码块的内容可以是任意JavaScript表达式并使用QWeb表达式中的任意可用变量,如record和widget。本例中我们还使用了专门提供的kanban_color() JS 函数,用于映射索引值到类颜色名。
作为一个更复杂的示例,我们使用这个指令来动态生成用户的颜色,红色字体表示优先级很高。下面替换看板卡片中的相应代码:
这将会根据借阅优先级的值生成class=”oe_kanban_text_red”或class=”oe_kanban_text_black”。请注意看板视图中是有oe_kanban_text_red这个 CSS 类的,但oe_kanban_text_black仅用于演示,实际并不存在。
ℹ️注意JavaScript表达式中使用的lt符号,是<的转义表达式,并不能在XML中使用。
表达式动态属性 – t-att
t-att- QWeb指令通过运行表达式动态生成属性值。我们的看板卡片中使用它来为<img>标签动态生成属性,title 属性使用以下表达式动态渲染:
.value字段返回在屏幕上显示的值。对于many-to-one字段,这通常是相关记录的 name 值。对于用户则是用户名。运行之后在鼠标悬停于图像上时会显示相应的用户名。
在表达式运行的结果值为假时,就不会渲染该属性。这对于特殊的 HTML 属性非常重要,比如 input 字段中的 checked,即便在没有属性值时也会有显示效果。
循环 – t-foreach
通过循环遍历来重复同一 HTML 代码块。我们可使用它来添加记录 follower 的头像。让我们先来仅渲染记录的partner ID:
t-foreach指令接收一个JavaScript表达式,运行来遍历集合。在大多数情况下,这会是 一个to-many关联字段的名称。与t-as指令一同使用来设置用于引用遍历各项的名称。下面使用的t-esc指令运行所提供的表达式,本处仅为 rec 变量名,将其渲染为已转译的安全 HTML。
在上例中,我们遍历了存储在message_partner_ids 字段中的 follower。因为在看板卡片上的空间有限,我们使用JS 的slice()函数来限定所显示的follower数量,如下所示:
rec变量存储每个遍历值,本例中为partner ID。这样我们可以将循环改写为:
比如可将其添加在右侧 footer 的用户头像旁。还包含一些帮助变量,它们的名称以t_as 中定义的变量名为前缀。本例中使用了rec,因此可用的帮助变量如下:
- rec_index是迭代索引,从0开始
- rec_size是集合中的元素数量
- rec_first在迭代的第一个元素中为真
- rec_last在迭代的最后一个元素中为真
- rec_even在索引为偶数时为真
- rec_odd在索引为奇数时为真
- rec_parity根据当前索引为odd或even
- rec_all表示进行迭代的对象
- rec_value在迭代{key:value} 字典时,存储value (rec存储键名)
例如可通过如下代码去除ID 列表最后逗号:
条件判断 – t-if
我们的看板视图在卡片选项菜单中使用了t-if指令来根据不同条件显示不同选项。t-if指令在客户端渲染看板视图时需传入在 JS 中运行的表达式。标签和其内容仅在条件运行值为true 时才会渲染。作为示例,仅在借出有值时显示图书借出数量,在request_date字段后加入如下代码:
我们使用了<t t-if=”…”>元素,这样在条件为 false 时,元素不会有任何输出。在为 true 时,仅会渲染其所包含的<li>元素来进行输出。注意条件表达式中使用gt符号来替代>以表示大于运算符。可通过t-elif和t-else来支持else if和else条件语句,使用示例如下:
Javascript表达式中,AND和OR的运算符分别为&&和 ||。但在 XML 中不支持&符号,我们可以使用 and 和 or 运算符来规避这一问题。
渲染值 – t-esc和t-raw
我们使用了<field>元素来渲染值,但也可以无需<field>标签直接显示字段值。t-esc指令运行表达式并将其渲染为转义后的 HTML 值,如下所示:
有些情况下,如果确定源数据是安全的,可以无需转义使用t-raw 来渲染原始值,如下例所示:
小贴士:出于安全考虑,应尽量避免使用t-raw。它应严格用于输出特别准备不包含用户数据的HTML 数据,或者是已明确对 HTML 特殊字符转义的用户数据。
为变量设置值 – t-set
对于更复杂的逻辑,我们可以将表达式结果存储在变量中,在模板中随后使用。这通过t-set指令来实现,它设置变量名,紧接着使用t-value指令来添加表达式计算分配的值。作为示例,以下代码将优先级较高的和前面一节一样渲染为红色,但使用red_or_black 变量来作为 CSS 类使用的变量,如下所示:
变量中也可分配 HTML内容,示例如下:
调用和复用其它模板 – t-call
QWeb模板可作为可复用的 HTML 片段插入到其它模板中。我们无需重复相同的 HTML 代码块,可以设计构成部分来组成更为复杂的用户界面视图,可复用的模板在<templates>标签中定义,通过顶级元素中 kanban-box 以外的 t-name值进行标识。这些模板可通过t-call来进行包含,在当前看板视图、相同模块的其它地方以及其它插件模块中均可。
follower头像列表可以通过可复用代码段来进行分离,下面通过子模板重写代码。首先应在 XML 文件中添加另一个模板,在<templates>元素内,<t t-name=”kanban-box”>节点之后,添加如下代码:
在kanban-box主模板调用它就简单明了了,将原来包含 for each 指令的<div>元素修改为如下代码:
调用其它插件模块中定义的模板,和视图类似,我们需要使用完整的module.name标识符。比如,以上代码片断可使用library_checkout.follower_avatars完整标识符来进行引用 。调用的模板和调用者运行在同一上下文中,所以调用方中的变量名在处理调用模板时同样可用。
一种更优雅的实现方式是向调用模板传递参数,这通过在 t-call 标签中设置变量来完成。这些仅在子模板上下文中运行和使用,在调用方上下文中并不存在。我们将使用这个方法来让调用方设置follower 头像的最大数,而不是在子模板中硬编码。首先,我们将原固定值3修改为一个变量 arg_max:
然后像下面这样在执行子模板调用时定义该变量:
t-call元素内的整个内容可通过0(数字零)这个魔法变量在子模板中使用。不使用参数变量,我们还可以定义代码片断并在子模板中通过<t t-raw=”0″ />使用。这对以模块化的方式创建布局、合并/嵌套 QWeb 模板尤为有用。
字典和列表动态属性
我们已经学习最重要的那些QWeb指令,但还有一部分我们也应该了解。下面简短地进行讲解。
前面我们看到t-att-NAME和t-attf-NAME样式的动态标签属性,此外还可以使用固定的t-att指令。它接收键值对字典或pair(两个元素的列表)。
使用如下映射:
将生成如下结果:
使用如下 pair:
将生成如下结果:
看板视图的继承
看板视图和报表中使用的模板可通过视图相同的常规方法来进行继承,例如,使用XPath表达式,参见第四章 Odoo 12 开发之模块继承。
常见的情况是使用<field>元素作为选择器,然后在其前或后添加其它元素。对于看板视图,同一字段可声明多次,例如在模板前和模板内分别声明。这时,选择器将匹配第一个字段元素,不会将修改我们希望修改的模板内的字段。要规避这一问题,我们需使用XPath来确保匹配的是模板内的字段,例如:
在上例中,XPath 查找<t t-name=”kanban-box”>元素内的<field name=”display_name”>元素。这条规则会排除掉<templates>版块之外的相同字段元素。对于更 复杂的XPath表达式,我们可以使用命令行工具来研究出正确的语法。你的 Linux 系统中可能已有安装了xmllint工具(sudo apt install libxml2-utils),它有一个–xpath 选项可对 XML 文件执行查询。
另一个输出更好看的选项是Debian/Ubuntu包中的libxml-xpath-perl,带有一个xpath 命令:
自定义 CSS 和 JavaScript
如前所见,看板视图大多数为 HTML 并重度使用了 CSS 类。我们介绍了标准产品中提供的一些常用 CSS 类,但要实现最佳效果,我们还可以为模块添加自己的 CSS。我们这里不会详细讲解 CSS 代码的写法,但相应地需要讲解如何为模块添加自己的 CSS (JavaScript)这些前端资源。Odoo 中后台的前端资源在assets_backend模块中声明。要在模块中添加前端资源,需要对模块进行继承。进行这一操作的 XML 文件通常放在views/ 模块子目录内。
以下是在library_checkout模块中添加一个 CSS 和 JavaScript文件的示例,对应文件为library_checkout/views/checkout_kanban_assets.xml:
和平常一样,需要在__manifest__.py描述文件中对其进引用,注意这些前端文件放在/static/src 子目录中,这不是强制要求,但是约定俗成如此。
总结
我们学习了看板和如何创建看板视图来实现这些看板。我们还介绍了QWeb模板以及使用它来设计看板卡片。QWeb同时还是 CMS 网站的渲染引擎,因此它在 Odoo 工具集中的重要性越来越高。
看板视图可通过其它视图中使用的相同XML语法来进行继承。看板的 XML 结构可能会更为复杂,我们经常需要使用XPath表达式来定义需继承的元素。
最后,高级看板视图可以使用独有的 CSS 和 JavaScript 文件。可作为模块文件来进行添加,然后应在web.assets_backend QWeb模板中添加这些文件,以在客户端页面中包含。
在下一篇文章中,我们将继续使用QWeb,但是是在服务端创建自定义报表 。