1. 继承的类型
继承机制是Odoo一个非常重要的功能,继承其他模块中定义的功能使我们可以无需修改底层对象就可以为我们的模块添加新的功能。比如添加字段或方法,修改已有字段或继承已有方法来执行新的逻辑。根据官方文档,Odoo提供三种类型的继承,分别是:
- 类继承(扩展)
- 原型继承
- 代理继承,也称委托继承
2. 类继承
类继承(扩展),可用于对已有模型添加新字段或方法。
2.1 例子
- 2.1.1 添加字段
class ResPartner(models.Model):
_inherit = 'res.partner'
authored_book_ids = fields.Many2many('library.book', string='著作',)
count_books = fields.Integer('著书数量', compute='_compute_count_books')
- 2.1.2 添加方法
class ResPartner(models.Model):
# ...
@api.depends('authored_book_ids')
def _compute_count_books(self):
for r in self:
r.count_books = len(r.authored_book_ids)
2.2 原理
- 在类继承中,继承模型从原有模型中获取所有方法和字段,即原有模型中的所有函数和字段都可以从继承模型中访问。
- 类继承不会创建新的模型,能够直接修改模型定义,新增字段将直接体现在原有模型中,已有字段也可以进行增量修改(如果该字段在父类中已存在,仅修改在继承类中声明的属性,其它的保持原有父类中的内容不变。),或对原模型中的函数进行重写和修改。
- 在数据库层,ORM对同一张数据表添加字段,没有新表生成。
2.3 注
通过_inherit实现类继承,也可以将父级模型的功能copy到一个全新的模型中,通过添加一个带有不同标识符的_name属性来实现。
示例:
class LibraryMember(models.Model):
_inherit = 'res.partner'
_name = 'library.member'
这种做法中,新模型有自己的数据表,包含完全独立于res.partner原有模型的自身数据。因其仍继承Partner模型,此后的任意修改也会影响到新模型。在官方文档中,这被称为原型继承,但在实践中很少使用,原因在于代理继承通常可以更高效的方式满足这一需求,也无需复制数据结构。
3. 原型继承
原型继承,用于从已有模型中copy整个定义。
3.1 例子
比如生成对 student 模型的拷贝,操作步骤依次为:
(1)在 models/ 目录下新建一个 student_copy.py 文件。
(2)在 student_copy.py 文件中添加如下代码:
from odoo import models, fields, api
class StudentCopy(models.Model):
_name = 'student.copy'
_inherit = 'student'
_description = "Student's Copy"
(3)在models/init.py 文件中导入一条新的文件引用。
from . import student_copy
3.2 原理
- 对现有模块完整的复制,有单独的数据库表。本例中,Odoo会拷贝 student 模型的定义,创建一个新模型 student.copy。新的 student.copy 模型有一个包含自身数据的数据表,与原有模型 student 完全独立开来。
- 原型继承通过同时使用 _name 和 _inherit 属性来实现。在模型中使用这两个属性时,Odoo会拷贝 _inherit 的模型定义,创建一个带有 _name 属性的新模型。
- 原型继承复制父类中的所有属性,会拷贝字段、属性和方法。如果想在子类中进行修改,只需在子类中添加新的定义。例如,student 模型有一个_name_get方法。如果希望在子类中使用不同版本的_name_get,需要在 student.copy模型中重新定义该方法。
4. 代理继承
有时我们希望在不修改原有模型的基础上,根据原有模型创建一个新模型来使用原有功能。这时可以通过原型继承来copy原有模型,但这会导致重复的数据结构。如果希望在不复制数据结构的前提下copy原有模型,就需要用到Odoo的代理继承。
4.1 例子
要在图书馆用户中添加会员,会员需要保留普通用户的所有数据,同时也要新增一些会员信息,比如:起始日期、结束日期、会员卡号。向原有模型中添加这些新字段或许可行,但是普通用户不需要这些信息,这时候就可以用代理继承。
(1)添加新模型来继承原有模型:
class LibraryMember(models.Model):
_name = 'library.member'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one('res.partner',ondelete='cascade')
(2)添加针对会员的字段:
# class LibraryMember(models.Model):
# ...
date_start = fields.Date('起始日期')
date_end = fields.Date('结束日期')
member_number = fields.Char()
4.2 原理
-
代理继承使用的不是 _inherit,而是 _inherits 类属性。_inherits模型属性设置我们想要继承的父级模型。本例中只有一个 res.partner 模型。它的值是一个键值对字典,键是被继承的模型,而值是用于关联它们的字段名。本例中,partner_id 是用于关联父级模型Partner的字段。
-
支持多态继承,可以从两个或多个其它的模型中进行继承。
-
注意:代理继承仅用于字段,而不能用于方法。因此,如果原有模型有一个 do_something() 方法,新模型不会自动继承它。
-
新建会员时,数据库会发生什么?
- 在 res_partner 表中新建一条记录
- 在 library_member 表中新建一条记录
- library_member 表中的 partner_id 字段设置为所创建的 res_partner 记录的 id
会员记录自动关联到一个新的 Partner(成员) 记录。它仅是一个 many-to-one关联,新的Partner记录会自动和新的会员记录一同创建。所有的会员都是成员(Partner),但只有部分成员是会员。
-
删除是会员的成员时会发生什么?
可通过关联字段的ondelete值来决定。对 partner_id 这里使用了cascade,表示删除成员会同时删除对应的会员。
4.3 简洁做法
继承代理有一个快捷方式,代替创建一个 _inherits 字典,可以在 Many2one 字段定义中使用 delegate=True 属性。这和 _inherits 选项的功能完全一样,其主要优点是更为简洁。
示例:
class LibraryMember(models.Model):
_name = 'library.member'
partner_id = fields.Many2one('res.partner', ondelete='cascade', delegate=True)
date_start = fields.Date('起始日期')
date_end = fields.Date('结束日期')
member_number = fields.Char()
5. 总结
在模块定制过程中,有许多情况需要继承一些模型以实现特定的情况。下图简要总结了上文中三种继承类型的区别与联系:
- 类继承
- 用于添加 features
- 与现有视图兼容的新类
- 存储在同一表中
- 原型继承
- 用于 copy features
- 新类被现有视图忽略
- 存储在不同的表中
- 代理继承
- 支持多态继承
- 新类被现有视图忽略
- 存储在不同的表中
- 新实例包含一个嵌入式的模型1的实例
转载自:https://zhuanlan.zhihu.com/p/395341464