计算字段导致无限重新计算 (odoo)

发布: (2026年1月7日 GMT+8 16:41)
5 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的完整文本内容,我将按照要求将其翻译为简体中文并保留原始的格式、Markdown 语法以及技术术语。谢谢!

问题陈述

设计不良的计算字段会因错误的依赖声明(@api.depends)触发不必要的重新计算或无限循环,导致性能问题和行为不稳定。

第 1 步 – 确认有问题的计算字段

常见症状

  • 表单视图永远加载不完
  • 打开记录时 CPU 飙升
  • 日志显示重复的重新计算
  • 记录创建/更新后服务器冻结

启用日志

--log-level=debug

查找重复的消息

Computing field x_field_name

第 2 步 – 识别常见的 错误 模式 (大多数 bug 都来自这里)

1. 计算字段依赖自身

@api.depends('total_amount')
def _compute_total_amount(self):
    for rec in self:
        rec.total_amount = rec.price * rec.qty

结果: 无限递归计算。

2. 在 compute 中写入其他字段

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty
        rec.subtotal = rec.total * 0.9  # BAD

3. 在 compute 中使用 write()

@api.depends('line_ids.amount')
def _compute_amount(self):
    for rec in self:
        rec.write({'total': sum(rec.line_ids.mapped('amount'))})

结果: 递归重新计算 + 数据库写入。

第3步 – 仅定义 正确 的依赖

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty

规则

  • 仅依赖源字段。
  • 切勿依赖自身的计算字段。
  • 避免使用 @api.depends('*')

第4步 – 在 compute 中永不修改其他字段

分离计算字段

total    = fields.Float(compute='_compute_total',    store=True)
subtotal = fields.Float(compute='_compute_subtotal', store=True)

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty

@api.depends('total')
def _compute_subtotal(self):
    for rec in self:
        rec.subtotal = rec.total * 0.9

好处: 清晰、可预测、无循环。

第5步 – 仅在必要时使用 store=True

不推荐(不必要的存储)

total = fields.Float(compute='_compute_total', store=True)

仅当字段满足以下条件时才使用 store=True

  • 经常被搜索
  • 用于分组或报表

推荐(临时 UI 值)

total = fields.Float(compute='_compute_total')

store=True 会增加重新计算的成本。

第6步 – 避免在 compute 中进行 ORM 写入(改用 @api.onchange

错误(compute)

@api.depends('qty')
def _compute_price(self):
    for rec in self:
        rec.price = rec.qty * 10

正确(onchange)

@api.onchange('qty')
def _onchange_qty(self):
    self.price = self.qty * 10

使用 onchange 处理仅 UI 的逻辑——避免重新计算循环。

第7步 – 在需要时使用 @api.depends_context

如果计算依赖于公司、语言或用户:

@api.depends_context('company')
@api.depends('amount')
def _compute_tax(self):
    for rec in self:
        rec.tax = rec.amount * rec.company_id.tax_rate

防止不必要的重新计算。

第8步 – 修复 One2many / Many2one 循环

常见循环

@api.depends('line_ids.total')
def _compute_total(self):
    for rec in self:
        rec.total = sum(rec.line_ids.mapped('total'))

如果 line.total 再次依赖父记录 → 会导致无限循环。

安全版本

@api.depends('line_ids.price', 'line_ids.qty')
def _compute_total(self):
    for rec in self:
        rec.total = sum(line.price * line.qty for line in rec.line_ids)

第9步 – 使用 SQL 约束而不是 compute 进行验证

错误示例(使用 compute 进行验证)

@api.depends('qty')
def _compute_check(self):
    if self.qty < 0:
        raise ValidationError("Invalid")

正确示例(约束)

@api.constrains('qty')
def _check_qty(self):
    for rec in self:
        if rec.qty < 0:
            raise ValidationError("Quantity cannot be negative")

第10步 – 最终安全的计算字段模板(最佳实践)

total = fields.Float(
    compute='_compute_total',
    store=True,
)

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty if rec.price and rec.qty else 0.0

关键点

  • 计算方法内部不进行写入。
  • 不使用递归。
  • 最小且正确的依赖。
  • 适用于生产环境。

结论

在 Odoo 中,无限重新计算问题几乎总是由不纯的计算字段引起的,这些字段会写入数据、依赖自身或依赖定义不明确。解决办法简单但严格:

  1. 保持计算方法
  2. 声明 最小准确 的依赖。
  3. 避免在计算中进行 ORM 写入;改用 @api.onchange 或约束。
  4. 将仅用于 UI 的逻辑与存储逻辑分离。

遵循这些规则可以消除循环,提升性能,使您的 Odoo 模块保持稳定且易于维护。

Calculations instead of business logic containers, performance stabilizes, recomputation stops, and your Odoo system becomes predictable and scalable.
Back to Blog

相关文章

阅读更多 »