第十二章 继承机制

篇别:第一篇 基础知识

本章学习目标

  • 准确区分 经典继承(_inherit)代理继承(_inherits)原型继承(_inherit + _name) 的表结构与使用场景。
  • 能在实际项目中选择 委托继承Many2one 扩展 并说明权衡。
  • 掌握 视图继承Mixin 列表 的写法,并理解 MRO 对方法解析的影响。
  • 能阅读 _inherit技术菜单中的模型继承列表 做升级影响分析。

导读:继承是 Odoo 扩展的核心语法

Odoo 很少通过「改核心源码」交付定制,而是 在独立模块中继承:扩展模型字段/方法、继承视图打补丁、组合 Mixin 获得消息/活动等能力。选错继承类型会导致 表结构冗余、联表性能差或升级困难


12.1 经典继承(_inherit)

12.1.1 知识要点

  • 形式class X(models.Model): _inherit = 'existing.model'(不写新 _name_name 与父一致,依惯例为 扩展同名模型)。
  • 效果:在 同一张业务表 上增加字段与方法(多模块可链式 _inherit,合并为同一模型类)。
  • 适用:给 res.partnersale.order 等标准模型 加字段、改方法、加约束

12.1.2 案例

案例 A:扩展伙伴

from odoo import models, fields

class Partner(models.Model):
    _inherit = 'res.partner'

    library_card = fields.Char('借书证号')
    is_library_member = fields.Boolean('图书馆会员')

案例 B:重写方法(须 super)

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    def action_confirm(self):
        # 前置逻辑
        res = super().action_confirm()
        # 后置逻辑
        return res

案例 C:多模块扩展同一模型

模块 A、B 均 _inherit = 'res.partner' → 最终 res.partner 合并字段与方法;方法顺序 由依赖与 MRO 决定。

12.1.3 截图占位

图 12-1 技术 → 模型 res.partner 的继承列表

12.1.4 本节练习

  1. 实操:扩展 sale.order,增加 library_noteText),并在销售订单表单 视图继承 中显示(可与 8.14 联做)。
  2. 简答:经典继承新增字段在数据库中落在哪张表?
  3. 判断:两个模块定义同名方法且都未 super,最终行为一定可预测。( )

参考答案提示:2. res_partner(该模型对应表)。3. 错,依赖 MRO 与模块顺序,需规范 super。


12.2 代理继承(_inherits)

12.2.1 知识要点

  • 形式_inherits = {'parent.model': 'delegate_field'},子模型有 Many2one 委托字段 指向父记录。
  • 效果:访问子记录时 自动委托 读取/写入父模型字段(具体规则以 ORM 为准);子表 + 父表 存储。
  • 典型product.productproduct.templateHR 版本 中部分 employee/user 关系也曾用类似模式。
  • 与 Many2one_inherits 由框架维护 委托语义(创建/复制/cascade 行为),手写 Many2one 需自己处理同步与 ondelete。

12.2.2 案例(简化示意)

class LibraryMember(models.Model):
    _name = 'library.member'
    _description = '图书馆会员'
    _inherits = {'res.partner': 'partner_id'}

    partner_id = fields.Many2one(
        'res.partner', required=True, ondelete='cascade',
    )
    level = fields.Selection([('normal', '普通'), ('vip', 'VIP')])

新建 library.member 时通常会 同时创建 关联 res.partner(框架行为需对照文档与测试验证)。

12.2.3 截图占位

图 12-2 product 变体与产品模板的委托关系示意

12.2.4 本节练习

  1. 简答_inherits 与「仅 Many2one + related 字段」在 创建流程 上的差异?
  2. 实操:画出 library.memberres.partnerER 简图(两表 + 外键)。
  3. 情景:会员只想用 partner 表即可,是否需要单独 library.member?决策依据?

参考答案提示:1. _inherits 由 ORM 统一创建/委托;手写需自己封装 create/write。


12.3 原型继承(_inherit + _name)

12.3.1 知识要点

  • 形式:新 _name,且 _inherit = 'base.model'
  • 效果复制/扩展 基模型结构得到 新模型,通常有 独立数据库表;适合 业务分叉(如 mail.message → 特定子类,具体以官方模型为准)。
  • 注意:与「经典继承」区别在 是否有新 _name 与新表

12.3.2 案例

class Animal(models.Model):
    _name = 'zoo.animal'
    _description = '动物'
    name = fields.Char(required=True)

class Mammal(models.Model):
    _name = 'zoo.mammal'
    _inherit = 'zoo.animal'
    gestation_days = fields.Integer('妊娠期(天)')

12.3.3 截图占位

图 12-3 技术菜单中 zoo.mammal 与 zoo.animal 两张模型

12.3.4 本节练习

  1. 判断:原型继承后两张表完全独立。( )
  2. 简答:何时用原型继承而不用「独立模型 + Many2one」?
  3. 实操:查 _table 或数据库 \d 确认 zoo_mammal 是否含 name 列(以实际 ORM 为准)。

参考答案提示:1. 通常有新表且字段合并策略依版本;需实测。2. 需共享大量基类行为/视图时代码复用更高。


12.4 视图继承(新增)

12.4.1 知识要点

第八章 8.14 一致:inherit_idxpathpriority

模型继承视图继承 常同时使用:Python 加字段,XML 把字段摆到界面。

12.4.2 案例

<record id="view_order_form_inherit_library" model="ir.ui.view">
  <field name="name">sale.order.form.inherit.library</field>
  <field name="model">sale.order</field>
  <field name="inherit_id" ref="sale.view_order_form"/>
  <field name="priority">15</field>
  <field name="arch" type="xml">
    <xpath expr="//field[@name='partner_id']" position="after">
      <field name="library_note"/>
    </xpath>
  </field>
</record>

12.4.3 截图占位

图 12-4 多条视图继承与 priority

12.4.4 本节练习

  1. 实操:两个模块继承同一表单:A priority=10,B priority=20,观察字段最终顺序。
  2. 简答:视图继承与 重写整视图(复制 arch)相比的升级优劣?

参考答案提示:2. 继承更抗上游变更;整视图易碎但自由度大。


12.5 Mixin 继承模式(新增)

12.5.1 知识要点

  • 形式_inherit = ['mail.thread', 'mail.activity.mixin', 'my.abstract.mixin']
  • Mixin:多为 AbstractModel_abstract = True),不单独建业务表,被「混入」目标模型。
  • MRO:多 mixin 时方法解析 从左到右;冲突应用 super() 显式协作,避免覆盖丢失。

12.5.2 案例

class Loan(models.Model):
    _name = 'library.loan'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    name = fields.Char()
    book_id = fields.Many2one('library.book')

获得:chatter、关注者、活动、消息子类型等。

12.5.3 截图占位

图 12-5 模型文件中多 mixin 列表

12.5.4 本节练习

  1. 简答:Mixin 方法冲突时,推荐的重构手段?
  2. 实操:临时注释一个 mixin,观察 数据库列视图报错(理解依赖)。
  3. 判断mail.thread 可以不继承任何模型单独安装使用。( )

参考答案提示:1. 抽第三种 mixin 或调整 _inherit 顺序并统一 super。3. 错,需混入具体模型。


本章综合练习

  1. 设计library.member 委托 res.partner,会员等级 level 在 partner 表单通过 视图继承 展示;写出 Python + XML 提纲。
  2. 对比表:经典 / 委托 / 原型 三种继承在 表数量、查询 JOIN、适用场景、升级风险 四列填表。
  3. 阅读:打开 product.product 源码片段,标出 _inherits_inherit(若有)并写一句说明。
  4. 拓展:官方文档「Inheritance」中关于 Delegate 的警告(若有)摘录一条。

本章对应白皮书目录:第十二章 继承机制。