很多人第一次做 Odoo 字段二开,会把问题想得很简单:
- XML 上写个
widget="xxx"; - registry 里注册一个同名字段;
- 页面能渲染出来就算完成。
这只能算摸到最外层。
真正决定字段组件怎么运行的,不只是 widget 名字,而是一整条解析链:
field.js先决定找谁;- registry 再决定这个候选项是否支持当前字段类型;
extractProps决定节点配置怎样转成组件 props;- formatter / parser 决定显示值与输入值如何往返;
viewType和jsClass还会改变整个选型优先级。
所以 Odoo 字段体系真正值得研究的,不只是 widget 本身,而是widget 是怎样被解析出来的。
一、getFieldFromRegistry() 先解决的不是组件渲染,而是查找优先级
field.js 里的 getFieldFromRegistry(fieldType, widget, viewType, jsClass) 很关键。
它不是简单按 widget 名字取值,而是先构造一组前缀:
- 如果有
jsClass,优先级是jsClass→viewType→ 空前缀; - 否则就是
viewType→ 空前缀。
这意味着同一个 widget key 并不是全局唯一语义,它可能在不同上下文下解析成不同实现,比如:
list.xxxform.xxx- 某个自定义
jsClass.xxx
这套规则的意义在于:
字段实现不是孤立组件,而是依附于“当前视图语境”的可替换实现。
很多定制项目一上来就往全局 fields registry 塞组件,最后影响面越来越大,问题就出在忽略了这个语境优先级。
二、widget 指定了也不代表一定合规,supportedTypes 仍然会兜底校验
getFieldFromRegistry() 找到 widget 后,并不会无条件信任它。
如果该 widget 声明了 supportedTypes,而当前字段类型不在其中,Odoo 会显式警告。
这说明 registry 项并不是“名字对上就一定能跑”的黑盒,而是带有类型边界的能力声明。
例如 char_field.js 就明确声明:
supportedTypes: ["char", "text"]
这类声明有两个作用:
- 防止错配被静默吞掉;
- 让同名 widget 可以在不同字段类型之间保留合理边界。
换句话说,字段 registry 不是随意映射表,而更像一张带类型约束的解析表。
三、extractProps 才是“XML 节点语言”和“组件语言”之间的翻译器
很多初学者只看组件 props,却忽略 props 是怎么来的。
实际上,registry 项里的 extractProps 才是最关键的中间层。
以 char_field.js 为例,extractProps 会从:
attrsoptionsplaceholder
里抽出真正组件关心的东西,例如:
isPassworddynamicPlaceholderautocomplete
这说明 field widget 并不是直接吃 XML 原始节点,而是先经过一次领域化翻译。
没有这层翻译,就会出现两种坏味道:
- 组件自己到处解析 attrs/options;
- 或 XML 细节直接泄漏进组件内部。
Odoo 明显更偏好 registry 项承担翻译责任,组件只消费已经整理好的 props。
四、formatter 和 parser 让字段解析不止停在“选中哪个组件”
字段选型只是第一步。真正进入可编辑状态后,还要解决一件更细的事:
页面上显示的值,和用户输入回模型的值,本来就不一定一样。
formatters.js 和 parsers.js 正是在承认这个现实。
比如:
formatChar可以处理 password 显示;formatDate/formatDateTime要处理 locale 和 numeric 模式;parseFloat/parseInteger要兼容本地化分隔符;parsePercentage、parseMonetary还要处理特殊输入语义。
尤其 parsers.js 很值得注意,它并不是傻乎乎地 Number(value):
- 会考虑
localization.thousandsSep与decimalPoint; - 会兼容带
=的数学表达式; - 某些场景还能返回
Operation,表示增量更新意图而非最终纯值。
这说明 parser 本质上不是“字符串转类型”,而是在做面向业务输入语义的解释。
五、字段解析链真正的顺序是什么
把这些源码串起来,更准确的顺序应该是:
- 视图解析阶段,
Field.parseFieldNode()读取节点与字段元信息; getFieldFromRegistry()按jsClass/viewType/ 默认前缀查候选实现;- 候选项通过
supportedTypes等约束完成适配校验; extractProps把 XML 配置转成组件 props;- 渲染阶段组件消费
record与 props; - 显示与输入再分别通过 formatter / parser 参与值流。
这样你就会明白,字段 widget 从来不是“名字对上就结束”,而是被逐层解析成一个受约束的运行单元。
六、为什么这个角度对二开很重要
因为很多字段二开的问题,根本不在组件模板,而在解析链某一层选错了位置:
- 本该做
viewType定制,却硬改全局 widget; - 本该做
extractProps翻译,却把 XML 解析逻辑塞进组件; - 本该用 formatter/parser 处理值语义,却直接在模板里字符串拼接。
最终结果往往是:
- 某个场景能跑;
- 换个视图或语言环境就坏;
- 代码复用性越来越差。
结语
Odoo 字段体系最值得学习的,不只是 widget 组件写法,而是它把“字段解析”拆成了几层稳定边界:
- registry 负责选型;
supportedTypes负责边界;extractProps负责翻译;- formatter / parser 负责值语义。
所以当你再看到一个 <field widget="xxx" /> 时,更准确的理解应该是:
这不是在“指定一个组件名”,而是在启动一整条字段解析与值语义协作链路。
DISCUSSION
评论区