第八章 继承
不论是后端模型还是前端的Widget, 抑或是组成报表的Qweb和视图, 我们推荐的核心原则就是能在不动原生源码的情况下,就不要动原生的代码。这可以说是Odoo开发中的最佳实践之一。
直接改动源代码带来的风险有很多, 比如原生代码的升级会导致改动代码的丢失, 开发人员的流动也会增加后续维护者的成本, 错误的源代码修改更有可能引起潜在的问题从而导致奇奇怪怪的问题出现。
因此 ,我们推荐使用继承的方式来进行二次开发, 基本上Odoo中所有的对象均可以通过继承的方式进行拓展。
模型的继承
这里我们还是沿用前面的书店模块,之前我们有了图书这个模型,现在我们希望书店能够上架一种新的图书-电子书,电子书区别于纸质书的特点就是无需纸张,但是需要规定电子书的格式,比如常见的epub,mobi,awz3等等,因此,我们需要在前边的图书模型上进行拓展,又不能影响既有图书的属性,怎么办,这就需要用到了继承,我们可以新建一个电子图书的模型,继承自图书,新增电子书属性。
继承的方式
odoo的继承不是像python的继承直接在class的括号中添加继承类,而是在类属性_inherit中添加,如上面的例子,代码就可以写成下面的样子:
class eBook(models.Model):
_inherit = "book_store.book"
etype = fields.Selection(selection=[('mobi', 'Mobi'), ('epub', 'Epub'), (
'awz', 'Awz3')], string='电子书格式', default='epub')
需要注意的是,上面的这种方式,在实际数据库中还是同一张表的扩展,也就是说,这种方式不能实现我们前面所说的效果。这种继承方式的使用场景是继承自odoo官方模块,为原有模块进行扩展而不修改源码,这样可以保持官方代码的干净,避免带来污染。如果想要使用另一张表,则需要给新模型起一个新的名字:
class eBook(models.Model):
_inherit = "book_store.book"
_name = "book_store.ebook"
etype = fields.Selection(selection=[('mobi', 'Mobi'), ('epub', 'Epub'), (
'awz', 'Awz3')], string='电子书格式', default='epub', help='')
这样的数据库中就会多出一张表,包含所有的book_store_book表中的字段,并且还有ebook对象新增的字段:
两种方式各有优缺点,请读者根据实际情况灵活运用。
多继承
Odoo也支持多继承,方法是把_inherit的值变成一个要继承的列表:
class sBook(models.Model):
_inherit = ["book_store.book","book_store.ebook"]
_name = "book_store.sbook"
etype = fields.Selection(selection=[('mobi', 'Mobi'), ('epub', 'Epub'), (
'awz', 'Awz3')], string='电子书格式', default='epub', help='')
继承的顺序从左到右
委托继承
Odoo还有另外的一种继承方式,不指明要继承的对象,而是指定本对象的某个Many2one的字段继承自一个其他对象,被继承的该对象的字段自动加载到本对象中,并且当其中任何一方的值发生变化时,都会同步到另一方中,这里把这种继承方式命名为委托继承。说的比较抽象,接下来我们举个栗子:
新建对象会员图书(sbook),但是此对象不继承任何一个对象,只是在属性中添加一个对电子图书(ebook)的Many2one关联字段,并在_inherits属性中声明这个字段:
class sBook(models.Model):
_name = "book_store.sbook"
_inherits = {'book_store.ebook': 'ebook_id'}
ebook_id = fields.Many2one(
'book_store.ebook', string='ebook', ondelete='restrict', required=True, help='')
_inherits属性是个字典,key为Many2one字段中外关联的对象名,value为Many2one的字段名。然后我们为sBook对象编写一个视图:
<record model="ir.ui.view" id="book_store.slist">
<field name="name">会员图书列表</field>
<field name="model">book_store.sbook</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="author"/>
<field name="date"/>
<field name="price"/>
</tree>
</field>
</record>
<record id="book_store.sbook_form" model="ir.ui.view">
<field name="name">会员图书</field>
<field name="model">book_store.sbook</field>
<field name="arch" type="xml">
<form string="图书详情" class="">
<sheet>
<h1>
<field name="name" invisible="0"/>
</h1>
<group>
<field name="ebook_id"/>
<field name="author" invisible="0"/>
<field name="date" invisible="0"/>
<field name="price" invisible="0"/>
<field name="ref" invisible="0"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="book_store.saction_window">
<field name="name">会员图书</field>
<field name="res_model">book_store.sbook</field>
<field name="view_mode">tree,form</field>
</record>
然后升级模块,可以看到如下图:
好像没有什么特别之处。。。别急,我们来修改一下会员图书的名字,将其改为"流浪地球-最终版",并把作者改回"刘慈欣":
发现了没有,ebook属性的名字,随着我们会员图书的名字的改名而改名,并且ebook的作者字段也变成了我们所选的"刘慈欣"。
这就是Odoo中比较著名的委托继承,典型的应用就是官方模块中的产品模板和产品的关系,在Odoo原生模块中,有一个对象叫做product.template,顾名思义就是产品模板,指的是一系列产品的通用模板属性。而在销售、采购和仓储的实际运用中,使用的是叫product.product的对象,这个才是真正的产品。product.product和product.template的关系就是我们上面所说的属性继承的关系,product.pruduct中一个many2one的字段product_tmpl_id关联的就是product.template,当product.template中的属性发生变化时,没有被重写(注意,product.product对象中有些字段覆盖了product.template中的字段)的属性就会跟着变化,形成了一种有趣的引用关系。关于这点,读者可以到产品中修改一下属性进行体会。
关于委托继承的更多内容, 参见第二部分第二章模型中的相关章节.
视图的继承
视图当然也是可以继承的, 视图继承的主要方式就是通过inherit_id和xpath实现的. 我们先来了解一下Xpath这种技术.
XPATH
我们的视图是XML文件,既然要继承,就势必会涉及到XML中标签定位的问题,这就是我们今天的主角——XPATH。
XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。—— 引自百度百科
这里我们简单介绍一下XPATH的语法,我们常用的几种表达式如下:
- / :从根节点选取
- //:从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
- .: 选取当前节点
- ..: 选取当前节点的父节点
- @: 选取属性为xx的节点
举例来说,我们要选book_store模块中form视图中的author字段,那么xpath就可以写成下面这样:
//field[@name='author']
选取当前节点的父节点,可以这么写:
//field[@name='author']/..
注意,..后面不要加/了。
谓语
XPATH还支持通过谓语来定位相对位置,例如:
/class/student[1] #选取属于 class 子元素的第一个 student 元素。
常见的谓语列表:
- last(): 最后一个元素
- last()-1: 倒数第二个元素
- position()<3: 前两个元素
- @gender:拥有属性gender的元素
- ID<50: ID小于50的元素
- student[ID<50]/name: student 元素的所有 name 元素,且其中的 ID 元素的值须小于 35
XPATH语法有很多,这里不一一介绍了,需要说明的是学号XPATH不仅对写odoo的模块有用,对于今后的其他框架的开发也是大有益处的。读者可以自行搜素XPATH相关的资料进行学习。
视图的继承
视图的继承要掌握以下两个重点:
- 要继承的视图的id
- 对目标字段的修改
视图的继承,很简单,只需要在xml文件中添加要给inherit_id字段,并且ref到父视图的xml_id就可以了。
例如,我们在book_store模块中新建一个类拓展原有book.store对象,新增一个分类字段,并显示在form视图的ref字段后面。
class Book(models.Model):
_inherit = "book_store.book"
category = fields.Char("分类")
视图的继承代码:
<record id="book_store_book_inherit" model="ir.ui.view">
<field name="name">继承视图</field>
<field name="model">book_store.book</field>
<field name="inherit_id" ref="book_store.book_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='ref']" position="after">
<field name="category"/>
</xpath>
</field>
</record>
升级模块后就可以看到我们的新增加的字段了:
继承的操作
xpath中的position操作,有以下几种:
- inside: 内嵌到节点中
- after: 在节点后添加
- before: 在节点前添加
- replace: 替换掉节点
- attributes: 更新属性
- move: 移动节点
前面的示例中,我们展示了after的用法,before与之相反,是在目标节点的前面添加。如果我们想要替换到原有的节点,比如销售模块中的地址排列,就可以使用replace来完全重写这个节点的代码。
下面重点介绍一下attributes的用法,attributes提供了一种让我们可以修改节点属性的方法,例如,我希望将字段ref的标签改为中文的“参考”,那么我们就可以这么写XPATH:
<xpath expr="//field[@name='ref']" position="attributes">
<attribute name="string"/>参考</attribute>
</xpath>
另外, position还支持move操作, move操作是从12.0引入的一个新的操作方式, 它的作用是调整节点在视图中的位置, 这使得我们将节点排序成为了可能.
<xpath expr="//@target" position="after">
<xpath expr="//@node" position="move"/>
</xpath>
或者
<field name="target_field" position="after">
<field name="my_field" position="move"/>
</field>
上面的例子中, 字段my_field将会被移动到target_field字段的后面.
字段继承的另外一种方式
除了XPATH外,对于field节点,我们有一种简化的写法:
<record id="book_store_book_inherit" model="ir.ui.view">
<field name="name">继承视图</field>
<field name="model">book_store.book</field>
<field name="inherit_id" ref="book_store.book_form"/>
<field name="arch" type="xml">
<field name="ref" position="after">
<field name="category"/>
</field>
</field>
</record>
这种写法与XPATH的效果完全一样。
继承的优先级
当一个视图被多个视图继承时,页面最终渲染的效果是这些视图叠加的效果。在视图的对象ir.ui.view种有一个字段用于标识视图的优先级,这个字段就是priority,当priority的值越小,它的优先级就越高。
想要查看本页面有哪些视图继承,可以在debug模式下,打开右上角的查看视图-表单(树形等)查看:
总结
本章主要介绍了模型和视图的继承, 其实像qweb和widget等对象都是可以继承的,继承技术是odoo开发过程中使用频率非常高的技术之一。