计算字段导致无限重新计算 (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 中,无限重新计算问题几乎总是由不纯的计算字段引起的,这些字段会写入数据、依赖自身或依赖定义不明确。解决办法简单但严格:
- 保持计算方法 纯。
- 声明 最小 且 准确 的依赖。
- 避免在计算中进行 ORM 写入;改用
@api.onchange或约束。 - 将仅用于 UI 的逻辑与存储逻辑分离。
遵循这些规则可以消除循环,提升性能,使您的 Odoo 模块保持稳定且易于维护。
Calculations instead of business logic containers, performance stabilizes, recomputation stops, and your Odoo system becomes predictable and scalable.