上下文计算

Odoo 计算字段为什么要把 company 放进 depends_context

计算字段不只取决于数据库值,也取决于上下文。公司、日期和货币一变,ORM 就需要知道该重新算哪一份结果。

Odoo 开发 框架
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 7 阅读

计算字段最容易被误解的一点,就是把它当成“函数返回值”。

实际上在 Odoo 里,计算字段是环境感知的:

  • 数据库里变了,它可能要重算;
  • 上下文变了,它也可能要重算;
  • 当前公司变了,结果甚至可能完全不同。

所以如果你看到 @api.depends_context('company'),它不是装饰器炫技,而是在告诉 ORM:

这个值不只跟字段有关,还跟上下文有关。

一、res_currency 是最直观的例子

res_currency.py 里,_compute_is_current_company_currency() 直接依赖 company 上下文:

  • 当前公司是谁,会影响“这是不是本公司币种”;
  • company_iddateto_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.pyodoo/orm/models.py 里,recompute()_recompute_field()_recompute_recordset() 共同完成了“先标记、再补算、再落地”的流程。

换句话说:

计算字段不是算一次就完事,而是要在正确的上下文里被正确地补算。

四、最常见的坑:把上下文依赖藏起来

如果一个字段的值明显跟公司、语言、日期、价格表有关,但代码里只写了普通 @api.depends,那就很危险。

你会遇到这类问题:

  • 换公司后结果还是上一家公司缓存的值;
  • 同一记录在不同日期下展示相同汇率;
  • 看起来“有时候对,有时候不对”,其实是缓存命中了错误上下文。

这类 bug 最烦的地方就在于:

它不是每次都错,而是只在上下文切换时错。

五、什么时候该用,什么时候别乱用

适合用 depends_context 的场景

  • 值依赖当前公司、日期、价格表、语言;
  • 同一条记录在不同上下文下必须返回不同结果;
  • 你明确知道“上下文就是业务的一部分”。

不要滥用的场景

  • 只是想“让它重新算一下”,却不知道依赖什么;
  • 其实应该改成存储字段、关联字段或独立模型;
  • 把隐藏的业务规则塞进 compute,结果让缓存和排错一起受伤。

经验上,凡是结果会随上下文变化的字段,都应该先问一句:

这是不是应该显式告诉 ORM?

结论

depends_context 的意义很简单:它让 ORM 知道“上下文也是依赖项”。

对公司相关的计算尤其如此:

  • 汇率会变;
  • 税号标签会变;
  • 甚至同一字段在不同公司下的“正确答案”都会变。

只要你把 company、date、currency 这些上下文当成一等公民,很多计算字段问题就会少很多。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。