第 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 to estate.property. It is defined as the sum of the living_area and 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,但它对于 Many2manyOne2many 有效。可以找到一个示例 here

让我们通过以下练习在我们的模块中尝试一下!

Exercise

计算最佳报价。

  • 添加“best_price` field to estate.property. It is defined as the highest (i.e. maximum) of the offers’ ``price`”。

  • 将字段添加到表单视图,如本节**目标**的第一张图片所示。

提示:您可能想尝试使用 mapped() 方法。请参阅 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 中,我们将了解如何在单击按钮时触发一些业务逻辑。