先给一句不会错的判断标准
在 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必须是cascade或restrict
这已经暴露本质了:
_inherits不是纯 Python 继承,而是通过一个 delegate many2one,把父模型挂进来。
也就是说:
- 子模型自己有自己的表
- 父模型也有父模型的表
- 两者通过一条 required many2one 连起来
第三层:为什么你在子模型上能直接访问父字段
这点很容易让人误以为 _inherits 就是“字段真的继承过来了”。
其实源码里 _add_inherited_fields() 说得非常清楚:
它会把父模型字段补成一种特殊 related field:
related=f"{parent_fname}.{name}"inherited=Truerelated_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.page把ir.ui.view复制了一份
而是:
website.page自己有自己的模型身份- 它通过
view_id委托到ir.ui.view - 所以像
arch、key、website_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():
如果传入值里还没有父记录,就会:
- 先整理出属于父模型的字段
- 先创建父记录
- 再把父记录 id 填到子模型的 delegate many2one 上
- 最后再创建子记录
所以 _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
评论区