第 8 章:计算字段和 Onchanges¶
relations between models 是任何 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`` is a recordset, i.e. an ordered collection of records. It supports the standard Python operations on collections, e.g. len(self) and iter(self), plus extra set operations such as recs1 | recs2。
迭代“self` gives the records one by one, where each record is itself a collection of size 1. You can access/assign fields on single records by using the dot notation, e.g. ``record.name`”。
Odoo 中可以找到许多计算字段的示例。 Here 是一个简单的。
Exercise
计算总面积。
添加“
total_area`field toestate.property. It is defined as the sum of theliving_areaand the ``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 有效。可以找到一个示例 here。
让我们通过以下练习在我们的模块中尝试一下!
反函数¶
您可能已经注意到,计算字段默认是只读的。这是预期的,因为用户不应该设置值。
在某些情况下,仍然能够直接设置值可能很有用。在我们的房地产示例中,我们可以定义报价的有效期并设置有效期。我们希望能够设置持续时间或日期,其中一个会影响另一个。
为了支持此功能,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
可以找到一个示例 here。
计算方法设置字段,而逆方法设置字段的依赖关系。
请注意,每次更改其依赖项时都会调用“inverse` method is called when saving the record, while the ``compute`”方法。
Exercise
计算报价的有效期。
将以下字段添加到“
estate.property.offer”模型中:
场地 |
类型 |
默认 |
|---|---|---|
有效性 |
整数 |
7 |
日期_截止日期 |
日期 |
其中``date_deadline`` is a computed field which is defined as the sum of two fields from the offer: the create_date and the validity。定义适当的反函数,以便用户可以设置日期或有效性。
提示:“create_date”仅在创建记录时填写,因此您需要后备以防止创建时崩溃。
在表单视图和列表视图中添加字段,如本部分**目标**的第二张图片所示。
附加信息¶
默认情况下,计算字段**不存储**在数据库中。因此,**不可能**在计算字段上进行搜索,除非出现 search method is defined. This topic is beyond the scope of this training, so we won’t cover it. An example can be found here。
另一种解决方案是使用“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`` is changed, the ``description``都会自动重新计算**所有记录**引用它!当数百万条记录需要重新计算时,这很快就会导致重新计算变得令人望而却步。
还值得注意的是,一个计算域可以依赖于另一个计算域。 ORM 足够智能,可以按照正确的顺序正确地重新计算所有依赖项……但有时会以性能下降为代价。
一般来说,定义计算字段时必须始终牢记性能。您的计算字段越复杂(例如,具有大量依赖项或计算字段依赖于其他计算字段时),计算所需的时间就越多。务必事先花一些时间评估计算字段的成本。大多数时候,只有当您的代码到达生产服务器时,您才会意识到它会减慢整个过程。不酷:-(
变化¶
参考:与此主题相关的文档可以在 onchange() 中找到:
注解
目标:在本节末尾,启用花园将设置默认面积 10 和朝北的方向。
在我们的房地产模块中,我们还希望帮助用户进行数据输入。设置“花园”字段后,我们要为花园区域和方向提供默认值。此外,当“花园”字段未设置时,我们希望花园区域重置为零并删除方向。在这种情况下,给定字段的值会修改其他字段的值。
“onchange”机制为客户端界面提供了一种更新表单的方法,而无需在用户填写字段值时将任何内容保存到数据库中。为了实现这一点,我们定义了一个方法,其中 self represents the record in the form view and decorate it with onchange() to specify which field it is triggered by. Any change you make on 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`, this is because the method is only triggered in a form view, where ``self`”上循环始终是单个记录。
Exercise
设置花园面积和方向的值。
创建一个 onchange in the estate.property 模型,以便在花园设置为 True 时设置花园面积 (10) 和方向(北)的值。取消设置后,清除字段。
附加信息¶
Onchanges 方法还可以返回非阻塞警告消息 (example)。
如何使用它们?¶
对于计算字段和 onchanges 的使用没有严格的规则。
在许多情况下,计算字段和 onchanges 都可以用来实现相同的结果。始终更喜欢计算字段,因为它们也会在表单视图上下文之外触发。切勿使用 onchange 将业务逻辑添加到模型中。这是一个 非常糟糕 的想法,因为以编程方式创建记录时不会自动触发 onchanges;它们仅在表单视图中触发。
计算字段和 onchanges 的常见陷阱是通过添加太多逻辑来试图变得“太聪明”。这可能会产生与预期相反的结果:最终用户对所有自动化感到困惑。
计算字段往往更容易调试:这样的字段是通过给定方法设置的,因此很容易跟踪值的设置时间。另一方面,onchanges 可能会令人困惑:很难知道 onchange 的范围。由于多个 onchange 方法可能会设置相同的字段,因此跟踪值的来源很容易变得困难。
使用存储的计算字段时,请密切注意依赖性。当计算字段依赖于其他计算字段时,更改值可能会触发大量重新计算。这会导致性能不佳。
在 next chapter 中,我们将了解如何在单击按钮时触发一些业务逻辑。