先说结论
@api.onchange 的定位只有一个:帮表单在用户输入时即时调整其他字段。
它不是保存逻辑,不是数据库触发器,也不是一条必经的业务规则。
换句话说:
onchange 负责“界面上看起来对不对”,不是“数据库里一定写成什么”。
这也是很多新手第一次写 Odoo 时最容易踩的坑。
源码里,onchange 本来就是给表单准备的
在 odoo/orm/models.py 里,Odoo 会先收集模型上的 onchange 方法,生成一个映射表。
你能从代码里看到两个关键信号:
@api.onchange方法会被登记到_onchange_methods- 真正的
onchange()接口并不在 ORM 核心实现,而是由 Web 层来负责调用和回传
这说明它的设计目标从一开始就不是“直接改数据库”,而是:
- 接收前端当前草稿值
- 运行 onchange 方法
- 把新值、warning、domain 等结果返回给页面
所以它天然属于表单交互层。
为什么它不会自动落库
因为用户此时通常还没有点“保存”。
表单里的记录可能还是一个虚拟草稿:
- 你改了
partner_id - 系统顺手帮你填了
payment_term_id - 也可能改了
currency_id、journal_id、tax_ids
这些变化先停留在客户端的表单缓存里,直到用户提交 create/write,才会真正写进数据库。
所以 onchange 的正确理解不是“改数据”,而是:
对当前草稿做即时补全。
onchage、compute、inverse、constraints 各管什么
把它们分开,会非常清楚:
onchange
只管表单交互。用户改字段时即时补值。
compute
只管字段的计算来源。它能在读取、重算、保存后保持一致。
inverse
当一个 computed field 需要用户反向编辑时,用它把值写回源字段。
constraints
只管校验。它不负责帮你自动改值,只负责在不合法时拦住。
如果你想让一个值“最终一定存在数据库里”,不要依赖 onchange。
一个很实用的判断题
问自己一句:
这个逻辑是不是只在“表单编辑过程中”有意义?
如果答案是“是”,那就可以考虑 onchange。
例如:
- 选了客户后,自动填付款条款
- 选了产品后,自动填税、单位、描述
- 改了公司后,顺手切换可用的 journal
如果答案是“不是”,比如:
- 创建后必须永久存在
- 批处理、导入、RPC 都必须一致
- 任何绕过表单的写入也要生效
那就应该放在 create() / write() / compute() / constraints() 里,而不是 onchange。
源码里还有一个容易忽略的小点:change_default
_onchange_methods 里不只会收集 @api.onchange,还会把某些字段的 change_default 也塞进去。
这意味着:
- 一些默认值变化,也会被当成“表单联动”处理
- onchange 不只是你手写的方法,还包含框架级的默认值联动
也就是说,表单里的自动补值,很多不是“业务代码神奇地跑了”,而是 ORM 和 Web 在协作。
实战里最常见的误区
1. 用 onchange 做必填校验
不稳。用户可能直接导入、写 RPC、跑脚本,根本不会经过表单事件。
2. 用 onchange 做核心业务计算
如果这个值需要被搜索、报表、后续逻辑依赖,应该用 compute 或真正落库的逻辑。
3. 以为 onchange 已经保存成功
没有。它只是返回一份“页面应该怎么变”的建议。
一个好用的记忆方式
把 onchange 想成“前台助手”:
- 负责提醒你
- 帮你自动填几项
- 帮你减少手工输入
- 但不负责最终签字入档
而 create() / write() 才是“签字入档”的那一步。
结尾
如果你只记一句话,那就记这句:
onchange 让表单更聪明,compute / inverse / constraints 让数据更可靠。
把这条边界守住,很多表单联动问题都会少一半。
DISCUSSION
评论区