第13章:继承

Odoo 的一个强大之处在于其模块化。每个模块都专注于一个业务需求,但模块之间也可以相互交互。这对于扩展现有模块的功能非常有用。例如,在我们的房地产场景中,我们希望直接在常规用户视图中显示销售人员的房产清单。

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

Python继承

注解

目标:在本节结束时:

  • 不应该删除不是新建或取消的属性。

取消链接
  • 当一个报价被创建时,物业状态应该变为“已收到报价”

  • 不应该创建一个价格比现有报价更低的报价

创建

在我们的房地产模块中,我们从未需要开发任何特定的内容来执行标准的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)

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

同时需要注意的是,即使我们可以直接覆盖 unlink() 方法,你几乎总是希望 使用装饰器 ondelete() 来编写一个新的方法。 使用这个装饰器标记的方法将在 unlink() 方法调用时被调用,避免了一些在 直接覆盖 unlink() 时可能发生的问题。

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

危险

  • 非常重要的是 始终 调用 super() 以避免中断流程。只有极少数非常特殊的情况下,您不想调用它。

  • 确保始终返回与父方法一致的数据。例如,如果父方法返回一个 dict(),你的覆盖方法也必须返回一个 dict()

Exercise

在CRUD方法中添加业务逻辑。

  • 如果属性的状态不是’New’或’Canceled’,则防止删除该属性

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

  • 在创建报价时,将属性状态设置为“已收到报价”。如果用户尝试创建低于现有报价的报价,则引发错误。

提示: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

向用户添加一个字段。

  • 将以下字段添加到 res.users

字段

类型

属性 ID

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

  • 在字段中添加域,以便仅列出可用的属性。

在下一节中,让我们将该字段添加到视图中并检查一切是否正常!

视图继承

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

注解

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

用户

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

向用户视图添加字段。

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

提示:一个用户视图的继承示例可以在 这里 找到。

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

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