模型继承边界

Odoo 里 _inherit 和 _inherits 到底差在哪:扩展、委托与字段归属讲透

结合 Odoo 19 ORM 与 website.page 源码,讲清 _inherit 和 _inherits 的根本区别:前者是在同一模型上加能力,后者是通过委托把父模型字段投影到子模型上,但数据仍存放在不同表里。

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

先给一句不会错的判断标准

在 Odoo 里:

  • _inherit 更像“在原模型身上继续长东西”
  • _inherits 更像“我自己有一张表,但把另一个模型的字段委托进来用”

所以它们最根本的区别,不是语法长得像不像,而是:

数据到底还在不在同一张表里。

这个点一旦想清楚,很多 create / write / unlink / 字段冲突问题都会顺很多。


第一层:_inherit 是扩展原模型,不是再造一个平行模型

最常见的写法是:

class SaleOrder(models.Model):
    _inherit = 'sale.order'

它的意思不是“生成一张新的 sale.order 子表”,而是:

  • 继续往已有模型 sale.order 上加字段
  • 加方法
  • 覆盖方法
  • 改视图、改行为

从数据库角度看,通常还是那张原表在承载主要数据。

所以 _inherit 的直觉应该是:

扩展同一个对象。

不是“再包一层”。


第二层:_inherits 是委托,不是传统 OO 里的同表继承

_inherits 则完全不是这个逻辑。

model_classes.py_check_inherits() 里,Odoo 会强制要求:

  • _inherits 指向的字段必须是 Many2one
  • 而且必须 required
  • 必须是 delegate=True
  • ondelete 必须是 cascaderestrict

这已经暴露本质了:

_inherits 不是纯 Python 继承,而是通过一个 delegate many2one,把父模型挂进来

也就是说:

  • 子模型自己有自己的表
  • 父模型也有父模型的表
  • 两者通过一条 required many2one 连起来

第三层:为什么你在子模型上能直接访问父字段

这点很容易让人误以为 _inherits 就是“字段真的继承过来了”。

其实源码里 _add_inherited_fields() 说得非常清楚:

它会把父模型字段补成一种特殊 related field:

  • related=f"{parent_fname}.{name}"
  • inherited=True
  • related_sudo=False

也就是说,子模型上那些“看起来像自己的字段”,本质上更像:

  • 自动帮你铺出来的 related 代理入口
  • 读写时再转去父记录

所以字段表现像在子模型上,但数据未必存到子表里

这正是 _inherits 最容易让人产生错觉的地方。


最经典的例子:website.page

/home/ubuntu/odoo-temp/addons/website/models/website_page.py 里:

class WebsitePage(models.Model):
    _name = 'website.page'
    _inherits = {'ir.ui.view': 'view_id'}

这里的意思不是:

  • website.pageir.ui.view 复制了一份

而是:

  • website.page 自己有自己的模型身份
  • 它通过 view_id 委托到 ir.ui.view
  • 所以像 archkeywebsite_id 这些能力,大量都和 view_id 有关

源码里你会看到很多 related 字段直接绑到 view_id

  • website_id = fields.Many2one(related='view_id.website_id', ...)
  • arch = fields.Text(related='view_id.arch', ...)

所以 website.page 不是“简单继承了 view 类”,而是:

页面模型 + 视图模型组成的一对委托关系。


第四层:create 时为什么 _inherits 会先碰父记录

models.py 的 create 流程里,Odoo 会专门遍历:

for model_name, parent_name in self._inherits.items():

如果传入值里还没有父记录,就会:

  1. 先整理出属于父模型的字段
  2. 先创建父记录
  3. 再把父记录 id 填到子模型的 delegate many2one 上
  4. 最后再创建子记录

所以 _inherits 场景里,create 不再只是“插一张表”那么简单,而是:

父子两张表协同创建。

这也解释了为什么你在 _inherits 模型上写 create 时,特别容易:

  • 把字段归属搞错
  • 误以为 vals 都进子表
  • 忘记某些父字段其实要交给父模型处理

第五层:write 为什么看起来像“写一个模型,实际动两边”

同理,写 _inherits 模型时,Odoo 也会把值拆开:

  • 哪些属于子模型自己
  • 哪些属于父模型委托进来的字段

然后分别写到:

  • 子表
  • 父表

所以如果你在调试时只盯一张表,经常会得出错误结论:

  • “怎么字段没写进去?”
  • “怎么这个字段根本不在这张表?”

其实是因为它本来就在父模型那边。


_inherit_inherits 最容易混淆的 3 个点

1)都能“看起来扩展了字段”

是的,但来源不同:

  • _inherit:字段就是扩到这个模型上
  • _inherits:很多字段只是代理到父模型

2)都能改方法

也是,但语义不同:

  • _inherit 更像增强原类
  • _inherits 更像组合 / 委托后的门面模型

3)都能让你在 Python 层“像同一个对象一样用”

这只是 ORM 封装得好,不代表数据库结构相同。


什么时候更适合 _inherit

当你只是想:

  • 给已有模型加字段
  • 重写业务逻辑
  • 延伸现有对象生命周期

通常优先 _inherit

因为它简单、直观、维护成本低。


什么时候才考虑 _inherits

当你明确需要:

  • 一个新的业务模型身份
  • 但又想复用另一个模型的大量字段与行为
  • 并保留两边表结构与职责边界

这时 _inherits 才合理。

它更像“组合 + 委托”,而不是“顺手继承一下”。

如果只是想复用几个字段,很多时候 related / mixin / 普通 _inherit 更干净。


一个判断题:website.page 是不是“就是 ir.ui.view”

不是。

更准确的说法是:

  • website.page 依赖 ir.ui.view
  • 并通过 view_id 把它委托进来
  • 于是你在 ORM 层感觉它们贴得很近

但它仍然是一个独立模型,不只是 ir.ui.view 的别名。


一句话总结

_inherit 是在原模型身上继续长,重点是“同一个模型被扩展”;_inherits 是通过 delegate many2one 把父模型字段投影进来,重点是“不同表、同体验、靠委托桥接”。

开发里一旦分不清这两者,最先出错的通常不是语法,而是字段归属、create/write 链路和删除联动。

DISCUSSION

评论区

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