本文参考:ORM API — Odoo 18.0 documentation
Odoo 模型继承与扩展机制解读
Odoo 提供了三种主要方式来扩展模型,每种方式都有特定的使用场景和机制:
- 传统继承(Classical Inheritance)
新模型基于现有模型创建,继承原始模型的字段、方法和元信息,但原模型保持不变。 - 模型扩展(Extension)
直接扩展现有模型,将新字段和方法添加到现有模型中。 - 委托继承(Delegation Inheritance)
通过 _inherits 实现字段委托,但不继承方法。
1. 传统继承(Classical Inheritance)
特点
- 创建一个全新的模型,基于现有模型。
- 使用 _name 定义新模型名,使用 _inherit 指定父模型。
- 可覆盖父模型的方法,同时继承其字段和其他方法。
代码示例
class Inheritance0(models.Model): _name = 'inheritance.0' _description = 'Inheritance Zero' name = fields.Char() def call(self): return self.check("model 0") def check(self, s): return "This is {} record {}".format(s, self.name) class Inheritance1(models.Model): _name = 'inheritance.1' _inherit = 'inheritance.0' _description = 'Inheritance One' def call(self): return self.check("model 1")
使用示例
a = env['inheritance.0'].create({'name': 'A'}) b = env['inheritance.1'].create({'name': 'B'}) print(a.call()) # 输出: "This is model 0 record A" print(b.call()) # 输出: "This is model 1 record B"
解读
- Inheritance1 继承了 Inheritance0 的所有字段和方法。
- Inheritance1 重写了 call 方法,但仍然可以调用 check 方法。
2. 模型扩展(Extension)
特点
- 不创建新模型,而是直接扩展现有模型。
- _inherit 指定扩展的模型,无需 _name。
- 常用于添加新字段、方法或修改模型行为。
代码示例
class Extension0(models.Model): _name = 'extension.0' _description = 'Extension Zero' name = fields.Char(default="A") class Extension1(models.Model): _inherit = 'extension.0' description = fields.Char(default="Extended")
使用示例
record = env['extension.0'].create({}) print(record.read()[0]) # 输出: {'name': "A", 'description': "Extended"}
解读
- Extension1 向 extension.0 模型中添加了 description 字段。
- 原始模型的字段和新字段均可用。
3. 委托继承(Delegation Inheritance)
特点
- 使用 _inherits 将字段委托给其他模型。
- 被委托的字段在父模型中可直接访问。
- 不继承被委托模型的方法。
代码示例
class Screen(models.Model): _name = 'delegation.screen' size = fields.Float(string='Screen Size in inches') class Keyboard(models.Model): _name = 'delegation.keyboard' layout = fields.Char(string='Layout') class Laptop(models.Model): _name = 'delegation.laptop' _inherits = { 'delegation.screen': 'screen_id', 'delegation.keyboard': 'keyboard_id', } name = fields.Char(string='Name') maker = fields.Char(string='Maker') screen_id = fields.Many2one('delegation.screen', required=True, ondelete="cascade") keyboard_id = fields.Many2one('delegation.keyboard', required=True, ondelete="cascade")
使用示例
screen = env['delegation.screen'].create({'size': 13.0}) keyboard = env['delegation.keyboard'].create({'layout': 'QWERTY'}) laptop = env['delegation.laptop'].create({ 'screen_id': screen.id, 'keyboard_id': keyboard.id, }) print(laptop.size) # 输出: 13.0 print(laptop.layout) # 输出: "QWERTY" laptop.write({'size': 14.0})
解读
- Laptop 模型通过 _inherits 委托字段到 Screen 和 Keyboard。
- Laptop 可以直接访问 Screen 和 Keyboard 的字段值,但无法继承它们的方法。
4.模型扩展和委托继承的区别
区别总结
维度 | 模型扩展 | 委托继承 |
---|---|---|
作用对象 | 直接修改目标模型 | 将字段委托到其他模型 |
字段继承 | 可新增字段或修改现有字段 | 通过外键关系访问委托模型的字段 |
方法继承 | 支持重写目标模型的方法 | 不继承委托模型的方法 |
数据库结构 | 不创建新表,直接修改原表 | 创建新表并通过 Many2one 外键关联委托模型 |
对原模型的影响 | 会影响原模型的所有实例 | 对原模型无直接影响 |
适用场景 | 扩展现有模型的功能(如添加字段或自定义行为) | 将其他模型的字段组合到新模型中(如表示“包含”关系的场景) |
总结类比
- 模型扩展类似于“在现有房子上加建一层”。
- 委托继承类似于“在新房子中安装了现成的家具”,可以用,但不改造家具本身。
5. 选择建议
- 选择模型扩展:
- 需要在现有模型中增加或重写字段、方法时。
- 修改后的功能需要作用于整个系统的相关模块时。
- 对模型的需求较为简单。
- 选择委托继承:
- 当前模型需要“组合”其他模型的字段,但原模型不能修改时。
- 需要更高灵活性且对其他模型无侵入式影响时。
- 适合表示“有一个”关系(has-a)的场景,例如:笔记本电脑“有一个”屏幕,而不是“是一个”屏幕。
6.字段的增量定义
特点
- 可以通过在子类中重新定义字段来扩展其属性。
- 必须保持字段类型一致。
代码示例
class First(models.Model): _name = 'foo' state = fields.Selection([('draft', 'Draft'), ('done', 'Done')], required=True) class Second(models.Model): _inherit = 'foo' state = fields.Selection(help="Blah blah blah")
解读
- Second 扩展了 state 字段,为其添加了提示信息(help 属性)。
7.错误处理
常见 Odoo 异常类型
异常类型 | 描述 | 示例 |
---|---|---|
UserError | 用户操作无意义时抛出,类似 HTTP 400。 | 提交状态不匹配的数据。 |
RedirectWarning | 显示警告信息的同时,可重定向用户到某操作。 | 操作需用户手动确认时。 |
AccessDenied | 登录密码错误。 | 尝试使用错误的密码登录。 |
AccessError | 访问权限错误。 | 访问用户无权限查看的记录。 |
CacheMiss | 缓存中缺少值。 | 访问已被刷新缓存的字段值。 |
MissingError | 记录不存在。 | 操作已删除的记录。 |
ValidationError | 违反约束。 | 创建重复登录名的用户。 |
8.总结
Odoo 的模型继承机制提供了灵活的扩展能力:
- 传统继承:用于创建基于现有模型的新模型。
- 模型扩展:用于修改或增强现有模型。
- 委托继承:用于将字段委托到其他模型,实现“组合而非继承”。
合理选择继承方式和错误处理机制,可以显著提高模块开发的效率和可靠性。
Odoo 模型继承与扩展机制解读