跳至内容

老赵解读Odoo 18开发教程08:计算字段与Onchange

2026年8月15日
老赵解读Odoo 18开发教程08:计算字段与Onchange

原文:Odoo 18 官方开发教程“Server framework 101”第 8 章。本文尽量保留原文结构、代码、练习与图片,仅进行中文翻译,并在文末增加“老赵解读”。

第 8 章:计算的字段和 Onchanges


模型之间的关系
任何 Odoo 模块。它们对于任何业务案例的建模都是必要的。然而,我们可能想要
给定模型内的字段之间的链接。有时,字段的值由以下确定:
其他字段的值以及其他时候我们希望帮助用户进行数据输入的值。

这些情况由计算的字段和 onchanges 的概念支持。虽然这一章是
技术上并不复杂,这两个概念的语义非常重要。
这也是我们第一次编写Python逻辑。到目前为止我们还没有写任何东西
除了类定义和字段声明之外。

计算字段

参考:与该主题相关的文档可以在以下位置找到
计算字段

说明

目标: 在本节末尾:

  • 在房产模型中,应计算总面积和最佳报价:

Compute fields

  • 在房产报价模型中,应计算并更新有效日期:

Compute field with inverse

在我们的房地产模块中,我们定义了居住区和花园区。正是那时
自然将总面积定义为两者字段的总和。我们将使用计算的概念
字段为此,即给定字段的值将根据其他字段的值计算。

到目前为止,字段已直接存储在
数据库。字段也可以 计算的。在这种情况下,字段的值不是
从数据库中检索,但通过调用 的方法即时计算
模型。

要创建计算的字段,请创建字段并设置其属性
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_areagarden_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。可以找到一个例子
这里

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

练习

计算最佳报价。

  • 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_datevalidity。定义适当的反函数
以便用户可以设置日期或有效性。

提示: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

在我们的房地产模块中,我们还希望帮助用户进行数据输入。当“花园”
字段已设置,我们要为花园面积和方向提供默认值。
此外,当‘花园’字段未设置时,我们希望花园面积重置为零,并且
要删除的方向。在这种情况下,给定字段的值会修改
其他字段。

“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 可以给出方便的默认结果,但关键业务结果必须由服务端再次计算或校验。

老赵解读Odoo 18开发教程07:模型关系