协同办公

Odoo Chatter 字段追踪为什么不是简单 diff:tracking、subtype 与日志消息生成链路讲透

Odoo 里字段开启 tracking 后,并不是在 write() 之后直接拼一段变更文案。源码先在 precommit 保存旧值,再做 _message_track 对比,接着由 _track_subtype 决定是否升级成带 subtype 的消息,最后才在 Chatter 中生成可展示日志。这条链路决定了字段追踪到底是“静态差异”还是“有业务语义的协作消息”。

Odoo 开发 协同办公
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

很多人把 Odoo 的字段追踪理解成:字段一变,系统比一下旧值新值,然后在 Chatter 里写一句“XXX 从 A 改成 B”。

mail.thread 源码做的事情复杂得多。真实链路是:

  1. _track_prepare() 先把旧值塞进 precommit 缓存;
  2. _track_finalize() 在提交前统一触发 _message_track() 做差异计算;
  3. _track_subtype() 决定这次变化是否属于某个业务 subtype;
  4. 最终再用 message_post() 生成一条可展示、可订阅、可通知的 Chatter 消息。

所以一句话概括:

Odoo 的 tracking 不是单纯 diff,而是把字段变化包装成一条带协作语义的消息生成流程。


这篇主要看哪里

核心源码在:

  • addons/mail/models/mail_thread.py

重点方法:

  • _track_prepare()
  • _track_finalize()
  • _message_track()
  • _track_subtype()
  • _track_get_default_log_message()
  • message_post()

第一层:为什么要先 _track_prepare()

字段一旦写入,旧值就没了。_track_prepare() 的职责就是在真正写操作完成前,把需要追踪的字段旧值保存到 self.env.cr.precommit.data

它只处理 _track_get_fields() 返回的可追踪字段,而且对 properties 字段还会做特殊转换,确保最终拿到的是可以比较、可以展示的旧值。

这说明 Odoo 很清楚 tracking 的核心难点不是“展示新值”,而是在事务内正确保留旧值上下文


第二层:为什么 _track_finalize() 要挂在 precommit

_track_prepare() 并不直接生成消息,而是把 _track_finalize() 注册到 precommit。

原因很直接:

  • 要等字段真正写完,才能同时拿到旧值和新值;
  • 要把同一事务里的 tracking 合并到统一处理点;
  • 要确保最终产物仍然能自然接到 message_post() 流程里。

这比在各个 write 分支里零散拼文案稳得多,也不容易产生“写了一半先发日志”的怪现象。


第三层:_message_track() 真正做的是“变化翻译”

_message_track() 并不只是 old != new。它会:

  • 根据字段元信息拿到 string/type/selection/currency_field
  • 调用 _mail_track() 生成 changed fields 与 tracking values;
  • 再从 precommit 里取可能预设的 body 或 author;
  • 最后决定这次变化应该生成什么消息。

这意味着 tracking 的输出至少有三层:

  1. 哪些字段变了;
  2. 日志正文长什么样;
  3. 这条消息有没有额外业务语义。

所以字段追踪不是 UI 文案拼接,而是“数据变化 → 协作消息”的翻译过程。


第四层:_track_subtype() 决定这是不是业务事件

默认的 _track_subtype() 返回 False,但很多业务模型会覆写它。

它的意义在于:

  • 普通字段变化可以只是一般日志;
  • 关键字段变化则可以升级成带 subtype 的业务事件。

例如阶段变化、负责人变化、关键状态切换,这些都不只是 diff,而可能意味着:

  • followers 收到不同通知;
  • 消息归属于不同订阅语义;
  • 协作历史里这条记录的阅读方式不同。

所以 _track_subtype() 的存在,正是 Odoo 把 tracking 从“技术日志”抬升成“业务协作消息”的关键。


第五层:为什么 tracking 最终还是走 message_post()

源码里如果 subtype 存在,会直接 record.message_post(...);即便没有 subtype,tracking 的结果最终也还是围绕 mail.message 展开。

这说明 Odoo 的设计选择非常明确:

字段变更记录不做成独立日志系统,而做成 Chatter 消息生态的一部分。

这样做的好处是:

  • 直接进入时间线;
  • 复用 subtype、followers、通知机制;
  • 不需要维护第二套展示与权限体系。

第六层:这套机制解决了什么问题

1. 保证旧值读取可靠

通过 precommit 缓存而不是事后猜测。

2. 保证消息生成发生在正确时机

既能看到最终新值,又不至于脱离当前事务。

3. 允许模型把字段变化升级成业务事件

通过 _track_subtype(),tracking 不再只是技术日志。

4. 让变更记录直接进入协作消息系统

不需要额外维护独立审计展示层。


实战里最容易误解的几个点

误解 1:字段 tracking 只是前端展示

不对。核心发生在 mail.thread 的事务与消息层。

误解 2:tracking 一定只发一条固定格式日志

不对。body、author、subtype 都可能被模型或上下文影响。

误解 3:_track_subtype() 只是锦上添花

不对。它决定某些变化是普通 diff 还是带业务语义的协作事件。


二开时最该守住的边界

1. 不要在 write 后自己手搓一条 Chatter 文案来替代 tracking

你会绕开旧值缓存、标准 tracking value、subtype 分流和统一消息生成链路。

2. 关键业务状态通知时,优先考虑覆写 _track_subtype()

这通常比简单在 write 里 message_post() 更符合 Odoo 原生协作语义。

3. 排查 tracking 异常时,不要只看最终 mail.message

还要看:

  • _track_prepare() 有没有拿到旧值;
  • precommit data 有没有被清掉;
  • _message_track() 有没有识别出真实 changes;
  • _track_subtype() 是否返回了预期 subtype。

结论

Odoo 的字段 tracking 机制,本质上是:

  • 先缓存旧值;
  • 再在事务末尾统一对比;
  • 按 subtype 决定业务语义;
  • 最终落成 Chatter 消息。

一句话总结:

Chatter 里的字段变化不是数据库 diff 的回显,而是 Odoo 用 mail.thread 把“数据变化”翻译成“协作事件”的结果。

DISCUSSION

评论区

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