第 12 章:继承

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_list):
        # Do some business logic, modify vals...
        ...
        # Then call super to execute the parent method
        return super().create(vals_list)

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

还需要注意的是,尽管我们可以直接覆盖 unlink() 方法,但几乎总是应该使用装饰器 ondelete() 编写一个新方法。标记了此装饰器的方法将在 unlink() 期间被调用,并避免在卸载模型模块时因直接覆盖 unlink() 而可能出现的一些问题。

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

危险

  • 始终调用 super() 以避免破坏流程非常重要。只有在极少数特定情况下才不调用它。

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

Exercise

为 CRUD 方法添加业务逻辑。

  • 如果属性的状态不是“新建”或“已取消”,则阻止删除该属性。

提示:使用 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 添加以下字段:

字段

类型

property_ids

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

  • 为该字段添加一个域条件,使其仅列出可用的属性。

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

视图继承

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

注解

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

用户

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 表达式,用于选择父视图中的单个元素。如果没有匹配到元素或匹配到多个元素,则会引发错误。

位置

应用于匹配元素的操作:

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 的模块化概念,继承被广泛使用。如需更多信息,请随时阅读相关文档!

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