全书完整目录请见:Odoo 12开发者指南(Cookbook)第三版
本章中,我们将讲解如下小节:
- 管理动态记录阶段
- 管理看板阶段
- 使用看板卡片快速创建表单
- 创建交互看板卡片
- 在看板列上添加进度条
- 根据时间条件使用自动化动作
- 根据事件条件使用自动化动作
- 创建基于QWeb的PDF报告
引言
业务应用不仅应存储记录还应管理业务工作流。有些对象,如线索(leads)或项目任务,有大量并例运行的记录。一个对象有大量记录时会让看清业务变得更为困难。Odoo还很处理这个问题的技巧。本章中,我们来学习如何设置带有动态阶段和看板组的业务工作流。这会有助于用户了解到业务如何运行的概念。
我们还会了解服务端动作和自动化动作等可以助力用户或职能顾问在无需创建自定义插件的情况下添加更简单的流程。最后,我们会创建一个基于QWeb的PDF报表来供打印。
技术准备
本章的技术要求包含Odoo在线平台。
本章中的所有代码可通过GitHub仓库进行下载:https://github.com/PacktPublishing/Odoo-12-Development-Cookbook-Third-Edition/tree/master/Chapter13。
观看如下视频来查看代码实时操作:
管理动态记录阶段
在my_library中,有一个state字段用于表示图书租赁记录的当前状态。state仅限于ongoing或returned的状态,无法在业务流程中加入新状态。为避免这一问题,我们可以使用many2one字段来给予设计用户所需看板工作流足够的灵活性。
准备工作
本节我们将使用第九章 高级服务端开发技巧中的my_library模块。该模块管理图书和它们的分类。它还记录图书的借出。我们添加了一个初始模块,Chapter13/r0_initial_module/my_library,本书的GitHub仓库可以帮助你开始学习:https://github.com/alanhou/odoo12-cookbook。
如何实现…
按照如下简单的步骤来对library.book.rent模块添加阶段:
- 新增名为library.rent.stage的模型如下:
1234567891011class 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>
在安装该模块之后,就可以在表单视图中看到这些阶段了,如下图所示:
TODO
运行原理…
因为我们想要动态管理记录阶段,就需要新建一个模型。在第1步中,我们创建了一个名为library.rent.stage的新模型用于存储动态阶段。在这个模型中,我们添加了一些新字段。其中有sequence字段,用于决定阶段的顺序。我们还添加了布尔字段fold,用于收缩起阶段将它们显示在一个下拉列表中。对于拥有大量阶段的业务流程这会很有帮助,因为它表示我们可以通过设置这个字段来将不重要的阶段隐藏在下拉菜单中。我们还添加了一个book_state字段来进行动态阶段和图书状态之间的映射。来接下来的部分中会使用到它。
fold字段也可用于在看板视图中展示折叠的看板列。通常,Work in Progress项应处于Unfolded阶段,而为Done或Cancelled的终结项应为Folded阶段。
小贴士:默认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字段来建立图书状态与动态阶段的映射。
为在阶段中反映图书状态,我们需要在library.book.rent 模型中重载create和write方法,如下所示:
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')
重启服务并不照样模块来应用修改。这会启用看板,如下图所示:
TODO
运行原理…
在第1步中,我们为 library.book.rent模型添加了一个看板视图。注意我们使用了stage_id作为看板的默认组,因此在用户打开看板时,看板卡片会按阶段进行分组。了解更多有关看板的知识,请参见第十章 后端视图。
在第2步中,我们在已有动作中添加了看板关键字。
在第3步中,我们在stage_id字段中添加了group_expand属性。我们还添加了_group_expand_stages()方法。group_expand修改了字段分组的行为。默认字段分组显示了所使用的阶段。例如,如果没有lost阶段的租借记录,分组就不会返回该阶段,因此看板也不会显示lost列。但在本例中,我们希望不论是是否使用了都显示所有的阶段。
_group_expand_stages()函数用于返回对阶段的所有记录。因此,看板视图会显示所有阶段,并且能通过拖拽它们来使用工作流。
扩展知识…
如果你不停地使用本节中的看板,会发现大量的不同功能。其中一些描述如下:
- 你可以通过点击新增列的选项来新建阶段。group_create选项可用于从看板中禁用添加列的选项。
- 可以通过拖拽列的头部来以不同顺序对列排序。这会更新library.rent.stage模型中的sequence字段。
- 可以通过看板列头部的齿轮图标来编辑或删除列。group_edit和group_delete选项可用于禁用这一功能。
- 阶段的fold字段值为true时会进行折叠并且列会显示为很细的一条。点击它时会扩展并显示看板卡片。
- 如果模型有布尔字段active的话,它会显示选项来在看板列中存档记录或取消存档。可存档选项可用于禁用这个功能。
- 看板列上的加号图标可用于直接从看板视图创建记录。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">
重启服务并更新模块来应用悠。然后点击列上方的加号图标。它会启用如下图所示的看板表单:
TODO
运行原理…
为创建一个自定义快速创建选项,我们需要创建一个最小化表单视图。通过第1步进行了实现。我们添加了两个必填字段,因为创建记录时缺少它们会产生报错。
第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}"/> - 更新看板视图来显示颜色、标签和优先级,如下例所示:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546<kanban default_group_by="stage_id" on_create="quick_create"quick_create_view="my_library.library_book_rent_view_form_minimal"><field name="stage_id" /><field name="color" /><templates><t t-name="kanban-box"><div t-attfclass="#{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><div class="oe_kanban_content"><div class="oe_kanban_card oe_kanban_global_click"><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><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></div></div></div></t></templates></kanban>
ℹ️粗体部分的代码应在已有看板视图中进行添加。
重启服务并更新模块来应用修改。然后,点击列上的加号图标。会显示如下图所示的看板:
TODO
运行原理…
在第2步中,我们为标签新增了模型及安全规则。在第3步中,我们在借阅的模型中新增了一些字段。
在第4步中,我们在表单视图中添加了一些字段。注意我们对popularity字段使用了优先级组件,它将选择字段显示为星星图标。在tag_ids字段中,我们使用了many2many_tags组件,它在标签的表单中显示了many2many字段。传递color_field选项来在标签中启用颜色功能。该选项的值将是颜色索引所存储的字段名。no_create_edit选项会禁用通过表单视图创建新标签的功能。
在第5步中,我们做了很多的改进。在看板卡片中添加了t-attfclass=”#{kanban_color(record.color.raw_value)}。它会被用于显示看板卡片的颜色。它使用颜色字段值并根据该值生成一个类。例如,如果一个看板记录在颜色字段有一个值2,它会在类中添加kanban_color_2。然后我们添加了一个下拉菜单来添加编辑、删除和看板颜色拾取器等选项。编辑和删除选项仅在用户有相应权限时才会显示。
最后,我们在看板卡片中添加了标签和优先级。在进行了这些新增后,看板应该会长成下图这样:
TODO
通过这一卡片设计,我们将能够直接通过看板卡片来设置优先级星标和颜色。
在看板视图上添加进度条
有时,我们在列中有大量的记录并很难对某个阶段获取清晰的显示。进度条可用于显示列的状态。本节中,我们将根据popularity字段来在看板中展示进度条。
准备工作
本节中我们将使用前一节中的my_library模块。
如何实现…
为在看板列中添加进度条,我们将需要在看板视图的定义中添加progressbar标签,如下:
1 2 3 |
<progressbar field="popularity" colors='{"low": "success", "medium": "warning", "high": "danger"}'/> |
注意看板列进度条是在Odoo 11中引入的。在那之前的版本中不会显示列进度条。
重启服务并更新模块来应用修改。然后,点击列中的加号图标。这会在看板列中显示进度条,如下图所示:
TODO
运行原理…
看板列中的进度条根据字段的值来进行显示。进度条支持4种颜色,因此无法支持4种以上的状态。可用的颜色有绿色(成功)、蓝色(信息)、红色(危急)和黄色(警告)。然后我们需要将颜色与字段状态进行映射。本例中,我们映射了优先级字段的三种状态,因此我们不希望没有需求的图书有任何进度条显示。
默认,进度条的旁边会显示记录的条数。可以在进度条上点击它来查看具体状态的总值。点击进度条还会高亮显示该状态的卡片。除了对记录计数外,我们还可以显示整型或浮点型字段的总和。进行这一实现,我们需要添加带有字段值的sum_field属性,如sum_field=”field_name”。
创建服务端动作
服务端动作加持Odoo自动化工具。我们通过它来描述要执行的动作。这些动作随后可由事件触发器调用,或在满足某些时间条件时自动触发。
最简单的用例是让终端用户通过在More按钮中选择来对文档执行某一动作。我们将为项目任务创建这种类型的动作,通过星标当前所选任务来Set as Priority(设置为优先)并设置三天后为截止日期。
准备工作
我将需要一个Odoo实例并安装Project应用。还需要启用开发者模式。如尚未启用,请通过Odoo的Settings界面进行激活。
如何实现…
按照如下步骤来创建服务端动作并在More菜单中使用:
- 在Settings顶级菜单中,选择Technical > Actions > Server Actions菜单项,并在记录列表的顶部点击Create按钮,如下图所示:
TODO - 通过如下值填写服务端动作表单:
- 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
- 第一个值我们将输入如下参数:
下图显示 了所输入的值:
TODO -
- 保存服务端动作并点击左上角的Create contextual action按钮来让其出现在项目任务的More选项中。
- 进入Project顶级菜单,选择Search > Tasks菜单项,并随机打开一个任务。通过点击More按钮,我们应该可以看到下图中显示了Set as Priority选项。选择它会对任务评星并修改截止日期为3天后:
TODO
运行原理…
服务端动作可对模型进行操作,因此首先要做的一件事是选择我们希望操作的Base Model。在本例中,我们使用了项目任务。
接下来,我们应选择所执行动作的类型。有一些可选项:
- 在其它的选项都无法灵活到做我们需要实现的操作时,Execute Python Code允许我们编写要执行的任意代码。
- Create a new Record允许我们在当前模型或另外的模型上新建记录。
- Update the Record允许我们对当前模型或其它模型设置值。
- Send Email允许我们选择一个email模板。它会用作在促发动作时发送邮件。
- Execute several actions可用于触发客户端或窗口动作,和点击菜单项的效果一样。
- Add Followers允许用户或频道订阅记录。
- Create Next Activity允许我们创建新的活动。这会在聊天器(chatter)中显示。
本例中我们使用了Update the Record来设置当前记录的一些值。我们设置了Priority为1来星标任务,并对Deadline字段设置了值。这个更有意思一些,因为所使用的值由Python表达式运算而得。配合中使用了Python中的datetime模块(https://docs.python.org/3/library/datetime.html)来计算三天后的日期。
这里以及其它动作类型中均可使用任意的Python表达式。出于安全原因,代码由odoo/tools/safe_eval.py文件中所实现的safe_eval函数进行检查。这意味着会不允许某些Python运算,但通常这都不构成问题。
扩展知识…
Python代码在一个受限的上下文中运行,可以使用如下的对象:
- env:这是对Environment对象的引用 ,类似类方法中的self.env 。
- model:这是对服务端动作所操作的模型类的引用。例如,它可能等价于self.env[‘project.task]。
- Warning:这是对openerp.exceptions.Warning的引用 ,允许进行阻止不希望动作的验证。它可用于抛出Warning(‘Message!’)。
- record或records:它提供对当前记录的引用,允许我们访问它们的字段值和方法。
- log:它用于在ir.logging model中记录消息,允许数据库端的注册记录动作。
- datetime, dateutil和 time:它们提供对Python库的访问。
使用 Python 代码服务端动作
服务端动作有多种可用类型,但执行自己的Python代码是最灵活的。正确地使用会让具备能力的用户通过用户界面实现高级业务规则,而无需创建具体的插件模块来安装该代码。
我们将展示通过实现向项目任务的follower(订阅者)发送提醒消息的服务端动作来使用这种类型的服务端动作。
准备工作
我们需要一个安装了Project应用的Odoo实例。
如何实现…
按照如下步骤来创建Python代码服务端动作:
- 新建一个服务端动作。在Settings菜单中,选择Technical > Actions > Server Actions菜单项,并点击记录列表顶部的Create按钮。
- 通过如下值填写Server Action 表单:
- Action Name: Send Reminder
- Base Model: Task
- Action To Do: Execute Python Code
- 在Python代码文本域中,删除默认文件并替换为如下代码:
123456789101112if 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',subtype='mt_comment')
下图显示了所输入的值:
TODO - 保存这一服务端动作并点击右上角的Create Contextual Action来让其出现在项目任务的More按钮中。
- 此时,点击Project顶级菜单并选择Search > Tasks菜单项。随机选取一个任务,对其设置截止日期,然后尝试使用More按钮中的Send Reminder选项。
运行原理…
本章中创建服务端动作一节中详细地讲解了如何创建常用的服务端动作。对于这种特定类型的动作,我们需要选择Execute Python Code选项,然后编写代码来运行文本域。
代码可以为多行,本例即为如此,它运行在一个引用了当前记录对象或会话用户等对象的上下文中运行。可以使用的引用在创建服务端动作一节中有进行讲解。
我们所使用的代码计算当前日期到截止日期的天数,并使用它来准备相应的通知消息。最后一行在任务的消息墙中进行了实际的消息发布。邮件通知需要subtype=’mt_comment’来将其发送给订阅者,和我们使用New Message按钮的情况一样。如果未给定子类型,默认使用mt_note,发布内部记录而不进行通过,和使用 Log an internal note按钮的效果一样。参见第二十三章 在Odoo中管理email来学习Odoo中有关mailing更多的知识。
扩展知识…
Python代码服务端动作是非常强大和灵活的方法,但相比自定义插件模块确实存在一些限制。
因为Python代码是在运行时执行,如果发生错误的话,栈追踪所提供的信息有限,调试会更加困难。也无法通过Python中的技术在服务端动作代码中加入断点,因此调试需要使用日志语句来实现。另一个点是,在尝试追踪模块代码中行为的原因时,可以无法找到相关的信息。这时,可能是由服务端动作所导致的。
在执行服务端动作更密集的使用时,交互可能会相当复杂,因此推荐适当规划并保持有效组织。
根据时间条件使用自动化动作
自动化动作可用于自动根据时间条件触发动作。我们可以使用它们来自动对一些满足某些条件或时间条件的记录执行一些操作。
作为示例,我们可以在截止日期的前一天触发一个对项目任务的提醒消息。下面来看如何实现。
准备工作
按照本节,我们将需要同时安装 Project Management应用(技术名称为project)和Automated Action Rules插件(技术名称base_automation),并且应启用开发者模式。我们还需要本章使用 Python 代码服务端动作一节中所创建的服务端动作。
如何实现…
按照如下步骤来创建一个任务中带有定时条件的自动化动作:
- 在Settings菜单中,选择Technical > Automation > Automated Actions菜单项,并点击Create按钮。
- 填写Automated Actions表单中的基本信息:
- Rule Name: Send notification near deadline
- Related Document Model: Task
- 在Trigger Condition字段中选择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)按钮。通过点击记录按钮,我们可以检查满足域表达式记录的记录列表。
- 对Trigger Date设置时间条件,选择要使用的字段,即Deadline,并设置Delay After Trigger Date为-1 Days。
- 在Actions标签栏的Server actions to run下面,点击Add an item并从此前应该已创建的列表选择中 Send Reminder。如未创建,我们仍可以通过Create按钮创建服务端动作来运行,如下图所示:
TODO - 点击Save来保存自动化动作。
- 执行如下步骤来进行测试
- 进入Project菜单,访问Search > Tasks,并通过过去的日期来为任务设置截止日期。
- 进入Settings菜单,点击Technical > Automation > Scheduled Actions菜单项,在列表中找到Base Action Rule: Check and execute动作,打开它的表单视图,并在左上角点击Run Manually按钮。这会强制定时自动化动作当前进行检查 。这在下图中有显示。注意这对新创建的demo数据可以使用,但是在已有数据库中可能无法正常运行:
TODO - 再次进入Project菜单并打开此前设置截止日期的相同任务。查看消息面板,应该可以看到由自动化触发的服务端动作所生成的提醒消息。
运行原理…
自动化动作作用于模型,可由事件或时间条件进行触发。前面几步用于设置Model和When to Run的值。
两种方法可使用过滤器来过滤出仅用于执行动作的记录。我们可以使用域表达式来进行实现。可以在第十章 后端视图中找到有关编写域表达式的更进一步信息。此外,我们可以使用用户界面功能对项目任务创建并保存过滤器,然后复制自动生成的域表达式,从Set selection based on a search filter列表中进行选择。
我们使用的域表达式选择所有在未勾选Fold标记的阶段中的带有非空截止日期。没有Fold标记的阶段被视作进行中。这样,我们避免了对处于Done, Canceled或Closed阶段的任务触发通知。
然后我们应定义了时间条件 – 在应触发动作时所使用的日期字段。时间区间可以为分钟、小时、天或月,并且区间值对于日期后的时间可以为正值,或者在日期间的时间可以为负值。在以天来使用时间区间时,我们可以提供一个定义工作日的Resource Calendar并可用于以天计数。
这些动作由Check Action Rules计划任务来检查。注意默认这是每4小时运行一次的。这对以天或月为单位的动作会合适,但如果你需要对更小的时间刻度进行动作的操作,则需要将运行的间隔调整为更小的值。
满足所有条件或最后一次动作执行之后的触发日期条件(字段日期加间隔)的记录的动作会被触发。这是为避免重复触发相同的动作。同时,这也是手动运行之前的动作可以在计划动作尚未触发的数据库中生效的原因,而可能不会在已由调试器运行的数据库中立即生效。
一旦触发了自动化动作,Actions标签会告诉你应发生什么。这可能是一个执行像修改记录值、发布消息或发送邮件这样的服务端动作。
扩展知识…
这些类型的自动化动作在满足某一时间条件时触发。它不是在条件仍为true时反复重复的动作。例如,自动化动作不能够在超过截止日期之后每天发布提醒。
而这种类型的动作可由存储于ir.cron模型中的Scheduled Actions执行。但是计划动作不支持服务端动作,它们仅能调用模型对象中的已有方法。因此,要实现一个自定义动作,我们需要写一个插件模块,添加所需的Python方法。
作为参考 ,Automated Actions模型技术名称为base.action.rule。
根据事件条件使用自动化动作
业务应用提供带有针对业务操作记录的系统,但也支持具体针对某一组织使用用例的动态业务规则。
把这些规则固化到插件模块中会很不灵活并且不在功能性用户的使用范围内。由事件条件触发的自动化动作可以很好的弥补这一问题并且提供自动化或强化组织流程的强大工具。作为示例,我们将强化对项目任务的认证,这样仅有项目经理能修改任务为Done阶段。
准备工作
学习本节,需要先安装项目管理应用。我们还需要启用开发者模式。如尚未启用,在Odoo的About对话框中进行激活。
如何实现…
按照如下步骤来对任务创建带有事件条件的自动化动作:
- 在Settings菜单中,选择Technical > Automation > Automated Actions菜单项,并点击Create按钮。
- 填写自动化动作表单中的基本信息:
- Rule Name: Validate Closing Tasks
- Related Document Model: Task
- Conditions标签栏 > When to Run: On Update
- Action To Do: Execute several actions
- on update规则允许我们在更新操作之前和之后设置两个记录过滤器:
- 在 Before Update Filter字段中,点击Edit domain按钮,在代码编辑器中设置一个有效的域表达式 – [(‘stage_id.name’, ‘!=’, ‘Done’)],并保存。
- 在Apply on字段中,点击Edit domain按钮,在代码编辑器中设置 [(‘stage_id.name’, ‘=’, ‘Done’)] 域并保存,如下图所示:
TODO
- 在Actions标签栏中点击Add an item。然后在列表对话框中点击Create按钮来新建一个服务端动作。
- 通过如下规则填写服务端动作表单,然后点击Save按钮:
- Action Name: Validate closing tasks
- Base 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') - 所输入的值如下图所示:
TODO
- 点击Save & Close来保存自动化动作并进行测试:
- 使用带有演示数据的数据库并以Administrator登录,进入Project菜单并点击E-Learning Integration项目来打开任务的看板视图。
- 然后,尝试拖拽一个任务到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_loan_report.xml文件的report标签中使用这一视图,如下例所示:
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表单的时候,或者在表单视图中选择成员时,会在下单菜单中出现打印图书借阅的选择,如下图所示:
TODO
运行原理…
第1步中,我们使用QWeb模板语言定义了报表的结构。我们没有像之前那样使用record语法,而是在这里使用了template元素。这纯粹是为了方便,QWeb报表和其它视图并没什么不同。使用这一元素的原因在于QWeb的arch字段需要遵循非常严格的规则,而template元素生成一个满足这些规则的arch内容。
现在不必担心template元素中的语法。这个话题会在第十六章 网页客户端开发中的使用客户端QWeb模板一节是进行深入讲解。
在第2步中,我们在另一个XML文件中声明了报表 。report元素是是另一个针对ir.actions.report.xml类型的动作的快捷方式。这里最关键部分是设置name字段来完成所定义模板的XML ID(即modulename.record_id),否则报表的生成会失败。model属性决定报表操作的是哪种记录类型,而string属性是在打印菜单中对用户显示的名称。
通过设置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(后两者称为外部布局)本身仅仅是视图,读者已经知道如何通过继承来对它们进行修改。要通过template元素进行继承,使用inherit_id属性。
小贴士:在此前版本的Odoo中,这些模型在report而非web模块中进行定义。在将模块移植到Odoo 11时,请不要忘记将调用中的标识符修改为t-call。