15. 继承的应用
Odoo 强大之处在于它的模块化设计。每个模块专为满足特定的业务需求而设计,模块之间也可以相互交互。这对于扩展现有模块的功能非常有用。例如,在房地产场景中,用户希望直接在普通用户视图中显示销售人员的房源列表。
在深入探讨具体的 Odoo 模块继承机制之前,先来看看如何修改标准 CRUD(创建、读取、更新或删除)方法的行为。
Python 继承
本小节的学习目标:不允许删除既非新建也未被取消的房产。

创建报价时,房产状态应变更为“已收到报价”。不应允许创建价格低于现有报价的报价。

在房地产模块中,并不需要开发特定功能来实现标准的 CRUD 操作,这是英文 Odoo 框架提供了执行这些操作所需的工具。通过标准 Python 继承机制,这些操作已经包含在模型中:
from odoo import fields, models
class TestModel(models.Model):
_name = "test_model"
_description = "Test Model"
...上面代码中的 TestModel 类继承自 Model,Model提供了 create()、read()、write() 和 unlink() 方法。
这些方法(以及 Model 上定义的任何其他方法)都可以进行扩展,以添加特定的业务逻辑:
from odoo import fields, models
class TestModel(models.Model):
_name = "test_model"
_description = "Test Model"
...
@api.model
def create(self, vals_list):
# Do some business logic, modify vals...
...
# Then call super to execute the parent method
return super().create(vals_list)对于 create() 方法而言,model() 装饰器是必要的,因为在创建上下文中,记录集 self的内容并不相关;但对于其他 CRUD 方法而言,它则并非必需。
另外需要注意的是,尽管可以直接重写 unlink() 方法,但绝大多数情况下,应该改用 ondelete() 装饰器来编写一个新方法。标记了此装饰器的方法将在 unlink() 期间被调用,这可以避免在直接重写 unlink() 时,因卸载模型模块而可能引发的一些问题。
在 Python 3 中,super() 等同于 super(TestModel, self)。当需要使用经过修改的记录集调用父类方法时,后者可能必不可少。
注意:
- 务必始终调用
super(),以免中断执行流程。只有极少数非常特殊的情况下才不需要调用它。- 请确保返回的数据与父类方法保持一致。例如,如果父类方法返回一个字典(
dict()),那么重写方法也必须返回一个字典(dict())。
开发练习:在 CRUD 方法中添加业务逻辑
如果state属性的值不是“新建/New”或“已取消/Cancelled”,则禁止删除该属性。
提示:使用
ondelete()装饰器创建一个新方法,并请注意,self可能是一个包含多条记录的记录集。
示例代码:
@api.ondelete(at_uninstall=False)
def _delete_if_state_not_new_or_canceled(self):
if any(rec.state != "new" and rec.state != "canceled" for rec in self):
raise exceptions.UserError("只能删除状态为新建或已取消的记录")在创建报价时,将房产的状态设置为“已收到报价”,如果用户尝试创建的报价金额低于现有报价,则触发错误。
提示:vals 中包含 property_id 字段,但该字段的类型为 int。要实例化 estate.property 对象,请使用 self.env[model_name].browse(value)
模型继承
在房地产模块中,用户想要直接在“设置/用户与公司/用户”表单视图中显示与此销售人员关联的房产信息。需要在 res.users 模型中添加一个字段,并调整其视图以显示该字段。
Odoo 提供了两种继承机制,用于以模块化方式扩展现有模型。
第一种继承机制
允许模块通过以下方式修改另一个模块中定义的模型的行为:
- 向模型添加字段;
- 重写模型中字段的定义;
- 向模型添加约束;
- 向模型添加方法;
- 重写模型中现有的方法。
第二种继承机制(委托)
允许模型的每个记录与父模型的记录建立关联,并提供对该父记录字段的透明访问。

在 Odoo 中,第一种机制无疑是最常用的。在前面示例中,向模型中添加一个字段,这使用的就是第一种机制。例如:
from odoo import fields, models
class InheritedModel(models.Model):
_inherit = ["inherited.model"]
new_field = fields.Char(string="New Field")按照惯例,每个继承的模型都定义在各自的 Python 文件中。在本示例中,该文件位置和名称为 models/inherited_model.py。
开发练习:在 res_users 表中添加字段
在 res.users 中添加以下字段:
| 字段 | 类型 | 备注 |
|---|---|---|
property_ids | One2many 关系,其字段引用 estate.property 中的销售人员字段:saleman_id |
在字段中添加一个域,以便仅列出可用的房产。示例代码如下:
在下一节中,将该字段添加到视图中,并检查一切是否能正常运行。
视图继承
本小节学习目标: 在用户表单视图中显示与销售人员关联的可用属性列表(如此用户销售的房产)。

Odoo 并未采用就地修改现有视图(即覆盖原有视图)的方式,而是提供了视图继承机制,其中子视图(即“扩展视图”)会叠加在根视图之上。这些扩展视图既可以向父视图添加内容,也可以移除父视图中的内容。
扩展视图通过 inherit_id 字段引用其父视图。与单一视图不同,其 arch 字段包含多个 XPath 元素,用于选择并修改父视图的内容:
<record id="inherited_model_view_form" model="ir.ui.view">
<field name="name">inherited.model.form.inherit.test</field>
<field name="model">inherited.model</field>
<field name="inherit_id" ref="inherited.inherited_model_view_form"/>
<field name="arch" type="xml">
<!-- find field description and add the field
new_field after it -->
<xpath expr="//field[@name='description']" position="after">
<field name="new_field"/>
</xpath>
</field>
</record>以下标签属性说明:
expr- 一个用于在父视图中选择单个元素的 XPath 表达式。如果未匹配到任何元素或匹配到多个元素,则会引发错误。position- 对匹配元素应用的操作:inside- 将XPath表达式的正文附加到匹配元素的末尾;replace- 将匹配的元素替换为XPath表达式的主体内容,并将新主体中所有$0节点实例替换为原始元素;before- 将XPath表达式中的主体作为同级元素插入到匹配元素之前;after- 将XPath表达式的正文作为匹配元素后的同级元素插入;attributes- 使用XPath主体中的特殊attribute元素来修改匹配元素的属性;
在匹配单个元素时,可以直接在待查找的元素上设置 position 属性。以下两种继承方式的结果相同。
<!--// 方式一: -->
<xpath expr="//field[@name='description']" position="after">
<field name="idea_ids" />
</xpath>
<!--// 方式二: -->
<field name="description" position="after">
<field name="idea_ids" />
</field>参考:有关此主题的文档可在“[继承](View records — Odoo 19.0 documentation)”中找到。
开发练习:在“用户”视图中添加字段
在新的切换标签页面中,将 property_ids 字段添加到 base.view_users_form 中。参考一下目录文件结构:
estate/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ ├── estate_property.py # 已有:定义 estate.property 模型
│ └── res_users.py # ✅ 新增:继承 res.users 添加 property_ids
├── views/
│ ├── estate_property_views.xml
│ └── res_users_views.xml # ✅ 新增:继承用户表单视图
└── security/
└── ir.model.access.csv # ✅ 确认 estate.property 的访问权限由于 Odoo 采用模块化设计,继承机制在其中得到了广泛应用。如需了解更多信息,请务必查阅相关文档。