第 12 章:继承

Odoo 的一个强大特性是其模块化。一个模块专注于特定的业务需求,但模块之间也可以相互交互。这对于扩展现有模块的功能非常有用。例如,在我们的房地产场景中,我们希望直接在普通用户视图中显示某个销售人员的房源列表。

但在深入了解 Odoo 的模块继承之前,让我们先看看如何修改标准 CRUD(创建、读取、更新或删除)方法的行为。

Python 继承

Note

目标:在本节结束时:

  • 不应允许删除状态不是“新建”或“已取消”的房源。

取消链接
  • 创建报价时,房源状态应变为 ‘Offer Received’

  • 创建的报价价格不应低于现有报价

创建

在我们的房地产模块中,我们从未开发任何特定代码就能执行标准的 CRUD 操作。Odoo 框架提供了完成这些操作所需的工具。事实上,得益于经典的 Python 继承,这些操作已经包含在我们的模型中:

from odoo import fields, models

class TestModel(models.Model):
    _name = "test_model"
    _description = "Test Model"

    ...

我们的 class TestModel 继承自 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):
        # Do some business logic, modify vals...
        ...
        # Then call super to execute the parent method
        return super().create(vals)

create() 方法需要使用 model() 装饰器,因为在创建的上下文中,记录集 self 的内容并不相关;但对于其他 CRUD 方法,则不需要该装饰器。

同样需要注意的是,尽管我们可以直接重写 unlink() 方法,但您几乎总是应该编写一个带有 ondelete() 装饰器的新方法来代替。带有此装饰器的方法将在 unlink() 执行期间被调用,并可避免在直接重写 unlink() 时,卸载模型所在模块可能出现的某些问题。

在 Python 3 中,super() 等价于 super(TestModel, self)。当您需要使用修改后的记录集调用父类方法时,可能需要使用后者。

Danger

  • 非常重要的是,务必 调用 super() 以避免破坏调用流程。只有在极少数非常特殊的情况下,您才不需要调用它。

  • 请确保 始终 返回与父类方法一致的数据。例如,如果父类方法返回一个 dict(),您的重写方法也必须返回一个 dict()。

Exercise

为 CRUD 方法添加业务逻辑。

  • 如果房源的状态不是 ‘New’ 或 ‘Cancelled’,则应阻止删除该房源

提示:创建一个带有 ondelete() 装饰器的新方法,并记住 self 可以是包含多条记录的记录集。

  • 在创建报价时,将房源状态设置为 ‘Offer Received’。同时,如果用户试图创建一个金额低于现有报价的报价,则应抛出错误。

提示:property_id 字段在 vals 中可用,但它是一个 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。

Exercise

向 Users 添加一个字段。

  • 向 res.users 添加以下字段:

字段

类型

property_ids

estate.property 中引用销售人员的字段的 One2many 反向字段

  • 为该字段添加一个 domain,使其只列出可用的房源。

在下一节中,让我们把该字段添加到视图中,并检查一切是否运行良好!

视图继承

参考:与此主题相关的文档可在 继承 中找到。

Note

目标:在本节结束时,与某位销售人员关联的可用房源列表应显示在其用户表单视图中

用户

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

将 xpaths 的主体作为兄弟元素插入到匹配元素之后

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>

一个视图继承扩展的示例可在 此处 找到。

Exercise

向 Users 视图添加字段。

在一个新的 notebook 页面中,将 property_ids 字段添加到 base.view_users_form。

提示:一个用户视图继承的示例可在 此处 找到。

由于其模块化理念,继承在 Odoo 中得到了广泛使用。请不要犹豫,阅读相应的文档以了解更多信息!

在 下一章 中,我们将学习如何与其他模块交互。