默认值链路

Odoo 默认值到底从哪来:context、ir.default、field.default 与继承字段 default_get 顺序讲透

很多人以为 Odoo 默认值只有字段 default。其实 models.py 的 default_get 明确按 context、ir.default、field.default、company_dependent fallback 与 inherited 字段委托顺序取值。

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

很多人第一次排查 Odoo 表单默认值时,脑子里只有一句话:

这个字段不是写了 default= 吗,为什么页面上没有?

但真正打开 /home/ubuntu/odoo-temp/odoo/orm/models.py 里的 default_get(),你会发现 Odoo 对“默认值”这件事的理解,比单个字段属性复杂得多。

它并不是只看字段定义,而是按一条很明确的优先链路去找值:

  1. context 里的 default_xxx
  2. ir.default 里给当前用户 / 当前公司配置的默认值
  3. 字段本身的 field.default
  4. company_dependent 字段的 fallback 默认值
  5. 如果字段是 inherited 字段,再委托父模型继续做 default_get

所以新手最容易踩的坑不是“不会写 default”,而是:

把默认值来源想得太单一,结果在错误的层次上排查。

一、default_get() 不是随便拼字典,而是按优先级逐层截胡

源码里 default_get(self, fields) 的主循环非常清楚。对每个字段名,它会按顺序判断:

1)先看 context

只要上下文里有 default_<field_name>,这个值就直接拿走,并 continue

这意味着:

  • action context 里塞的默认值,优先级最高;
  • 向导打开表单时带入的 default_partner_iddefault_company_id,通常会压过模型里自己写的默认值;
  • 你在 Python 里测试 field.default 明明正常,但界面里看到别的值,很可能不是字段错了,而是 action 或 button 的 context 先截胡了。

这是 Odoo 的一个重要设计:

“当前操作意图”优先于“模型通用默认值”。

因为用户此刻是从某个入口打开表单,入口上下文本来就应该最懂这次创建动作想要什么。

二、ir.default 比字段 default= 更像“用户偏好层”

如果 context 没给值,源码第二步会读取:

  • self.env['ir.default']._get_model_defaults(self._name)

/home/ubuntu/odoo-temp/odoo/addons/base/models/ir_default.py 里的 _get_model_defaults() 也很值得看。

它会按当前:

  • uid
  • company_id
  • model_name
  • 可选条件 condition

去取默认值,并按 SQL 结果顺序保留最高优先级项。

这说明 ir.default 的定位不是“模型代码逻辑”,而更像:

  • 用户习惯
  • 公司习惯
  • 后台配置出来的默认表单行为

所以如果你遇到这些现象:

  • 同一个模型,不同用户新建时默认值不同;
  • 同一个用户,切公司后默认值变了;
  • 字段没有写 default=,但界面总是自己带某个值;

第一反应就不该只去搜字段定义,而该去看是不是有 ir.default 在生效。

三、为什么 field.default 反而排在后面

很多人会惊讶:字段定义里的 default=,竟然不是最前面。

源码里它排在:

  • context 之后
  • company_dependentir.default 之后

这其实非常合理。

field.default 更像“模型作者给出的通用兜底建议”; 而 contextir.default 更像“这次操作 / 这个用户 / 这家公司当前真实想要的默认值”。

也就是说,Odoo 把优先级排成了:

具体场景 > 用户/公司偏好 > 模型通用默认

这就是为什么很多业务系统里,你应该把“本次动作专属默认值”放进 action context,而不是指望一个通用字段 default 统治所有入口。

四、company_dependent 字段为什么要单独走 fallback

default_get() 里有个很容易被忽略的分叉:

  • company_dependent 字段,先查 ir.default
  • company_dependent 字段,字段默认值之后再查 fallback

这不是多余,而是在尊重 company-dependent 字段的含义。

这类字段本来就带“按公司分层”的语义。Odoo 不希望太早把它们当成普通字段默认值处理,而是让:

  • 显式上下文先说了算
  • 字段自己定义的默认逻辑先试一次
  • 最后再回到公司维度 fallback

这能避免一种常见误判:

  • 你以为是字段默认值没跑;
  • 实际上是公司依赖字段最后用 fallback 接住了;
  • 或者你以为切公司不影响新建默认;
  • 实际上 company-dependent 默认本来就是按公司语义取值。

五、inherited 字段为什么有时像“晚一步才出现默认值”

default_get() 的最后一段会把 inherited 字段收集到 parent_fields,然后再:

  • defaults.update(self.env[model].default_get(names))

这说明通过 _inherits 暴露出来的字段,不是和当前模型字段完全平铺等价处理的。

Odoo 会先完成当前模型能直接确定的默认值,再把 inherited 字段委托给父模型去算。

这带来两个实战结论:

1)父模型默认值不会自动“抢在最前面”

如果你是在子模型上打开表单,当前模型的 context / ir.default / 字段默认值先参与; 父模型 inherited 字段默认值是后续委托补上的。

2)排查 inherited 字段默认异常时,不要只盯当前模型

你可能在子模型上看到一个 inherited 字段没默认值,但真正逻辑写在父模型:

  • 父模型 default_get
  • 父模型字段 default=
  • 父模型自己的 ir.default

如果只在子模型找,往往会越找越乱。

六、源码还顺手处理了一个前端常见坑:x2many 默认值格式

default_get() 里还有一段特别实用的注释:

  • 它故意不直接用 _convert_to_write() 处理 x2many 默认值;
  • 因为像 [(Command.LINK, 2), (Command.LINK, 3)] 这种结构,web client 作为默认值并不稳定;
  • Odoo 会先走 cache,再转成更适合前端消费的写法,比如归一成 Command.SET 风格。

这背后的意思是:

默认值不仅要“逻辑上对”,还要“前端能吃得下”。

所以有时候你在 shell 里看一个默认值像是合法的 ORM 命令,前端却没有按预期展示,并不一定是字段定义错了,而可能是默认值格式没有被规范到 web client 能稳定处理的形态。

七、实战里怎么排查“默认值不对”

推荐按这个顺序查:

  1. 先看入口 action / button / wizard 有没有 default_xxx
  2. 再看后台有没有 ir.default
  3. 再看字段 default= 或模型自定义 default_get
  4. 如果字段是 company_dependent,切公司复测
  5. 如果字段来自 _inherits,继续追父模型
  6. 如果是 x2many,顺手检查默认值命令格式是否适合前端

这个顺序的好处是,你是在沿着 Odoo 的源码顺序排查,而不是按自己的主观猜测乱跳。

八、最容易误解的一点:默认值不是“模型真相”,而是“创建起点”

最后一定要分清:默认值解决的是“新记录创建时先带什么”,不是“字段以后永远应该是什么”。

所以以下几件事不要混为一谈:

  • default_get() 给的初始值
  • onchange 后界面动态改出来的值
  • create() / write() 最终真正落库的值
  • 约束和权限把某些值挡掉后的结果

很多“默认值失效”的 bug,最终根本不是默认值问题,而是:

  • onchange 又覆盖了一次;
  • create 里又重算了一次;
  • 权限或多公司过滤让候选值不可用;
  • inherited 父模型逻辑在别处补值。

结语

default_get() 这段源码看起来不长,但它把 Odoo 一个很核心的产品观表达得很清楚:

  • 当前入口上下文最重要;
  • 用户 / 公司偏好其次;
  • 模型作者给的字段默认再其次;
  • inherited 字段仍由真正拥有它的父模型负责;
  • 前端能否稳定消费默认值,也被纳入设计范围。

所以以后再看到“这个字段明明写了 default 为什么没生效”,更准确的问题应该是:

这次新建动作里,到底是谁在默认值链路上先拿到了发言权?

DISCUSSION

评论区

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