变更追踪

Odoo Chatter 字段追踪为什么不是“把旧值和新值记一下”:mail.tracking.value、字段权限过滤与差异展示讲透

很多人以为 Chatter 里的字段变更只是生成一句“X 改成 Y”。但从 mail.tracking.value 与 account 对 mail.message 的扩展看,Odoo 真正在维护的是一份结构化差异数据:既要按字段类型存储旧值/新值,又要在展示时再判断当前用户有没有权限看到这些字段。

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

很多人看到 Odoo Chatter 里的变更记录,会下意识地把它理解成一种“描述文本”:

  • 状态从 Draft 改成 Posted
  • 金额从 100 改成 120
  • 负责人从 A 改成 B

但如果真去看 /home/ubuntu/odoo-temp/addons/mail/models/mail_tracking_value.py,会发现 Odoo 并不是先生成一句可读文本再存起来。

它真正做的是:

先把每个字段变化保存成结构化差异数据,再在展示阶段决定当前用户能看到什么样的差异。

这两个阶段分开,是理解 Chatter 追踪行为的关键。

一、Odoo 追踪的不是一句话,而是一组 tracking values

mail.tracking.value 这个模型专门承载“字段发生了什么变化”。

它不是只存一段 message body,而是按类型拆成多组字段:

  • old_value_integer / new_value_integer
  • old_value_float / new_value_float
  • old_value_char / new_value_char
  • old_value_text / new_value_text
  • old_value_datetime / new_value_datetime
  • currency_id
  • field_id
  • mail_message_id

这意味着 Chatter 里你最后看到的那句变更提示,底层不是一段已经写死的人类语言,而是:

  • 哪个字段变了;
  • 旧值是什么;
  • 新值是什么;
  • 它属于什么类型;
  • 是否需要货币精度、日期格式或 Many2one 显示名。

这也是为什么 Odoo 能把 tracking 同时用于:

  • 普通 Chatter 展示;
  • 通知摘要;
  • account 审计日志预览;
  • 搜索 old/new value。

因为它先保存的是结构,不是最终文案。

二、_create_tracking_values() 真正在做的是类型归一化

_create_tracking_values() 很值得细看。

它会根据字段类型,把变化装进不同的槽位。

普通标量字段

对于:

  • integer
  • float
  • char
  • text
  • datetime

逻辑很直观:

  • 旧值进 old_xxx
  • 新值进 new_xxx

monetary

货币字段不是只存数字,还会把 currency_id 记录下来。

这一步很关键,因为前端展示差异时需要知道:

  • 这是金额;
  • 应该用哪种货币格式;
  • 小数精度是多少。

所以金额变更不是“浮点数改了”,而是“带币种上下文的变更”。

date

date 会被转成 datetime 存入追踪值。

这说明 mail.tracking.value 为了统一展示与序列化,宁愿在存储层把 date 和 datetime 往同一类槽位靠,也不额外设计一组专门的日期字段。

selection

selection 存的不是技术值,而是显示文本。

也就是说,追踪记录优先服务于“人能看懂”,而不是“还能拿回原始 key 再二次翻译”。

many2one / many2many / one2many / tags

这类关联字段不会只存 ID,还会尽量存:

  • 记录 ID
  • display name
  • 多值列表拼接后的文本

这就是为什么 Chatter 里看到的是“负责人从 Mitchell Admin 改成 Marc Demo”,而不是 3 -> 7

换句话说:

tracking value 是结构化的,但它仍然优先面向展示,而不是做审计仓库里那种完全原子化、纯技术型快照。

三、为什么“谁在看”会影响看到的差异内容

这点特别容易被误解。

很多人默认以为:

  • 既然差异已经存进数据库,谁打开消息都该看到一样的内容。

_filter_has_field_access(env) 恰恰说明不是这样。

源码逻辑是:

  • 如果 tracking 绑定到了一个真实字段,当前用户必须对该字段有 read 权限;
  • 如果 tracking 已经没有 field_id,只剩历史信息,则通常只有系统管理员能看。

这意味着:

一条 mail.message 是共享的,但其中 tracking_value_ids 的可见子集,不一定对所有用户相同。

所以你在现场看到“管理员能看到完整字段变更,普通用户只看到部分甚至看不到”,这不是前端随机出问题,而是设计如此。

四、为什么 account 模块还要再包一层 audit preview

/home/ubuntu/odoo-temp/addons/account/models/mail_message.py 里,account 给 mail.message 扩了一个 account_audit_log_preview

它的核心流程很清楚:

  1. 先拿到 notification 类型消息;
  2. 再用 message.sudo().tracking_value_ids._filter_has_field_access(self.env) 过滤当前环境能看的差异;
  3. 再调用 _tracking_value_format() 把结构化差异格式化成“旧值 ⇨ 新值 (字段名)”;
  4. 最后拼成审计预览文本。

这里最值得注意的是第 2 步。

源码不是直接 tracking_value_ids._tracking_value_format(),而是先 sudo() 取数据,再按当前用户权限过滤。

这表达了一个很成熟的安全思路:

  • 计算阶段可以从更高权限读取底层数据;
  • 但最终展示阶段必须按当前用户的字段权限裁剪。

所以 account 审计预览不是“数据库里早就有一段完整文案”,而是每次按当前用户上下文即时生成可见版本

五、_tracking_value_format() 为什么还要再做一次格式化

mail.tracking.value 已经存了 old/new 值,为什么不直接显示?

因为存储值和展示值不是同一件事。

_tracking_value_format() 还要补足这些信息:

  • 字段 string 名称;
  • 字段显示顺序;
  • 是否是 property 字段;
  • 金额精度;
  • 日期 / datetime 的格式输出;
  • boolean 的布尔化;
  • 每种字段在前端该怎么呈现。

所以 tracking value 更像是一个“半成品差异对象”,而不是最终 UI 文案。

存储层负责:

  • 可追溯;
  • 可搜索;
  • 可按类型重建。

展示层负责:

  • 对人类友好;
  • 对当前用户安全;
  • 对具体模型语义友好。

六、为什么 account 还要限制 tracking 的删除与改写

addons/account/models/mail_tracking_value.py 很短,但信息量很大。

它做了两件事:

  • 删除 tracking value 时,回调到 mail_message_id._except_audit_log()
  • 写 tracking value 时,也先走 _except_audit_log()

而 account 对 mail.message_except_audit_log() 会检查:

  • 是否属于受保护的 restricted audit trail;
  • 是否允许修改主体、body、关联对象等关键内容;
  • 如果不允许,就抛错。

翻成人话就是:

在会计场景里,tracking value 不是普通聊天碎片,而是审计链的一部分。

所以你不能把它当作“发错了就删、看不顺眼就改”的普通 chatter 消息。

这也解释了为什么很多会计客户会感受到:

  • 一般消息可以改;
  • 但某些审计相关变更记录几乎碰不得。

因为它们已经被提升成审计留痕对象了。

七、新手最容易误判的三个点

1)“消息存在”不等于“所有 tracking 字段都对所有人可见”

消息是同一条,但差异明细会按字段权限过滤。

2)“Chatter 上展示的文案”不等于“数据库原样保存的文本”

数据库里先存的是结构化 old/new 值,最终显示文案是后拼出来的。

3)“tracking value”不只是通知 UI 的附属品

在 account 里,它还可能属于受保护的 audit trail,具备审计约束。

八、开发时最值得注意的实战建议

1)自定义追踪字段时,先想清字段权限

如果字段本身有 groups 限制,不同用户在 Chatter 里看到的结果天然会不同。

2)不要把 tracking 当成纯文本日志来二次加工

更稳的做法是复用 _tracking_value_format() 这套格式化链,而不是自己拼 old/new 字符串。

3)会计相关消息不要随意改写或清理

尤其是在 restrictive audit trail 场景里,这些 tracking 可能受法规或审计链保护。

4)排查“为什么有人看得到、有人看不到”时,优先查字段 access groups

很多问题不是消息丢了,而是 _filter_has_field_access() 把差异裁掉了。

总结

mail.tracking.value 最值得记住的一点是:

Odoo 跟踪的不是一段“变更说明文字”,而是一份可按类型重建、可按权限裁剪、可按模型格式化的结构化差异。

所以 Chatter 里的字段追踪,其实是三层机制叠在一起:

  1. 存储层:按类型保存 old/new 值;
  2. 权限层:按当前用户字段权限过滤可见差异;
  3. 展示层:按模型字段语义重新格式化成可读文案。

把这三层分清后,你就会明白:

  • 为什么同一条消息不同人看到的不完全一样;
  • 为什么金额、日期、Many2one 的追踪表现不一样;
  • 为什么 account 会把 tracking value 当成审计链的一部分来保护。

这才是 Odoo Chatter 字段追踪真正稳健的地方:

它不是“记一句话”,而是在维护一份既能读、又能控、还能审计的差异记录。

DISCUSSION

评论区

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