先把一句最容易说错的话改掉
_inherits 不是“子类继承父类逻辑”。
如果你用 Python 面向对象那套直觉去理解它,后面大概率会在这些地方踩坑:
- 以为字段真的复制到了当前模型
- 以为删除行为会自动像继承层级那样自然传播
- 以为搜索、复制、权限都只看当前模型就够了
但从源码设计看,_inherits 更接近:
当前模型通过一个
Many2one(delegate=True)去代理另一个模型的字段。
这其实是 组合 / 代理建模,不是传统意义上的类继承。
为什么说 _inherits 的核心是 delegate=True
在 odoo/orm/fields_relational.py 的 Many2one._setup_attrs__() 里,有个很关键的逻辑:
- 如果某个字段名出现在模型类的
_inherits.values()中 - 这个
Many2one字段就会被自动标记成delegate = True - 同时还会启用
bypass_search_access
这其实已经把 _inherits 的本质说得很明白了。
框架不是在说:
- “把父模型代码复制一份给你”
而是在说:
- “这个模型有一个指向父模型的代理字段,你可以通过它访问父模型字段”
所以 `_inherits = {'res.partner': 'partner_id'}`` 的真实语义不是“当前模型继承了 res.partner 类”,而是:
- 当前模型持有
partner_id partner_id是代理入口- 访问被代理字段时,框架会沿着这个入口去父记录取值
_inherit 和 _inherits 根本不是同一类问题
很多项目里最乱的地方,就是把 _inherit 和 _inherits 混用却没分清目的。
_inherit 解决的是“在同一模型上扩展定义”
比如:
- 给
sale.order加字段 - 重写它的方法
- 改它的约束、视图、业务行为
这本质上还是 同一个模型。
_inherits 解决的是“我要一个新模型,但又想复用另一张主表上的字段集合”
它更像:
- 我有自己的表
- 我也有自己的主键记录
- 但我希望通过一个代理 Many2one,把另一模型的一批字段当成“可直达字段”来使用
所以 _inherits 是建模问题,_inherit 更偏扩展问题。
把这两个问题混成一个问题,模型会越写越别扭。
为什么它更像“组合”,而不是“继承”
你可以把 _inherits 想成一个壳模型:
- 自己保存自己的字段
- 再通过某个代理字段连向父模型记录
- 某些字段访问会自动转发到父记录
这就是典型的 composition 思维:
- 当前模型拥有一个父对象引用
- 而不是成为那个父对象本身
这会直接影响你对很多行为的预期。
例如:
1)删除策略不是“继承树自然收口”
它本质上受那个 Many2one 的 ondelete 策略影响。
Many2one 源码里会根据是否 required、是否 transient 等情况推导默认 ondelete,也会检查某些不合理配置。
也就是说,父子关系的删除边界,首先是 关系字段边界,不是“类层级边界”。
2)复制时不会只复制眼前表的数据
在 models.py 的 copy_data() 里,框架会把 inherited fields、黑名单字段、父模型字段这些情况专门拆开处理。
这说明复制一个 _inherits 模型时,Odoo 并不是简单把当前表行拷贝一份,而是要考虑:
- 哪些字段来自代理父记录
- 哪些字段应由新的父记录承接
- 哪些翻译、One2many、Many2many 要递归处理
3)导入 / XMLID 创建时也会补父记录的 XMLID
在 _load_records() 里,如果当前记录带了 XMLID,而 _inherits 的父记录是刚创建的,框架还会顺手给父记录生成对应 XMLID。
这再次证明:
- 当前模型记录和父模型记录是两层对象
- 只是框架帮你把访问体验粘在一起了
_inherits 真正适合什么场景
它适合这种需求:
- 你确实需要 一个新模型身份
- 但这个新模型又确实要托管或复用另一模型的一批字段
- 而且这种复用不是“偶尔读一下”,而是要成为日常字段接口的一部分
比如你想表达:
- “这是一个新业务对象”
- 但它和某个基础对象具有强绑定的一对一结构
这时 _inherits 很顺手。
什么时候别急着上 _inherits
如果你的真实诉求只是下面这些,其实往往不该先选 _inherits:
场景 1:只是想给原模型多加几个字段
那用 _inherit 就够了。
场景 2:只是偶尔想关联到另一个模型
普通 Many2one 就够了,不需要代理整套字段。
场景 3:只是想复用几段业务逻辑
这更像 Python mixin 或普通方法抽象,不是 delegation 建模问题。
很多人一看到“想复用字段”,就条件反射上 _inherits,最后把模型做成“谁是主角都说不清”的结构。
新手最容易踩的 4 个坑
坑 1:把 _inherits 当“更强的 _inherit”
不对。它们不是强弱关系,而是解决不同问题。
坑 2:忘了它底层一定要有代理 Many2one
没有那个字段,就没有 delegation。
这也是为什么源码会强校验:如果你手动声明 delegate=True 却没放进 _inherits,框架会直接报错。
坑 3:低估父记录生命周期成本
你不是只维护一张表,而是在维护“当前记录 + 父记录”这组绑定对象。
坑 4:对权限、搜索、复制的复杂度预期过低
因为被代理字段不是真的长在当前表上,很多行为都天然比单表模型更复杂。
一个更实用的判断法
每次你想用 _inherits,先问自己三句:
- 我是不是需要一个新的业务模型身份?
- 我是不是要把父模型字段当成长期公开接口来使用?
- 我能不能接受“两个记录对象绑定维护”的复杂度?
这三句里只要有两句答不稳,通常就该先退回去看看:
- 普通
Many2one _inherit- mixin
是不是更清爽。
最后一句话
_inherits 最容易让人误会的地方,就是名字里的“inherits”。
但按源码真实语义,它更应该被翻译成:
通过代理 Many2one 暴露父模型字段的一种组合建模机制。
一旦你把它看成 delegation/composition,而不是类继承,很多设计选择就会自然得多,模型也不容易长歪。
DISCUSSION
评论区