全书完整目录请见:Odoo 14开发者指南(Cookbook)第四版
业务应用不仅是存储记录还应管理业务工作流。有些对象,如线索(leads)或项目任务,有大量并行运行的记录。一个对象记录过多时会让厘清业务变得更为困难。Odoo有一些处理这种问题的技术。本章中,我们来学习如何设置带有动态阶段和看板组的业务工作流。这会有助于用户了解到业务是如何运行的。
我们还会学习服务端动作和自动化动作等技术,无需创建自定义插件即可助力用户或职能顾问添加更简单的流程自动化。最后,我们会创建一个基于QWeb的可打印PDF报表。
本章中,我们会讲解如下小节:
- 管理动态记录阶段
- 管理看板阶段
- 在看板卡片中添加快速创建表单
- 创建交互看板卡片
- 在看板视图中添加进度条
- 创建服务端动作
- 使用Python代码服务端动作
- 根据时间条件使用自动化动作
- 根据事件条件使用自动化动作
- 创建基于QWeb的PDF报告
- 通过看板卡片管理活动
- 对表单视图添加统计按钮
- 对记录启用存档选项
技术准备
本章的技术要求为搭建好了Odoo在线平台。
本章中的所有代码可通过GitHub仓库进行下载:https://github.com/alanhou/odoo14-cookbook。
管理动态记录阶段
在my_library中,有一个state字段用于表示图书租赁记录的当前状态。state仅限于ongoing或returned的状态,无法在业务流程中加入新状态。为避免这一问题,我们可以使用many2one字段来给予设计用户所选看板工作流足够的灵活性,并可以随时添加或删除新状态。
准备工作
本节我们将使用第八章 高级服务端开发技巧中的my_library模块。该模块管理图书及它们的分类。它还记录图书的借出。我们在本书的GitHub仓库添加了一个初始模块,Chapter12/00_initial_module/my_library,可以帮助你开始学习:https://github.com/alanhou/odoo14-cookbook/tree/main/Chapter12。
如何实现…
按照如下的简单步骤来对library.book.rent模块添加阶段:
- 新增名为library.rent.stage的模型如下:
123456789101112class LibraryRentStage(models.Model):_name = 'library.rent.stage'_order = 'sequence,name'name = fields.Char()sequence = fields.Integer()fold = fields.Boolean()book_state = fields.Selection([('available', 'Available'),('borrowed', 'Borrowed'),('lost', 'Lost')],'State', default="available") - 在security/ir.model.access.csv文件中为这个新模型添加访问权限,如下:
12acl_book_rent_stage,library.book_rent_stage_default,model_library_rent_stage,,1,0,0,0acl_book_rent_librarian_stage,library.book_rent_stage_librarian,model_library_rent_stage,group_librarian,1,1,1,1 - 在library.book.rent模型中删除 state 字段,替换为一个新的many2one类型的stage_id字段及其方法,如下例所示:
123456789@api.modeldef _default_rent_stage(self):Stage = self.env['library.rent.stage']return Stage.search([], limit=1)stage_id = fields.Many2one('library.rent.stage',default=_default_rent_stage) - 将表单视图中的state字段替换为stage_id字段,如下例所示:
1234<header><field name="stage_id" widget="statusbar"options="{'clickable': '1', 'fold_field': 'fold'}"/></header> - 在列表视图的state字段替换为stage_id字段,如下所示:
12345<tree><field name="book_id"/><field name="borrower_id"/><field name="stage_id"/></tree> - 在data/library_stage.xml文件中添加一些初始阶段。同时不要忘记在声明文件中进行添加,如下例所示:
1234567891011121314151617181920212223242526272829<?xml version="1.0" encoding="utf-8"?><odoo noupdate="1"><record id="stage_draft" model="library.rent.stage"><field name="name">Draft</field><field name="sequence">1</field><field name="book_state">available</field></record><record id="stage_rent" model="library.rent.stage"><field name="name">On rent</field><field name="sequence">5</field><field name="book_state">borrowed</field></record><record id="stage_due" model="library.rent.stage"><field name="name">Due</field><field name="sequence">15</field><field name="book_state">borrowed</field></record><record id="stage_returned" model="library.rent.stage"><field name="name">Completed</field><field name="sequence">25</field><field name="book_state">available</field></record><record id="stage_lost" model="library.rent.stage"><field name="name">Lost</field><field name="sequence">35</field><field name="fold" eval="True"/><field name="book_state">lost</field></record></odoo>
在安装该模块之后,就可以在表单视图中看到这些阶段了,如下图所示:
图12.1 – 表单视图中的阶段选择器
从上图中可以到图书租借记录的各个阶段。这些阶段均可以点击,因此可以通过点击来修改阶段。折叠阶段显示在 More 下拉列表中。
运行原理…
因为我们想要动态管理记录阶段,就需要新建一个模型。在第1步中,创建了一个名为library.rent.stage的新模型用于存储动态阶段。在这个模型中,我们添加了一些新字段。其中有sequence字段,用于决定阶段的排序。我们还添加了布尔字段fold,用于收缩起阶段显示在下拉列表中。对于拥有大量阶段的业务流程这会很有帮助,因为那样我们可以通过设置这个字段来将不重要的阶段隐藏在下拉菜单中。我们还添加了一个book_state字段来进行动态阶段和图书状态之间的映射。在接下来的部分中会使用到它。
fold字段也可用于在看板视图中展示折叠的看板列。通常,进行中的任务应处于展开阶段,而为完成或取消的完结项应为折腾阶段。
默认fold是用于存储阶段折叠值的字段名。但我们可以通过添加类属性_fold_name = ‘is_fold’进行修改。
在第2步中,我们对新模型添加了基本访问权限规则。
在第3步中,我们在 library.book.rent模型中添加了stage_id many2one 字段。在创建新的借阅记录时,我们希望将默认阶段的值设置为Draft。添加了一个_default_rent_stage() 方法来进行实现。这个方法会获取 library.rent.stage模型中序列值最低的记录,因而在新建记录时,序列值最低的阶段会在表单视图中显示为当前阶段。
在第4步中,我们在表单视图中添加了stage_id字段。通过添加clickable选项,使得状态栏可点击。我们还为fold字段添加了选项来将不重要的阶段显示在下拉菜单中。
第5步中,我们在列表视图中添加了stage_id字段。
第6步中,我们添加了不同阶段的默认值。用户在安装这个模块后会看到这些基本阶段。如果想要学习XML数据语法的更多知识,请参见第六章 管理模块数据中的使用XML文件加载数据一节。
📝通过以上实现,用户可随时定义新阶段。我们需要为library.rent.stage添加视图和菜单来在用户界面中添加新的阶段。如不知道如何添加视图和菜单的话请参见第九章 后端视图。
如果想要避免这一麻烦,看板视图提供了内置的在看板视图自身中添加、删除或修改阶段的功能,在下一节中会进行讲解。
扩展知识…
注意我们在library.book模型中有一个state字段。该字段用于展示图书的状态,换句话说它是否可借阅。我们添加了book_state字段来建立图书状态与动态阶段的映射。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@api.model def create(self, vals): rent = super(LibraryBookRent, self).create(vals) if rent.stage_id.book_state: rent.book_id.state = rent.stage_id.book_state return rent @api.multi def write(self, vals): rent = super(LibraryBookRent, self).write(vals) if self.stage_id.book_state: self.book_id.state = self.stage_id.book_state return rent |
此后,在用户修改任意借阅记录的状态时,它都会在图书记录中进行反映。
扩展知识
- 参见第九章 后端视图学习有关添加视图和菜单的知识。
管理看板阶段
看板是用于管理工作流的一种简单方法。它按列进行组织,每一列对应一个阶段,任务项从左到右进行直至结束。带有阶段的看板视图让用户可以选择自己的工作流,提供了灵活性。它在单屏中提供了记录的完整概览。
准备工作
本节中,我们将使用前一小节的my_library模块。对libary.book.rent模型添加看板并通过阶段对看板卡片进行分组。
如何实现…
按照如下步骤来启用工作流,如图书借阅模型的看板:
- 为libary.book.rent添加一个看板视图,如下:
1234567891011121314151617181920212223242526272829<record id="library_book_rent_view_kanban" model="ir.ui.view"><field name="name">Rent Kanban</field><field name="model">library.book.rent</field><field name="arch" type="xml"><kanban default_group_by="stage_id"><field name="stage_id" /><templates><t t-name="kanban-box"><div class="oe_kanban_global_click"><div class="oe_kanban_content"><div class="oe_kanban_card"><div><i class="fa fa-user"/><b><field name="borrower_id" /></b></div><div class="text-muted"><i class="fa fa-book"/><field name="book_id" /></div></div></div></div></t></templates></kanban></field></record> - 在 library_book_rent_action动作中添加看板如下:
123...<field name="view_mode">kanban,tree,form</field>... - 添加_group_expand_stages()方法并在stage_id字段中添加group_expand属性,如下:
12345678@api.modeldef _group_expand_stages(self, stages, domain, order):return stages.search([], order=order)stage_id = fields.Many2one('library.rent.stage',default=_default_rent_stage,group_expand='_group_expand_stages')
重启服务并更新模块来应用修改。这时会启用看板,如下图所示:
从上图中可以看出,看板会按阶段进行分组显示借书记录。可以将卡片拖至另外一个阶段栏目中。移动卡片同时也会修改其在数据库中的阶段值。
运行原理…
在第1步中,我们为 library.book.rent模型添加了一个看板视图。注意我们使用了stage_id作为看板的默认组,因此在用户打开看板时,看板卡片会按阶段进行分组。了解更多有关看板的知识,请参见第九章 后端视图。
在第2步中,我们在已有动作中添加了kanban关键字。
在第3步中,我们在stage_id字段中添加了group_expand属性。我们还添加了_group_expand_stages()方法。group_expand修改了字段分组的行为。默认字段分组显示了所使用的阶段。例如,如果没有lost阶段的租借记录,分组就不会返回该阶段,因此看板也不会显示lost列。但在本例中,我们希望不论是是否使用了都显示所有的阶段。
_group_expand_stages()函数用于返回阶段的所有记录。因此,看板视图会显示所有阶段,并且能通过拖拽它们来应用工作流。
扩展知识…
如果你不停地使用本节中的看板,会发现有大量的功能。其中一些描述如下:
- 可以通过点击Add new column的选项来新建阶段。group_create选项可用于在看板中禁用这一选项。
- 可以通过拖拽列头来以不同顺序对列排序。这会更新library.rent.stage模型中的sequence字段。
- 可以通过看板列头部的齿轮图标来编辑或删除列。group_edit和group_delete选项可用于禁用这一功能。
- 阶段的fold字段值为true时会进行折叠并且列会显示为很细的一条( 参见上图中的Lost)。点击它时会扩展并显示看板卡片。
- 如果模型有布尔字段active的话,它会显示选项来在看板列中存档记录或取消存档。archivable选项可用于禁用这个功能。
- 看板列上的加号图标可用于直接从看板视图创建记录。quick_create选项可用于禁用这一功能。当前这一个功能在本例中无法使用。在下一节中会进行解决。
其它内容
- 学习更多有关看板的知识,参见第九章 后端视图。
在看板卡片中添加快速创建表单
分组的看板视图提供有快速创建功能,让我们可直接通过看板视图生成记录。列上的加号图标会在列中显示可编辑看板卡片,用于创建记录。本节中,我们将学习如何设计一个快速创建你所心仪的看板表单。
准备工作
本节将使用前一小节中的my_library模块。我们将在看板中对library.book.rent模型使用快速创建选项。
如何实现…
按照下面的步骤来对看板添加自定义快速创建表单:
-
- 对library.book.rent模型新建一个最小化表单视图,如下:
123456789101112<record id="library_book_rent_view_form_minimal" model="ir.ui.view"><field name="name">Library Rent Form</field><field name="model">library.book.rent</field><field name="arch" type="xml"><form><group><field name="book_id" domain="[('state', '=', 'available')]"/><field name="borrower_id"/></group></form></field></record> - 在<kanban>标签中添加快速创建选项,如下:
123<kanban default_group_by="stage_id"quick_create_view="my_library.library_book_rent_view_form_minimal"on_create="quick_create"> - 重启服务并更新模块来应用修改。然后点击列上方的加号图标。它会启用如下图所示的看板表单:
- 对library.book.rent模型新建一个最小化表单视图,如下:
在看板视图中点击Create按钮时,会看到一个带有输入框的小卡片,而不会直接跳转到表单视图。可以在其它填入值、点击Add按钮,这会创建一条借书记录。
运行原理…
创建一个自定义快速创建选项,我们需要创建一个最小化表单视图。这在第1步进行了实现。我们添加了两个必填字段,因为创建记录时缺少它们会产生报错。如果表单未包含所有必填字段,Odoo会报错并在对话框中打开默认的表单视图要求你填写所有的必填字段值(译者注:报错不在前台,请查看服务端日志)。
第2步中,我们在看板视图中添加了这一新表单视图。使用quick_create_view选项可以建立自定义表单视图与看板视图之间的映射关系。我们还添加了一个额外的选项on_create=”quick_create”。该选项会在我们在控制面板中点击Create按钮时在第1列中显示快速创建表单。没有该选项,Create会以可编辑模式打开表单视图。
我们可以通过在kanban标签中添加quick_create=”false”来禁用快速创建功能。
ℹ️quick_create_view选项在Odoo版本12中进行的添加。在此前的版本中,快速创建选项仅在name字段中显示。如果有一些其它必填字段,需要重载name_create()来设置其它值。如果不希望重载name_create()方法的话可以对必填字段设置默认值。
创建交互看板卡片
看板卡片支持所有的HTML标签,也就是说能够以想要的任何方式进行设计。Odoo提供内置的方式来让看板卡片更具交互性。本节中我们将在看板卡片中添加颜色选项,星标组件以及many2many标签。
准备工作
本节中我们将使用前一小节中的my_library模块。
如何实现…
按照如下步骤来创建一个美观的看板卡片。
- 添加新模型来管理library.book.rent模型的标签,如下:
1234class LibraryRentTags(models.Model):_name = 'library.rent.tag'name = fields.Char()color = fields.Integer() - 为library.rent.tag模型添加基本权限,如下:
12acl_book_rent_tags,library.book_rent_tags_default,model_library_rent_tag,,1,0,0,0acl_book_rent_librarian_tags,library.book_rent_tags_librarian,model_library_rent_tag,group_librarian,1,1,1,1 - 在library.book.rent模型中新增字段如下:
1234567color = fields.Integer()popularity = fields.Selection([('no', 'No Demand'),('low', 'Low Demand'),('medium', 'Average Demand'),('high', 'High Demand')], default="no")tag_ids = fields.Many2many('library.rent.tag') - 在表单视图中添加字段,如下:
123<field name="popularity" widget="priority"/><field name="tag_ids" widget="many2many_tags"options="{'color_field': 'color', 'no_create_edit': True}"/>
在接下来的几步中,我们将更新已有的看板视图。新增的代码会进行加粗显示。 - 在看板视图中color字段如下:
1234...<field name="stage_id" /><field name="color" />... - 在看板视图中添加下拉框拾取颜色:
123456789101112131415161718...<t t-name="kanban-box"><div t-attf-class="#{kanban_color(record.color.raw_value)} oe_kanban_global_click"><div class="o_dropdown_kanban dropdown"><a class="dropdown-toggle o-no-caret btn" role="button" data-toggle="dropdown"><span class="fa fa-ellipsis-v"/></a><div class="dropdown-menu" role="menu"><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><ul class="oe_kanban_colorpicker" data-field="color"/></div></div>... - 在看板视图中添加标签和popularity字段:
123456789101112...<div class="text-muted"><i class="fa fa-book"/><field name="book_id" /></div><span class="oe_kanban_list_many2many"><field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/></span><div><field name="popularity" widget="priority"/></div>...
📝重要信息:粗体部分的代码应在已有看板视图中进行添加。
重启服务并更新模块来应用修改。然后,点击列上的加号图标。会显示如下图所示的看板:
我们在看板结构中的修改会在看板卡片中启用额外的选项。现在可以对看板自身选取颜色。同时,我们可以通过星标来排卡片的优先级。
运行原理…
在前两步中,我们为标签新增了模型及安全规则。在第3步中,我们在借阅的模型中新增了一些字段。
在第4步中,我们在表单视图中添加了一些字段。注意我们对popularity字段使用了优先级组件,它将选择字段显示为星标。在tag_ids字段中,我们使用了many2many_tags组件,它在标签的形式显示many2many字段。传递color_field选项来对标签中启用颜色功能。该选项的值为颜色索引所存储的字段名。no_create_edit选项会禁用通过表单视图创建新标签的功能。
在第5步中,我们做了很多的改进。在看板卡片中添加了t-attfclass=”#{kanban_color(record.color.raw_value)}。它会用于显示看板卡片的颜色。它使用color字段值并根据该值生成一个css class。例如,如果一个看板记录在颜色字段有一个值2,它会在类中添加kanban_color_2。然后我们添加了一个下拉菜单来添加编辑、删除和看板颜色拾取器等选项。编辑和删除选项仅在用户有相应权限时才会显示。
最后,我们在看板卡片中添加了标签和优先级。在进行了这些新增后,看板应该会长成下图这样:
图12.5 – 看板卡片选项
通过这一卡片设计,我们将能够直接通过看板卡片来设置优先级星标和颜色。
在看板视图上添加进度条
有时,我们在列中有大量的记录并很难对某个阶段获取清晰的图景。进度条可用于显示列的状态。本节中,我们将根据popularity字段来在看板中展示进度条。
准备工作
本节中我们将使用前一节中的my_library模块。
如何实现…
为在看板栏中添加进度条,我们将需要在看板视图的定义中添加progressbar标签,如下:
1 2 3 |
<progressbar field="popularity" colors='{"low": "success", "medium": "warning", "high": "danger"}'/> |
注意看板栏进度条是在Odoo 11中引入的。在那之前的版本中不会显示列进度条。
重启服务并更新模块来应用修改。然后,点击列中的加号图标。这会在看板列中显示进度条,如下图所示:
更新模块,就在看板栏中添加了进度条。进度条的颜色显示基于记录状态的记录条数。可以点击其中的进度条来按状态过滤记录。
运行原理…
看板列中的进度条根据字段的值来进行显示。进度条支持4种颜色,因此无法显示4种以上的状态。可用的颜色有绿色(成功)、蓝色(信息)、红色(危险)和黄色(警告)。然后我们需要将颜色与字段状态进行映射。本例中,我们映射了流行度字段的三种状态,因为我们不希望没有需求的图书有任何进度条显示。
默认,进度条的旁边会显示记录的条数。可以在进度条上点击它来查看具体状态的总值。点击进度条还会高亮显示该状态的卡片。除了对记录计数外,我们还可以显示整型或浮点型字段的总和。进行这一实现,需要添加带有字段值的sum_field属性,如sum_field=”field_name”。
创建服务端动作
服务端动作加持Odoo的自动化工具。我们通过它来描述要执行的动作。这些动作随后可由事件触发器调用,或在满足某些时间条件时自动触发。
最简单的用例是让终端用户通过在More按钮中选择文档对其执行某一动作。我们将为项目任务创建这种类型的动作,通过星标当前所选任务来Set as Priority(设置为优先)并设置三天后为截止日期。
准备工作
我们需要一个Odoo实例并安装Project应用。还需要启用开发者模式。如尚未启用,请通过Odoo的Settings界面进行激活。
如何实现…
按照如下步骤来创建服务端动作并在More菜单中使用:
- 在Settings顶级菜单中,选择Technical > Actions > Server Actions菜单项,并在记录列表的顶部点击Create按钮,如下图所示:
图12.7 – 服务端动作表单视图 - 通过如下值填写服务端动作表单:
- Action Name: Set as Priority
- Base Model: Task
- Action To Do: Update the Record
- 在服务端动作对话框的Data to write 标签下添加如下行
-
- 第一个值我们将输入如下参数:
- Field: Deadline
- Evaluation Type: Python expression
- Value: datetime.date.today() + datetime.timedelta(days=3)
- 第二个值我们将输入如下参数
- Field: Priority
- Evaluation Type: Value
- Value: 1
- 第一个值我们将输入如下参数:
下图显示了所输入的值:
图12.8 – 设置写入的行 -
- 保存服务端动作并点击左上角的Create contextual action按钮来让其出现在项目任务的Action下拉列表中。
- 进入Project顶级菜单,选择Search > Tasks菜单项,并随机打开一个任务。通过点击Action下拉列表,我们应该可以看到下图中显示的Set as Priority选项。选择它会对任务评星并修改截止日期为3天后:
图12.9 – 设置优化级服务端动作
添加了该服务端动作之后,在任务中就会出现设置优化级的选项。点击后该动作会将星标变成黄色,表示该任务的优先级上升。同时该服务端动作会修改截止日期。
运行原理…
服务端动作对模型进行操作,因此首先要做的一件事是选择我们希望操作的模型。本例中,我们使用了项目任务。
接下来,我们应选择所执行动作的类型。有一些可选项:
- 在其它的选项都无法灵活到做我们需要实现的操作时,Execute Python Code允许我们编写要执行的任意代码。
- Create a new Record允许我们在当前模型或其它模型上新建记录。
- Update the Record允许我们对当前模型或其它模型设置值。
- Send Email允许我们选择一个email模板。它会用于在动作触发时发送邮件。
- Execute several actions可用于触发客户端或窗口动作,和点击菜单项的效果一样。
- Add Followers允许用户或频道订阅记录。
- Create Next Activity允许我们创建新的活动。这会在聊天器(chatter)中显示。
- Send SMS Text Message 让我们可以发送短信。需要选择短信模板。
📝注:Send SMS Text Message是Odoo 中的收费服务。如需发送短信需要先购买短信积分。
本例中我们使用了Update the Record来设置当前记录的一些值。我们设置了Priority为1来星标任务,并对Deadline字段设置了值。这个更有意思一些,因为所使用的值由Python表达式运算而得。本例中使用了Python中的datetime模块来计算三天后的日期。
这里以及其它动作类型中均可使用任意的Python表达式。出于安全原因,代码由odoo/tools/safe_eval.py文件中所实现的safe_eval函数进行检查。这意味着会不允许某些Python运算,但通常这都不构成问题。
在下拉选项中添加服务端动作时,通常对所有内部用户均可用。但如果希望仅对选定用户显示的话,可以对服务端动作添加组。这在服务端动作表单视图的Security标签栏中可以找到。
扩展知识…
Python代码在一个受限的上下文中运行,可以使用如下的对象:
- env:这是对Environment对象的引用 ,类似类方法中的self.env 。
- model:这是对服务端动作所操作的模型类的引用。本例中它等价于self.env[‘project.task]。
- Warning:这是对openerp.exceptions.Warning的引用 ,允许进行阻止不希望动作的验证。它可用于抛出Warning(‘Message!’)。
- record或records:它提供对当前记录的引用,让我们可访问它们的字段值和方法。
- log:它用于在ir.logging 模型中记录消息,允许数据库端的日志记录动作。
- datetime, dateutil和 time:它们提供对Python库的访问。
使用Python代码服务端动作
服务端动作有多种可用类型,但执行自己的Python代码是最灵活的。正确地使用会让具备能力的用户通过用户界面实现高级业务规则,而无需创建具体的插件模块来安装该代码。
我们将展示通过实现向项目任务的follower(订阅者)发送提醒消息的服务端动作来使用这种类型的服务端动作。
准备工作
我们需要一个安装了Project应用的Odoo实例。
如何实现…
按照如下步骤来创建Python代码服务端动作:
- 新建一个服务端动作。在Settings菜单中,选择Technical > Actions > Server Actions菜单项,并点击记录列表顶部的Create按钮。
- 通过如下值填写服务端动作表单:
- Action Name: Send Reminder
- Base Model: Task
- Action To Do: Execute Python Code
- 在Python代码文本域中,删除默认文本并替换为如下代码:
1234567891011if not record.date_deadline:raise Warning('Task has no deadline!')delta = record.date_deadline - datetime.date.today()days = delta.daysif days==0:msg = 'Task is due today.'elif days < 0:msg = 'Task is %d day(s) late.' % abs(days)else:msg = 'Task will be due in %d day(s).' % daysrecord.message_post(body=msg, subject='Reminder', message_type='comment', subtype_xmlid='mail.mt_comment')
下图显示了所输入的值:
图12.10 – 带输入值的Python代码 - 保存这一服务端动作并点击右上角的Create Contextual Action来让其出现在项目任务的Action下拉列表中。
- 此时,点击Project顶级菜单并选择Search > Tasks菜单项。随机选取一个任务,对其设置截止日期,然后尝试使用Action下拉列表中的Send Reminder选项。
译者注:看原书的朋友会收到这样的错误:message_post doesn’t support subtype parameter anymore. Please give a valid subtype_id or subtype_xmlid value instead,请参照上面最后一行代码进行修改。
这和前一小节一样会生效,区别在于这一服务端动作使用的是Python代码。对任务执行该服务端动作后,会在聊天界面添加一条消息。
运行原理…
本章中创建服务端动作一节中详细地讲解了如何创建常用的服务端动作。对于这种特定类型的动作,我们需要选择Execute Python Code选项,然后在运行文本域中编写代码。
代码可以为多行,本例即为如此,它在一个引用了当前记录对象或会话用户等对象的上下文中运行。可以使用的引用在创建服务端动作一节中有进行讲解。
我们所使用的代码计算当前日期到截止日期的天数,并使用它来准备相应的通知消息。最后一行在任务的消息墙中进行了实际的消息发布。邮件通知需要subtype=’mt_comment’来将其发送给订阅者,和我们使用New Message按钮的情况一样。如果未给定子类型,默认使用mt_note,发布内部记录而不进行通知,和使用 Log an internal note按钮的效果一样。参见第二十三章 在Odoo中管理Email来学习Odoo中有关mailing更多的知识。
扩展知识…
Python代码服务端动作是非常强大和灵活的方法,但相比自定义插件模块还存在一些限制。
因为Python代码是在运行时执行,如果发生错误的话,栈追踪所提供的信息有限,调试会更加困难。也无法通过第七章 调试模块中演示的技术,在服务端动作代码中加入断点,因此调试需要使用日志语句来实现。另一个点是,在尝试追踪模块代码中行为的原因时,可能无法找到相关的信息。这可能是由服务端动作所导致的。
在执行服务端动作更密集的使用时,交互可能会相当复杂,因此推荐适当规划并保持有效组织。
其它内容
- 参见第二十三章 在Odoo中管理Email来学习Odoo中有关mailing更多的知识
根据时间条件使用自动化动作
自动化动作可用于自动根据时间条件触发动作。我们可以使用它来自动对一些满足某些条件或时间条件的记录执行一些操作。
作为示例,我们可以在截止日期的前一天触发一个对项目任务的提醒消息。下面来看如何实现。
准备工作
学习本节,我们需要同时安装项目管理应用(技术名称为project)和自动化动作规则插件(技术名称base_automation),并应启用开发者模式。我们还需要本章使用 Python 代码服务端动作一节中所创建的服务端动作。
如何实现…
按照如下步骤来对任务创建一个带有定时条件的自动化动作:
- 在Settings菜单中,选择Technical > Automation > Automated Actions菜单项,并点击Create按钮。
- 填写Automated Actions表单中的基本信息:
- Action Name: Send notification near deadline
- Related Document Model: Task
- 在Trigger条件字段中选择Based on Time Condition
- 在Action To Do中选择Execute several actions
- 要设置记录条件,点击Apply on版块中的Edit Domain按钮。在弹出对话框的代码编辑器中,设置有效域表达式,[“&”,[“date_deadline”,”!=”,False],[“stage_id.fold”,”=”,Fal se]],并点击Save按钮。修改另一个字段时,满足条件的记录数信息会被更新,并显示Record(s)按钮。通过点击Records按钮,我们可以查看满足域表达式记录的记录列表。
- 对Trigger Date设置时间条件,选择要使用的字段,即Deadline,并设置Delay After Trigger Date为-1 Days。
- 在Actions标签栏的Server actions to run下面,点击Add an item并从列表选择此前应已创建的 Send Reminder,如下图所示:
如未创建,我们仍可以通过Create按钮创建服务端动作来运行
- 点击Save来保存自动化动作。
- 执行如下步骤来进行测试
- 进入Project菜单,访问Search > Tasks,并用过去的日期来为任务设置截止日期。
- 进入Settings菜单,点击Technical > Automation > Scheduled Actions菜单项,在列表中找到Base Action Rule: Check and execute动作,打开它的表单视图,并在左上角点击Run Manually按钮。这会强制定时自动化动作当前进行检查 。这在下图中有显示。注意这对新创建的demo数据可以使用,但是在已有的数据库中可能无法正常运行:
图12.12 – 运行自动化动作(用于测试)
- 再次进入Project菜单并打开此前设置截止日期的同一任务。查看消息面板,应该可以看到由自动化触发的服务端动作所生成的提醒消息。
在添加针对截止日期的自动化动作,会在任务截止的前一天添加提醒消息。
运行原理…
自动化动作作用于模型,可由事件或时间条件触发。前几步为设置Model和When to Run的值。
两种方法都可使用过滤器来过滤出仅用于执行动作的记录。我们可以使用域表达式来进行实现。可以在第九章 后端视图中找到有关编写域表达式的更进一步信息。此外,我们可以使用用户界面功能对项目任务创建并保存过滤器,然后复制自动生成的域表达式,从Set selection based on a search filter列表中进行选择。
我们使用的域表达式选择所有在未勾选Fold标记的阶段中的带有截止日期的记录。没有Fold标记的阶段被视作进行中。这样,我们避免了对处于Done, Canceled或Closed阶段的任务触发通知。
然后我们应定义时间条件 – 在触发动作时所使用的日期字段。时间区间可以为分钟、小时、天或月,并且区间值可为正值是日期后的时间,或者是负值为日期前的时间。在以天来使用时间区间时,我们可以提供一个定义工作日的资源日历并可用于以天计数。
由Check Action Rules计划任务来检查。注意默认这是每4小时运行一次的。这对以天或月为单位的动作会很合适,但如果你需要对更小的时间刻度进行动作的操作,则需要将运行的间隔调整为更小的值。
满足所有条件或最后一次动作执行之后的触发日期条件(字段日期加间隔)的记录的动作会被触发。这是为避免重复触发相同的动作。同时,这也是手动运行之前的动作可以在计划动作尚未触发的数据库中生效的原因,而可能不会在已由调度器运行的数据库中立即生效。
一旦触发了自动化动作,Actions标签会告诉你应发生什么。这可能是一个执行像修改记录值、发布消息或发送邮件这样的服务端动作。
扩展知识…
这些类型的自动化动作在满足某一时间条件时触发。它不是在条件仍为true时反复重复的动作。例如,自动化动作不能够在超过截止日期之后每天发布提醒。
而这种类型的动作可由存储于ir.cron模型中的计划执行。但是计划动作不支持服务端动作,它们仅能调用模型对象中的已有方法。因此,要实现一个自定义动作,我们需要写一个插件模块,添加所需的Python方法。
作为参考 ,其技术名称为base.action.rule。
其它内容
- 有关编写域表达式的更多详情,请参见第九章 后端视图。
根据事件条件使用自动化动作
业务应用提供带有针对业务操作记录的系统,但也支持具体针对某一组织使用用例的动态业务规则。
把这些规则固化到插件模块中会很不灵活并且不在功能性用户的使用范围内。由事件条件触发的自动化动作可以很好的弥补这一问题并且提供自动化或强化组织流程的强大工具。作为示例,我们将强化对项目任务的验证,这样仅有项目经理能修改任务为Done阶段。
准备工作
学习本节,需要先安装项目管理应用。我们还需要启用开发者模式。如尚未启用,在Odoo的Settings界面中进行激活。
如何实现…
按照如下步骤来对任务创建带有事件条件的自动化动作:
- 在Settings菜单中,选择Technical > Automation > Automated Actions菜单项,并点击Create按钮。
- 填写自动化动作表单中的基本信息:
- Action Name: Validate Closing Tasks
- Model: Task
- Trigger Condition: On Update
- Action To Do: Execute several actions
- Trigger Fields: Stage id
- On Update规则允许我们在更新操作之前和之后设置两个记录过滤器:
在 Before Update Filter字段中,点击Edit domain按钮,在代码编辑器中设置一个有效的域表达式 – [(‘stage_id.name’, ‘!=’, ‘Done’)],并保存。
在Apply on字段中,点击Edit domain按钮,在代码编辑器中设置 [(‘stage_id.name’, ‘=’, ‘Done’)] 域并保存,如下图所示:
图12.13 – 自动化动作表单视图 - 在Actions标签栏中点击Add an item。然后在列表对话框中点击Create按钮来新建一个服务端动作。
- 通过如下值填写服务端动作表单,然后点击Save按钮:
- Action Name: Validate closing tasks
- Model: Task
- Action To Do: Execute Python code
- Python Code: 输入如下代码:
12if user != record.project_id.user_id:raise Warning('Only the Project Manager can close Tasks')
所输入的值如下图所示:
图12.14 – 添加子动作
- 点击Save & Close来保存自动化动作并进行测试:
- 使用带有演示数据的数据库并以Administrator登录,进入Project菜单并点击项目来打开任务的看板视图。
- 然后,试着拖拽一个任务到Done阶段栏中。因为这个项目的管理员是Demo用户,而我们使用的是Administrator用户,自动化动作会被触发,而我们的警告信息会阻止这一修改。
运行原理…
我们先通过为自动化动作命名,然后设置它所应操作的模型。对我们所需要的动作类型应选择On Update,但还有On Creation, On Creation & Update, On Deletion和Based On Form Modification选项。
接下来,我们定义了过滤器来决定何时应触发我们的动作。On Update动作允许我们定义两个过滤器 – 一个在记录修改前、一个在修改后检查。这可用于表示转换 – 监测何时从状态 A 变为状态 B。在本例中,我们希望在未完成任务修改为Done阶段时触发该动作。On Update是允许这两个过滤器的唯一动作类型,其它动作类型仅允许使用一个过滤器。
📝重要提示:应格外注意我们的示例条件仅对英语用户才能正常使用。这是因为Stage Name(阶段名)是一个可翻译字段,在不同语言中有不同的值。因此,在使用可翻译字段作为过滤器时应非常小心。
最后,我们通过在自动化动作触发时希望完成的事创建并添加了一个(或多个)服务端动作。本例中,我们选择演示如何实现自定义验证,利用使用Warning异常阻止用户修改的Python代码服务端动作。
扩展知识…
在第五章 基本服务端开发中,我们学习了如何重定义模型的write()方法来对更新记录执行动作。记录更新自动化动作提供另一种实现方式,有诸多好处和缺点。
好处有很容易定义由所存储的计算字段触发动作,这对纯代码实现会非常麻烦。也可以对记录定义过滤器并对不同记录添加不同规则,或可通过搜索域匹配不同条件的记录。
但是,在对比模型中的Python业务逻辑时自动化动作也会有一些不利之处。规划不当的话,它所具备的灵活性会导致复杂的交互,很难进行维护和调试。同时,write过滤器之前和之后的操作会导致代码过重,这对于执行敏感性动作会是一个问题。
创建基于QWeb的PDF报告
在与外界进行沟通时,通过数据库中的记录生成PDF文档通常是必要的。Odoo使用了与表单视图所使用的相同模型语言QWeb。
本节中,我们将创建一个QWeb报告来打印当前某个成员借阅的图书的信息。本节将复用本章稍前的使用看板卡片快速创建表单一节中所展示的模型。
准备工作
我尚未安装wkhtmltopdf,请按照第一章 安装Odoo开发环境中所描述的方式进行安装,否则将无法得到漂亮的PDF文件。
同时,再检查一下配置参数web.base.url(或者report.url) 是否为Odoo实例所能访问的URL,否则,报表会花费很长时间来生成并且结果看上去会很奇怪。
如何实现…
- 本节中,我们对res.partner添加了一个报表,它打印出成员所借阅的图书列表。我们需要在与library.book.rent模型相关联的partner模型上添加一个one2many字段,如下例所示:
1234class ResPartner(models.Model):_inherit = 'res.partner'rent_ids = fields.One2many('library.book.rent', 'borrower_id') - 在reports/book_rent_templates.xml文件中为报表定义一个视图,如下:
12345678910111213141516171819202122232425262728<?xml version="1.0" encoding="utf-8"?><odoo><template id="book_rents_template"><t t-call="web.html_container"><t t-foreach="docs" t-as="doc"><t t-call="web.internal_layout"><div class="page"><h1>Book Rent for <t t-esc="doc.name"/></h1><table class="table table-condensed"><thead><tr><th>Title</th><th>Expected return date</th></tr></thead><tbody><tr t-foreach="doc.rent_ids" t-as="rent" ><td><t t-esc="rent.book_id.name" /></td><td><t t-esc="rent.return_date" /></td></tr></tbody></table></div></t></t></t></template></odoo> - 在reports/book_rent_report.xml文件中添加一个标签,如下例所示:
12345678<?xml version="1.0" encoding="utf-8"?><odoo><report id="report_book_rent"name="my_library.book_rents_template"model="res.partner"string="Book Rents"report_type="qweb-pdf" /></odoo> - 在插件的声明文件中添加这两个文件并在依赖中添加contacts,这样可以打开partner的表单视图,如下例所示:
12345678910...'depends': ['base', 'contacts'],'data': ['views/library_book.xml','views/library_member.xml',...'reports/book_loan_report.xml','reports/book_loan_report_template.xml',]...
此时,在打开partner表单的时候,或者在列表视图中选择成员时,会在下拉菜单中出现打印图书借阅的选项,如下图所示:
图12.15 – 报表打印动作
译者注:如出现中文乱码,请安装字体:
1 2 |
sudo apt-get install ttf-wqy-zenhei -y sudo apt-get install ttf-wqy-zenhei -y |
运行原理…
第1步中,我们添加了one2many字段rent_ids。该字段包含客户的借书记录。我们将在QWeb报表中用它来列出用户所借阅的书籍。
第2步中我们定义了QWeb模板。模板的内容用于生成 PDF。本例中,我们使用了一些基本HTML结构。还使用了一些属性如t-esc和t-foreach,用于在报告中生成动态内容。现在不必担心template元素中的语法。这个话题会在第十四章 CMS网站开发中的使用客户端QWeb模板一节是进行深入讲解。
另外模板要重点注意的是布局。本例中,我们在模板中使用了web.internal_layout,它会以最小化的头部和底部生成最终的PDF。如果想要带有更多信息的头部以及带有公司 Logo 和公司信息的底部,使用web.external_layout部局。我们也可以在用户通过列表视图打印时,对用于生成多条记录报告的docs参数添加一个for循环。
在第3步中,我们在另一个XML文件中通过<record>声明了报表 。它会注册报告的ir.actions.report模型。这里最关键的部分是设置report_name字段来完成所定义模板的XML ID(即modulename.record_id),否则报表的生成会失败。model属性决定报表操作的是哪种类型的记录,而name字段是在打印菜单中对用户显示的名称。
📝注:在之前的Odoo版本中,<report>标签用于注册报告。但从v14开始进行了淘汰,我们需要通过<record>标签创建一条ir.actions.report的记录。为保持向后兼容Odoo v14中仍支持<report>标签,但使用时会在日志中报出一条警告。
通过设置report_type为qweb-pdf,我们请求由视图所生成的 HTML 由wkhtmltopdf运行并以PDF分发给用户。在有些情况中,我们可能会希望使用qweb-html 来在浏览器中渲染HTML。
扩展知识…
报表的HTML中有一些标记类对于布局非常重要。确保所有的内容封装在一个设置了page类的元素中。如果忘记的话,什么都不会显示。使用header或footer类来对记录添加头部和底部。
还有要记住这是HTML,因此可以使用pagebreak-before, page-break-after和page-break-inside等CSS属性。
可以看到我们的所有模板内容体在带有t-call属性的两个元素中包裹。我们会在稍后的第十四章 CMS网站开发中了解这一属性的机制,但在报表中进行相同的操作非常关键。这些元素处理HTML生成对所有必要的CSS的连接以及包含一些报表生成所需的数据。虽然web.html_container并没有可替代项,第二个t-call却可以为web.external_layout。不同之处在于外部布局已经有显示公司logo、公司名和一些公司对外联系所展示信息的头部和底部,而内部布局仅给出一个带有分页、打印日期和公司名的头部。为保持一致性,请保持使用两者中的一个。
📝重要信息:注意web.internal_layout, web.external_layout, web.external_layout_header和web.external_layout_footer(后两者称为外部布局)本身仅仅是视图,读者已经知道如何通过继承来对它们进行修改。使用inherit_id属性来用template元素进行继承。
通过看板卡片管理活动
Odoo使用活动在调度记录的动作。这些活动可以在表单视图及看板视图中管理。本节中,我们将学习如何在看板视图卡片中管理活动。我们会在借书看板的卡片中添加一个活动微件。
准备工作
本节中,我们将使用前面小节中的my_library模块。
如何实现…
按照如下步骤通过看板视图添加及管理活动:
- 在专用文件中添加对mail的依赖:
1'depends': ['base', 'mail'], - 在library.book.rent 模型中继承activity mixin:
123class LibraryBookRent(models.Model):_name = 'library.book.rent'_inherit = ['mail.thread', 'mail.activity.mixin'] - 在看板视图的color字段下添加activity_state字段:
12<field name="color" /><field name="activity_state"/> - 在看板模板内添加activity_ids字段。以如下代码在popularity字段下添加该字段:
123456<div> <field name="popularity" widget="priority"/></div><div> <field name="activity_ids" widget="kanban_activity"/></div>
更新my_library模块应用修改。打开借书的看板视图,会在看板卡片中看到如下图所示的活动管理器:
在上图中可以看到,在应用了本节的代码后,我们就可以在看板卡片中管理活动了。同时,也可以通过看板卡片处理或创建活动。
运行原理…
在第1步中,我们在模块的声明中添加了一条依赖。背后的原因是所有与活动相关的实现都是mail模块的一部分。不安装mail,我们就无法在模型中使用活动。
第2步中我们对library.book.rent模型添加了activity mixin。这会对借书记录启用活动。添加mail.activity.mixin会添加活动所需的所有字段和方法。我们还添加了mail.thread mixin,因为在用户处理活动时会记录消息。如果希望学习更多有关活动的知识,请参见第二十三章 在Odoo中管理Email中的对活动管理活动一节。
在第3步中,我们对看板视图添加了activity_state字段。该字段由活动微件用于显示颜色微件。颜色代表将要开始活动的当前状态。
第4步中,我们添加了活动微件本身。它使用activity_ids字段。本例中,我们在单独的<div>标签中添加了活动微件,但读者可以根据自己的设计需求将其放到其它地方。使用活动微件,我们可以直接在看板卡片中计划、编辑及处理活动。
扩展知识…
在本章的在看板视图中添加进度条一节中,我们根据popularity字段显示了看板进度条。但还可以根据接下来的活动的状态来显示进度条:
1 2 3 4 |
<progressbar field="activity_state" colors='{"planned": "success", "today": "warning", "overdue": "danger"}'/> |
这会根据未来活动的状态来显示进度条。Odoo的部分视图中使用了这种基于状态的进度条。
其它内容
- 如果希望学习有关邮件线程的更多知识,请参见第二十三章 在Odoo中管理Email中的在文档中管理chatter一节。
- 如果希望学习有关活动的知识,参见第二十三章 在Odoo中管理Email中的对活动管理活动一节。
对表单视图添加统计按钮
Odoo使用统计按钮来在表单视图中在视觉上关联两个不同的对象。用于显示关联记录的一些基础KPI。它还用于跳转和打开其它视图。本节中,我们会在图书的表单视图中添加统计按钮。这一统计按钮会显示借书记录的总数,点击后会跳转到看板视图的列表。
准备工作
本节中,我们将使用前面小节中的my_library模块。
如何实现…
按照如下步骤在图书的表单视图中添加统计按钮:
- 对library.book模型添加rent_count计算字段。该字段会计算图书借出订单的数量:
123456rent_count = fields.Integer(compute="_compute_rent_count")def _compute_rent_count(self):BookRent = self.env['library.book.rent']for book in self:book.rent_count = BookRent.search_count([('book_id', '=', book.id)]) - 在library.book模型的表单视图中添加统计按钮。只需在<sheet>标签内的前面进行添加:
12345<div class="oe_button_box" name="button_box"><button class="oe_stat_button" name="%(library_book_rent_action)d" type="action" icon="fa-book" context="{'search_default_book_id': active_id}"><field string="Rent Orders" name="rent_count" widget="statinfo"/></button></div>
更新my_library模块来应用修改。打开任意一本书的表单视图,就会看到统计按钮,如下图所示:
点击统计按钮,会跳转到看板视图的租借订单。此时在看板视图中仅会看到当前图书的订单。
运行原理…
在第1步中,我们添加了计算当前图书上租借记录数的计算字段。该字段的值将用于在统计按钮中显示计数。如果想要学习有关计算字段的更多知识,请参见第四章 应用模型中的向模型添加计算字段一节。
在第2步中,我们在library.book模型的表单视图中添加了统计按钮。针对统计按钮有一个特定的语法以及位置。所有的统计按钮需要包裹在oe_button_box类的div>中。统计按钮框需要放在<sheet>标签中。注意我们在按钮框中使用了一个name属性。这一name属性在希望添加新统计时会有作用,但需要通过带有oe_stat_button类的<button>标签添加统计按钮。内部统计按钮只是一个具有不同用户界面的表单视图。这表示它支持普通按钮的所有属性,如action, icon和context。
本例中,我们使用了租借订单的动作,即在用户点击统计按钮时,会跳转至借书记录,但它会显示所有的借书记录。我们希望仅显示当前图书的借出记录。这时需要传递search_default_book_id。这会对当前图书应用一个默认过滤器。注意book_id是library.book.rent 模型中的一个many2one字段。如果希望过滤其它字段,通过在上下文中在其前加上search_default_进行使用。
统计按钮经常用到,因为用处很大,会显示记录相关的整体统计数据。可以使用它来显示当前记录相关的所有信息。例如,对联系人记录,Odoo用统计按钮显示当前联系人发票总额、线索总数、订单总数等相关信息。
其它内容
对记录启用存档选项
Odoo提供内置的记录存档和取消存档选项功能。这有助于让用户隐藏掉不再重要的记录。本节中,我们将添加图书的存档/取消存档选项。我可以在图书不可用时存档图书。
准备工作
本节中,我们将使用前面小节中的my_library模块。
如何实现…
存档和取消存档大多自动进行。该选项在模型中有名为active的布尔字段时即可使用。在library.book模型中已经有active字段了。但如尚未添加的话,请按如下步骤添加active字段:
- 在library.book模型中添加active布尔字段如下:
1active = fields.Boolean(default=True) - 在表单视图中添加active字段:
1<field name="active" invisible="1"/>
更新my_library模块来应用修改。此时即可存档图书了。存档的选项位于Action下拉框中,如下图所示:
存档记录后,会希望在Odoo中在任何地方看到该记录。需要在搜索视图中应用过滤来查看它。
运行原理…
名为active的布尔字段在Odoo中有特殊用途。如果在模型中添加active字段,active字段值为false的记录在Odoo中任何地方都不显示。
在第1步中,我们对library.book模型添加了一个active字段。注意我们将默认值保留为True。如果不添加默认值,新记录会默认以存档模式进行创建而不在视图中显示,即便刚刚才进行的创建。
在第2步中,我们在表单视图中添加了active字段。如果没在表单视图中添加active字段,Action下拉菜单中就不会显示存档/取消存档选项。如果不想要在表单视图中显示该字段,可以使用invisible属性在表单视图中进行隐藏。
本例中,在存档了图书后,该书就不会列表或其它视图中显示。甚至在租借订单记录的many2one下拉框中也不会显示。如果希望取消该书的存档,需要在搜索视图中应用过滤器来显示未存档记录,然后恢复该书。
扩展知识…
如果模型中有一个active布尔字段,search方法就不会返回存档的记录。如果希望搜索所有的记录,不论是否存档,那么像下面这样在上下文中传递active_test:
1 |
self.env['library.book'].with_context(active_test=False).search([]) |
注意如果存档记录与其它记录关联,那么会在关联表单视图中显示。例如,Book A有Order A。那么,存档Order A表示此后无法在租借记录中无法选择Book A。但打开Order A时,可以看到存档的Book A。