原文:Odoo 18 官方开发教程“Server framework 101”第 8 章。本文尽量保留原文结构、代码、练习与图片,仅进行中文翻译,并在文末增加“老赵解读”。
第 8 章:计算的字段和 Onchanges
模型之间的关系 是
任何 Odoo 模块。它们对于任何业务案例的建模都是必要的。然而,我们可能想要
给定模型内的字段之间的链接。有时,字段的值由以下确定:
其他字段的值以及其他时候我们希望帮助用户进行数据输入的值。
这些情况由计算的字段和 onchanges 的概念支持。虽然这一章是
技术上并不复杂,这两个概念的语义非常重要。
这也是我们第一次编写Python逻辑。到目前为止我们还没有写任何东西
除了类定义和字段声明之外。
计算字段
参考:与该主题相关的文档可以在以下位置找到
计算字段。
说明
目标: 在本节末尾:
- 在房产模型中,应计算总面积和最佳报价:
- 在房产报价模型中,应计算并更新有效日期:
在我们的房地产模块中,我们定义了居住区和花园区。正是那时
自然将总面积定义为两者字段的总和。我们将使用计算的概念
字段为此,即给定字段的值将根据其他字段的值计算。
到目前为止,字段已直接存储在
数据库。字段也可以 计算的。在这种情况下,字段的值不是
从数据库中检索,但通过调用 的方法即时计算
模型。
要创建计算的字段,请创建字段并设置其属性
compute 到方法的名称。计算
方法应该为每个记录设置计算出的字段的值
self。
按照惯例, compute 方法是私有的,这意味着它们不能
从表示层调用,只能从业务层调用(请参阅
第 1 章:架构概述)。私有方法的名称以
下划线 _。
依赖关系
计算出的字段的值通常取决于其他字段的值
计算出的记录。 ORM 希望开发人员指定这些依赖项
使用装饰器 depends() 的计算方法。
ORM 使用给定的依赖关系来触发重新计算
字段每当它的一些依赖项被修改时:
from odoo import api, fields, models
class TestComputed(models.Model):
_name = "test.computed"
total = fields.Float(compute="_compute_total")
amount = fields.Float()
@api.depends("amount")
def _compute_total(self):
for record in self:
record.total = 2.0 * record.amount
说明
self是一个集合。对象
self是一个 记录集,即有序集合
记录。它支持集合上的标准 Python 操作,例如
len(self)和iter(self),以及额外的设置操作,例如recs1 | recs2。迭代
self会一一给出记录,其中每个记录是
本身是一个大小为 1 的集合。您可以在单个上访问/分配字段
记录使用点符号,例如record.name。
在 Odoo 中可以找到许多计算字段的示例。
这里
是一个简单的。
练习
计算总面积。
- 将
total_area字段添加到estate.property。它被定义为
living_area和garden_area。- 在表单视图中添加字段,如本节第一张图片所示 目标.
对于关系字段,可以使用通过字段的路径作为依赖项:
description = fields.Char(compute="_compute_description")
partner_id = fields.Many2one("res.partner")
@api.depends("partner_id.name")
def _compute_description(self):
for record in self:
record.description = "Test for partner %s" % record.partner_id.name
该示例给出了 Many2one,但它适用于
Many2many 或 One2many。可以找到一个例子
这里。
让我们通过以下练习在我们的模块中尝试一下!
练习
计算最佳报价。
- 将
best_price字段添加到estate.property。它被定义为最高(即最大值)
报价’price。- 将字段添加到表单视图,如本节的第一张图片所示 目标.
提示:您可能想尝试使用
mapped()方法。参见
这里
举一个简单的例子。
反函数
您可能已经注意到,计算出的字段默认情况下是只读的。这是预计自
用户不应该设置一个值。
在某些情况下,仍然能够直接设置值可能很有用。在我们的房地产例子中,
我们可以定义报价的有效期并设置有效日期。我们希望能够
设置持续时间或日期,其中一个影响另一个。
为了支持此功能,Odoo 提供了使用 inverse 函数的功能:
from odoo import api, fields, models
class TestComputed(models.Model):
_name = "test.computed"
total = fields.Float(compute="_compute_total", inverse="_inverse_total")
amount = fields.Float()
@api.depends("amount")
def _compute_total(self):
for record in self:
record.total = 2.0 * record.amount
def _inverse_total(self):
for record in self:
record.amount = record.total / 2.0
可以找到一个例子
这里。
计算方法设置字段,而逆方法设置字段
依赖关系。
注意,保存记录时会调用 inverse方法,而
每次更改其依赖项时都会调用 compute 方法。
练习
计算报价的有效日期。
- 将以下字段添加到
estate.property.offer模型:
字段 类型 默认 有效性 Integer 7 日期_截止日期 Date 其中
date_deadline是计算出的字段,它定义为两个字段的总和
报价:create_date和validity。定义适当的反函数
以便用户可以设置日期或有效性。提示:
create_date仅在创建记录时填写,因此您将
需要后备以防止创建时崩溃。
- 在表单视图和列表视图中添加字段,如本节第二张图片所示 目标.
附加信息
计算出的字段为 未存储 默认在数据库中。因此它是 不
可能 除非定义了 search 方法,否则搜索计算的字段。这个话题超出了范围
本次培训的内容,因此我们不会介绍它。可以找到一个例子
这里。
另一种解决方案是使用 store=True 属性存储字段。虽然这是
通常很方便,请注意添加到您的模型中的潜在计算负载。让我们重复使用
我们的例子:
description = fields.Char(compute="_compute_description", store=True)
partner_id = fields.Many2one("res.partner")
@api.depends("partner_id.name")
def _compute_description(self):
for record in self:
record.description = "Test for partner %s" % record.partner_id.name
每次更改合作伙伴 name 时,都会自动重新计算 description
所有记录 指的是它!当以下情况时,这很快就会变得无法重新计算:
数以百万计的记录需要重新计算。
还值得注意的是,计算出的字段可以依赖于另一个计算出的字段。 ORM 是
足够聪明,能够以正确的顺序正确地重新计算所有依赖项……但有时在
性能下降的成本。
一般来说,在定义计算的字段时必须始终牢记性能。越多
复杂的是你的字段来计算(例如,有很多依赖项或者当计算出的字段
取决于其他计算的字段),计算所需的时间越多。总是需要一些时间
预先评估计算出的字段的成本。大多数时候,只有当你的代码
到达生产服务器时,您意识到它会减慢整个过程。不酷:-(
变化
参考:与该主题相关的文档可以在以下位置找到
onchange():
说明
目标:在本节末尾,启用花园将设置默认面积 10 和
向北的方向。
在我们的房地产模块中,我们还希望帮助用户进行数据输入。当“花园”
字段已设置,我们要为花园面积和方向提供默认值。
此外,当‘花园’字段未设置时,我们希望花园面积重置为零,并且
要删除的方向。在这种情况下,给定字段的值会修改
其他字段。
“onchange”机制为客户端界面提供了一种更新
每当用户填写表单时,都不将任何内容保存到数据库中
字段值。为了实现这一点,我们定义了一个方法,其中 self 表示
表单视图中的记录并用 onchange() 装饰它
指定它由哪个字段触发。您所做的任何更改
self 将反映在表格上:
from odoo import api, fields, models
class TestOnchange(models.Model):
_name = "test.onchange"
name = fields.Char(string="Name")
description = fields.Char(string="Description")
partner_id = fields.Many2one("res.partner", string="Partner")
@api.onchange("partner_id")
def _onchange_partner_id(self):
self.name = "Document for %s" % (self.partner_id.name)
self.description = "Default description for %s" % (self.partner_id.name)
在此示例中,更改合作伙伴也会更改名称和描述值。取决于
用户随后是否更改名称和描述值。另请注意,我们不
在 self 上循环,这是因为该方法仅在表单视图中触发,其中 self 始终为
单个记录。
练习
设置花园面积和方向的值。
在
estate.property模型中创建onchange以便设置值
当花园设置为 True 时,花园区域 (10) 和方向(北)。取消设置时,清除字段。
附加信息
Onchanges 方法还可以返回非阻塞警告消息
(例子)。
如何使用它们?
对于计算的字段和 onchanges 的使用没有严格的规则。
在许多情况下,计算的字段和 onchanges 都可以用来实现相同的结果。总是
更喜欢计算的字段因为它们也会在表单视图的上下文之外触发。从来没有
曾经使用 onchange 将业务逻辑添加到您的模型中。这是一个 非常糟糕 从那时起的想法
以编程方式创建记录时不会自动触发 onchanges;他们只是
在表单视图中触发。
计算字段和 onchanges 的常见陷阱是通过添加太多内容来试图变得“太聪明”
逻辑。这可能会产生与预期相反的结果:最终用户感到困惑
所有自动化。
计算出的字段往往更容易调试:这样的字段是通过给定方法设置的,因此很容易
跟踪该值何时设置。另一方面,Onchanges 可能会令人困惑:很难
了解 onchange 的范围。由于多个 onchange 方法可能会设置相同的字段,因此
跟踪值的来源很容易变得困难。
当使用存储计算的字段时,要密切注意依赖关系。计算时字段
依赖于其他计算的字段,改变一个值会触发大量的重新计算。
这会导致性能不佳。
在 下一章 中,我们将看到如何触发一些
单击按钮时的业务逻辑。
老赵解读
计算字段适合表达可由其他数据确定的结果,onchange 适合改善表单录入体验。二者最大的区别是:计算逻辑属于服务端业务模型,而 onchange 只在表单交互时触发,不能作为数据正确性的唯一保障。
凡是通过导入、RPC、自动任务或其他模块也必须成立的规则,都应放在模型层。onchange 可以给出方便的默认结果,但关键业务结果必须由服务端再次计算或校验。