Delegation

Odoo 的 _inherits 不是代码继承,而是代理建模:delegation 真正解决什么问题

很多人把 _inherits 当成“高级版 _inherit”,结果越做越乱。其实它更接近“当前模型通过一个 Many2one 代理父模型字段”,本质是组合建模,不是 Python 继承。

Odoo 开发
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 8 阅读

先把一句最容易说错的话改掉

_inherits 不是“子类继承父类逻辑”。

如果你用 Python 面向对象那套直觉去理解它,后面大概率会在这些地方踩坑:

  • 以为字段真的复制到了当前模型
  • 以为删除行为会自动像继承层级那样自然传播
  • 以为搜索、复制、权限都只看当前模型就够了

但从源码设计看,_inherits 更接近:

当前模型通过一个 Many2one(delegate=True) 去代理另一个模型的字段。

这其实是 组合 / 代理建模,不是传统意义上的类继承。


为什么说 _inherits 的核心是 delegate=True

odoo/orm/fields_relational.pyMany2one._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)删除策略不是“继承树自然收口”

它本质上受那个 Many2oneondelete 策略影响。

Many2one 源码里会根据是否 required、是否 transient 等情况推导默认 ondelete,也会检查某些不合理配置。

也就是说,父子关系的删除边界,首先是 关系字段边界,不是“类层级边界”。

2)复制时不会只复制眼前表的数据

models.pycopy_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,先问自己三句:

  1. 我是不是需要一个新的业务模型身份
  2. 我是不是要把父模型字段当成长期公开接口来使用?
  3. 我能不能接受“两个记录对象绑定维护”的复杂度?

这三句里只要有两句答不稳,通常就该先退回去看看:

  • 普通 Many2one
  • _inherit
  • mixin

是不是更清爽。


最后一句话

_inherits 最容易让人误会的地方,就是名字里的“inherits”。

但按源码真实语义,它更应该被翻译成:

通过代理 Many2one 暴露父模型字段的一种组合建模机制。

一旦你把它看成 delegation/composition,而不是类继承,很多设计选择就会自然得多,模型也不容易长歪。

DISCUSSION

评论区

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