第十一章 Odoo 12开发之看板视图和用户端 QWeb

本文为最好用的免费ERP系统Odoo 12开发手册系列文章第十一篇。

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 标签和属性来进行生成。这样可以很好地控制如何渲染内容,但也让视图设计更为复杂。看板视图设计灵活性很强,我们将尽力以直接易懂地方式介绍快速创建看板视图的知识。查看与所需相似的看板视图来获取创意然后创建自己的看板是一种不错的方法。

我们将学习两种使用看板视图的方式。一种是卡片列表,它用于联系人、产品、雇员通讯录或应用等。联系人看板视图长这样:

Odoo 12联系人看板视图

但这不是真正的看板,看板应是一个组织成不同列的卡片,当然看板视图也支持这种布局。可能过 CRM 或项目应用来查看示例。访问CRM > Sales > My Pipeline可得到如下结果:

Odoo 12 CRM看板视图

这两种布局的最大区别是卡片按列的组织方式。这通过 Group By 功能实现,与列表视图中相似。通常分组是通过stage字段实现。看板视图的一个非常有用的功能是可以在列之间拖放卡片,自动分配分组视图字段的对应值。从两个示例中的卡片我们可以看到一些分别。其实它们的设计非常灵活,设计看板卡片不只有一种方式。这两个示例为我们提供设计的一些基础。

联系人卡片基本组成有左侧的图像,主区域的加粗标题和紧随其后的一系列值。CRM 管道卡片结构更为复杂些。卡片主区域也有一个标题以及相关信息紧随其后,还有 footer 区。在该区域中,可看到左侧有一个优先级组件,后面带有一个活动指示,在右侧是一个负责用户的头像。上图中看不到,在鼠标悬停在右上角时还会有一个选项菜单。这个菜单让我们可以修改卡片的颜色提示等。

我们将使用这种更复杂的结构来作为借阅看板卡片的参照。

设计看板视图

我们将改进一直以来开发的library_checkout模型,为图书借阅添加看板视图。为此我们使用一个新文件library_checkout/views/checkout_kanban_view.xml。需要在__manifest__.py文件的 data 键最下方添加这个文件。在library_checkout/views/library_menu.xml文件中,可以看到借阅菜单项使用的窗口操作。需要对其修改来启用本文中添加的视图类型:

1
2
3
4
<act_window id="action_library_checkout"
name="Checkouts"
res_model="library.checkout"
view_mode="kanban,tree,form,activity,calendar,graph,pivot" />

这里我们修改了菜单操作来在view_mode列表的最前面添加了kanban,来让它成为默认的视图模式。然后我们来添加kanban视图记录。与其它视图基本相同,除了 arch 字段内,最外层 XML元素为。下一步创建实际使用的 XML 文件library_checkout/views/checkout_kanban_view.xml来放置这个惊艳的看板视图:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<odoo>
<record id="library_checkout_kanban" model="ir.ui.view">
<field name="model">library.checkout</field>
<field name="arch" type="xml">
<kanban>

</kanban>
</field>
</record>
</odoo>

在使用看板视图前,我们需要为图书借阅模型添加几个字段。

优先级、看板状态和颜色

除阶段外,看板中还有一些常用和有用的字段:

  • priority让用户组织他们的工作项,标记什么应优先处理
  • kanban_state标记是否应移向下一阶段或因某种原因原地不动。在模型定义层中两者都是选择项字段。在视图层,对它们有特别的组件用于表单和看板视图。
  • color用于存储看板卡片显示的颜色,并可通过看板视图中的颜色拾取器菜单设置

编辑library_checkout/models/library_checkout.py文件来在我们的模型中添加这些字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Checkout(models.Model):
...
priority = fields.Selection(
[('0', 'Low'),
('1', 'Normal'),
('2', 'High')],
'Priority',
default='1')
kanban_state = fields.Selection(
[('normal', 'In Progress'),
('blocked', 'Blocked'),
('done', 'Ready for next stage')],
'Kanban State',
default='normal')

我们还应该在表单视图中添加这些字段,使用各自的特别组件。kanban_state字段就加在<div class="oe_title">之前并在按钮框之后:<field name="kanban_state" widget="state_selection" />。priority应添加在name 字段之前,包裹在<h1>元素中:<field name="priority" widget="priority" />。color字段一般不出现在表单视图中。

既然借阅模型已有我们所需使用的所有字段,我们可以来写看板视图了。

看板卡片元素

看板视图框架包含一个<kanban>外层元素和以下基础结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<kanban default_group_by="stage_id" class="o_kanban_small_column">
<!-- Fields -->
<field name="stage_id" />
<field name="id" />
<field name="color" />
<field name="kanban_state" />
<field name="priority" />
<field name="message_partner_ids" />

<!-- Optional progress bar -->
<progressbar
field="kanban_state"
colors='{"done": "success", "blocked": "danger"}' />
<!-- Templates with HTML snippets to use -->
<templates>
<t t-name="kanban-box">
<!-- HTML Qweb template -->
</t>
</template>
</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区域,应在看板盒子模型底部使用

并添加oe_kanban_bottom CSS 类。还可以通过oe_kanban_bottom_left和oe_kanban_bottom_right CSS 类进一步分割为左、右 footer 区。此外,可通过Bootstrap的pull-left和pull-right类在卡片的任意位置(包括oe_kanban_bottom底部区域)添加向左或向右对齐元素。

以下是对看板卡片中QWeb模板的第一次迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<t t-name="kanban-box">
<!-- Set the Kanban Card color -->
<div t-attf-class="
oe_kanban_color_#{kanban_getcolor(record.color.raw_value)}
oe_kanban_global_click">
<div class="o_dropdown_kanban dropdown">
<!-- Top-right drop down menu here... -->
</div>
<div class="oe_kanban_body">
<!-- Content elements and fields go here... -->
</div>
<div class="oe_kanban_footer">
<div class="oe_kanban_footer_left">
<!-- Left hand footer... -->
</div>
<div class="oe_kanban_footer_right">
<!-- Right hand footer... -->
</div>
</div>
<div class="oe_clear" />
</div>
</t>

这就是看板卡片的整体结构。你可能注意到了在顶部<div>元素中使用了color字段来动态设置卡片颜色。在后面的部分中我们会讲解t-attf QWeb指令的细节。现在来为主内容区域添加内容:

1
2
3
4
5
6
7
8
9
10
11
<div class="oe_kanban_body">
<div>
<strong>
<a type="open"><field name="member_id" /></a>
</strong>
</div>
<ul>
<li><field name="user_id" /></li>
<li><field name="request_date" /></li>
</ul>
</div>

这个模板中大部分都是常规 HTML,但也有渲染字段值的<field>元素和在常规表单视图按钮中使用的 type 属性,此处用在锚文本标签中。

在左部 footer 中插入优先级组件:

1
2
3
4
5
6
7
                            <div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<field name="priority" widget="priority" />
<field name="activity_ids" widget="kanban_activity" />
</div>
...
</div>

这里像我们在表单视图中做的那样添加了priority字段。还添加了一个计划活动的字段,使用kanban_activity特殊组件来显示即将开始活动的指示。

在右部footer中,放入看板状态组件和请求借阅的会员头像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="oe_kanban_bottom_right">
<field name="kanban_state"
widget="kanban_state_selection" />
<img t-att-src="kanban_image(
'library.checkout',
'member_image',
record.id.raw_value)"
t-att-title="record.member_id.value"
t-att-alt="record.member_id.value"
width="24"
height="24"
class="oe_kanban_avatar"
/>
</div>

补充:原文件使用的 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

Odoo 12看板 footer 添加

为看板卡片添加选项菜单

看板卡片可在右上角带有一个选项菜单。通常的操作有编辑或删除记录,但也可以为其添加和按钮调用的同样操作。还有一个设置卡片颜色的组件。以下是oe_kanban_content顶部添加的选项菜单的基础代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="o_dropdown_kanban dropdown">
<a class="dropdown-toggle btn"
data-toggle="dropdown" role="button"
aria-label="Dropdown menu"
title="Dropdown menu"
href="#">
<span class="fa fa-ellipsis-v" />
</a>
<div class="dropdown-menu" role="menu">
<!-- Edit and Delete actions, if available: -->
<t t-if="widget.editable">
<a role="menuitem" type="edit" class="dropdown-item">Edit</a>
</t>
<t t-if="widget.deletable">
<a role="menuitem" type="delete" class="dropdown-item">Delete</a>
</t>
<!-- Color picker option -->
<ul class="oe_kanban_colorpicker" data-field="color" />
</div>
</div>

下拉菜单基本上是由带有<a>标签的<li> HTML 列表元素组成。Edit 和 Delete 这类选项需要满足指定条件下才会出现。这通过QWeb的t-if命令来实现。本文后续会详细讲解QWeb的命令。widget全局变量表示一个KanbanRecord()  JS 对象,负责渲染当前看板卡片。有两个非常有用的属性:widget.editable和widget.deletable,让我们可以检查相应的操作是否可用。

可以看到如何根据记录字段值来显示或隐藏选项,Set as Done仅在未设置is_done 字段时才会显示。最后一个选项添加颜色拾取器组件来使用 color 数据字段选择或修改卡片背景色。因此,除