计算字段最容易被误解的一点,就是把它当成“函数返回值”。
实际上在 Odoo 里,计算字段是环境感知的:
- 数据库里变了,它可能要重算;
- 上下文变了,它也可能要重算;
- 当前公司变了,结果甚至可能完全不同。
所以如果你看到 @api.depends_context('company'),它不是装饰器炫技,而是在告诉 ORM:
这个值不只跟字段有关,还跟上下文有关。
一、res_currency 是最直观的例子
在 res_currency.py 里,_compute_is_current_company_currency() 直接依赖 company 上下文:
- 当前公司是谁,会影响“这是不是本公司币种”;
company_id、date、to_currency会影响当前汇率的计算结果。
这也是为什么 _compute_current_rate() 同时写了:
@api.depends('rate_ids.rate')@api.depends_context('to_currency', 'date', 'company', 'company_id')
这里的意思很清楚:
数据变了要算;上下文变了也要算。
如果没有这个声明,缓存就可能拿错另一家公司、另一日期、甚至另一目标货币的结果。
二、res_partner 里也有类似需求
res_partner._compute_vat_label() 的实现很简单:它读取当前公司的国家,然后决定显示 VAT 还是别的税号标签。
这看起来像小事,但它其实说明了一个重要原则:
- 同一条记录;
- 在不同公司上下文里;
- 展示出来的“正确值”可能不同。
所以公司上下文不是“可有可无的附加信息”,而是这个字段值的一部分。
三、depends_context 影响的是缓存和重算边界
很多人以为 depends_context 是给计算函数增加一个参数,其实不是。
它更像是在告诉 ORM:
- 哪些上下文值会改变结果;
- 缓存键要把哪些上下文纳入考虑;
- 什么时候旧结果不能再复用。
在 odoo/orm/fields.py 和 odoo/orm/models.py 里,recompute()、_recompute_field()、_recompute_recordset() 共同完成了“先标记、再补算、再落地”的流程。
换句话说:
计算字段不是算一次就完事,而是要在正确的上下文里被正确地补算。
四、最常见的坑:把上下文依赖藏起来
如果一个字段的值明显跟公司、语言、日期、价格表有关,但代码里只写了普通 @api.depends,那就很危险。
你会遇到这类问题:
- 换公司后结果还是上一家公司缓存的值;
- 同一记录在不同日期下展示相同汇率;
- 看起来“有时候对,有时候不对”,其实是缓存命中了错误上下文。
这类 bug 最烦的地方就在于:
它不是每次都错,而是只在上下文切换时错。
五、什么时候该用,什么时候别乱用
适合用 depends_context 的场景
- 值依赖当前公司、日期、价格表、语言;
- 同一条记录在不同上下文下必须返回不同结果;
- 你明确知道“上下文就是业务的一部分”。
不要滥用的场景
- 只是想“让它重新算一下”,却不知道依赖什么;
- 其实应该改成存储字段、关联字段或独立模型;
- 把隐藏的业务规则塞进 compute,结果让缓存和排错一起受伤。
经验上,凡是结果会随上下文变化的字段,都应该先问一句:
这是不是应该显式告诉 ORM?
结论
depends_context 的意义很简单:它让 ORM 知道“上下文也是依赖项”。
对公司相关的计算尤其如此:
- 汇率会变;
- 税号标签会变;
- 甚至同一字段在不同公司下的“正确答案”都会变。
只要你把 company、date、currency 这些上下文当成一等公民,很多计算字段问题就会少很多。
DISCUSSION
评论区