odoo框架之模块继承
odoo框架之模块继承
开篇前准备
# 新建模块 library_member
# 目录结构
library_member
- models
- __init__.py
- views
- __init__.py
- __manifest__.py
一.原模型继承
in-place模型继承, _inherit 属性
# 原模型继承的作用:
# 1. 为模型添加字段
# 2. 修改已有字段
# 继承的结果
# 1. 原模型继承,在原模型上添加修改,
# 2. 重新指定_name属性,生成新的表
为模型添加字段
# 1.将models导入到library_member/__init__.py文件来
from . import models
# 2.在library_member/models/__init__.py添加如下代码
from . import library_book
# 3.创建library_book.py, 继承 library.book模型,
from odoo import fields, models
class Book(models.Model):
# 声明继承,以及要继承的模型
_inherit = 'library.book'
# 添加新的字段
is_available = fields.Boolean('Is Available?')
修改已有字段
继承模型时,可对已有字段叠加修改,也就是说仅需定义要增加或修改的字段属性。
# 目的:
#为isbn字段添加一条提示,说明同时支持10位数的 ISBN(稍后会实现该功能)
#为publisher_id字段添加数据库索引,以提升搜索效率
# 步骤
# 1.编辑library_member/models/library_book.py,加入如下代码
class Book(models.Model):
...
# 重新定义isbn字段
isbn = fields.Char(help="Use a valid ISBN-13 or ISBN-10.")
# index 是添加索引
publisher_id = fields.Many2one(index=True)
二.修改视图和数据
视图继承
表单、列表和搜索视图通过arch XML结构定义.
定位到xml元素的位置后,对其进行修改. 视图中也存在inherit_id属性
<!-- 添加views/book_view.xml文件来继承 Partner 视图 -->
<?xml version="1.0"?>
<odoo>
<record id="view_form_book_extend" model="ir.ui.view">
<field name="name">Book: add Is Available? field</field>
<field name="model">library.book</field>
<field name="inherit_id" ref="library_app.view_form_book" />
<field name="arch" type="xml">
<field name="isbn" position="after">
<field name="is_available" />
</field>
</field>
</record>
</odoo>
注解
### 同一个模块下,xml id 不要重复
# 1. view_form_book_extend 是当前视图的id
# 2. 由于没有生成新的模型所以依旧依赖library.book
# 3. inherit_id 字段,ref的值是 继承这个视图的xml id
# 4. position 指的是位置
# 5. is_available 是新增字段
节点定位的方式以及节点的位置
# 节点位置
inside(默认值):# 在所选节点内添加内容,这一节点应是<group>或<page>一类的容器
after:# 在选定节点之后向父节点添加内容
before:# 在选定节点之前向父节点添加内容
replace:# 替换所选节点。若使用空元素则会删除该元素。Odoo 之后还允许使用其它标记来包裹元素,通过在内容中使用$0来表示被替换的元素。
attributes:# 修改匹配元素属性值。内容中应包含带有一个或多个<attribute name=”attr-name”>value</attribute>元素。如<attribute name=”invisible”>True</attribute>,若不带内容,如<attribute name=”invisible” />则 attribute 会从所选元素中删除。
replace # 可删除 XML 元素,但应避免这么做。这么做会破坏其它依赖所删除节点、将其作为占位符添加元素的模块。一个替代方案是,让该元素不可见。
move # 子元素合并。效果是将子定位符目标节点移到父定位符目录位置。如:<field name="target_field" position="after"><field name="my_field" position="move"/></field>
filed 字段定位节点
<field name="target_field" position="after">
....
</field>
<!-- 当name出现重复时,无法精确定位到节点 -->
xpath 定位节点
# 使用xpath的原因
# 1. 存在没有带唯一值的属性来用作 XML 节点选择器。在所选元素没有 name 属性时可能出现这一情况,如<group>、<notebook>或<page>视图元素。
# 2. 另外就是有多个带有相同 name 属性的元素,比如在看板 QWeb 视图中相同字段可能在同一 XML 模板中被多次包含。
# 3. xpath也有限制
# 例子:
<xpath expr="//field[@name='isbn']" position="after">
<field name="is_available" />
</xpath>
修改数据
# 1. 普通数据记录不同于视图,它没有 XML arch 结构,也不能使用 XPath 来进行扩展。但还是可以通过替换字段值来进行修改。
# 2. <record id="x" model="y">数据加载元素实际是对 y 模型进行插入或更新操作。若不存在记录 x,则被创建,否则被更新/覆盖。其它模块中的记录可通过<module>.<identifier>全局标识符访问,因此可以在我们的模块中重写其它模块中已写入的数据
<!-- 我们将 User 安全组的名称修改为 Librarian,对应修改library_app.library_group_user记录。添加library_member/security/library_security.xml并加入如下代码: -->
<odoo>
<!-- Modify Group name -->
<record id="library_app.library_group_user" model="res.groups">
<field name="name">Librarian</field>
</record>
</odoo>
三.其他模型继承机制
原型继承拷贝功能_inherit
# 即 在继承模型中添加_name属性, 相当于拷贝了一个一模一样的表,添加上新增字段
# 缺点:
# 1.数据结构冗余
代理继承内嵌模型(_inherits)
定义: UML 中这种称作组合(composition)关系:父类无需子类即可存在,而子类必须要有父类才能存在。
在图书项目中,我们要添加一个图书会员模型。会员有会员卡并通过会员卡借阅读书。我们要记录卡号,还要存储email 和地址这类个人信息。Partner 模型已包含联系和地址信息,所以最好是进行复用,而不去创建重复的数据结构。
# 为会员模型创建library_member/models/library_member.py文件并加入如下代码:
from odoo import fields, models
class Member(models.Model):
_name = 'library.member'
_description = 'Library Member'
card_number = fields.Char()
partner_id = fields.Many2one(
'res.partner',
delegate=True, # 复用users的
ondelete='cascade',
required=True)
### 代理继承可通过如下组合来进行替代:
1.父模型中的一个 many-to-one 字段
2.重载 create()方法自动创建并设置父级记录
3.父字段中希望暴露的特定字段的关联字段
# 有时这比完整的代理继承更为合适。例如res.company并没有继承res.partner,但使用到了其中好几个字段。
# 步骤
# 1. 在library_member/model/__init__.py导入新模型 library_member
from . import library_book
from . import library_member
# 2.创建library_member/security/ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_member_user,Member User Access,model_library_member,library_app.library_group_user,1,1,1,0
access_member_manager,Member Manager Access,model_library_member,library_app.library_group_manager,1,1,1,1
# 3.创建library_member/views/library_menu.xml
<odoo>
<act_window id="action_library_member"
name="Library Members"
res_model="library.member"
view_mode="tree,form" />
<menuitem id="menu_library_member"
action="action_library_member"
parent="library_app.menu_library" />
</odoo>
# 4. 创建library_member/views/member_view.xml
<?xml version="1.0" ?>
<odoo>
<record id="view_form_member" model="ir.ui.view">
<field name="name">Library Member Form View</field>
<field name="model">library.member</field>
<field name="arch" type="xml">
<form>
<group>
<field name="name" />
<field name="email" />
<field name="card_number" />
</group>
</form>
</field>
</record>
<record id="view_tree_member" model="ir.ui.view">
<field name="name">Library Member List View</field>
<field name="model">library.member</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="card_number" />
</tree>
</field>
</record>
</odoo>
使用 mixin类继承模型
mixin 是基于 models.Abstract 的抽象的模型(而不是models.Model),它在数据库中没有实际的体现,而是提供功能供其它模型复用(混合 mixed in)
Odoo 插件提供多种 mixin,最常的两种由 Discuss 应用(mail 模块)提供:
# 1. mail.thread提供在许多文档表单下方或右侧的消息面板功能,以及消息和通知相关逻辑。这在我们自己的模型中将经常会添加,下面就来一起学习下
# 2. mail.activity.mixin模型提供待办任务计划
### mail 模块现在通过mail.activity.mixin抽象模型提供Activities任务管理功能
# 步骤
# 1. 'depends': ['library_app', 'mail'], 添加依赖
# 2. mixin 类的继承通过_inherit属性完成,编辑 library_member.py
class Member(models.Model):
_name = 'library.member'
_description = 'Library Member'
_inherit = ['mail.thread', 'mail.activity.mixin']
# 3. 编辑 member_view.xml ,form内添加
<form>
....
<!-- mail mixin fields -->
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
四.继承方法
与python继承相同
# 在library_member/models/library_book.py文件中添加如下方法:
from odoo import api, fields, models
class Book(models.Model):
...
def _check_isbn(self):
self.ensure_one()
isbn = self.isbn.replace('-', '')
digits = [int(x) for x in isbn if x.isdigit()]
if len(digits) == 10:
ponderators = [1, 2, 3, 4, 5, 6, 7, 8, 9]
total = sum(a * b for a, b in zip(digits[:9], ponderators))
check = total % 11
return digits[-1] == check
else:
# 执行父类方法
return super(Book,self)._check_isbn()
五.继承 Web 控制器和模板
继承网页控制器
- 在控制器端添加对查询参数的支持,访问/library/books?available=1过滤出可借阅图书
- 在模板端,添加一个图书不可用的表示
from odoo import http
from odoo.addons.library_app.controllers.main import Books
class BookExtended(Books):
@http.route()
def list(self, **kwargs):
response = super().list(**kwargs)
if kwargs.get('available'):
Book = http.request.env['library.book']
# 新增过滤条件
books = Book.search([('is_available', '=', True)])
response.qcontext['books'] = books
return response
# 注意:
# 它至少需要一个简单的@http.route()装饰器来保持路径活跃。如果不带参数,将会保留父类中定义的路由。但也可以为@http.route() 装饰器添加参数,来重新定义或替换类路由。
继承 QWeb 模板
与视图继承类似,定义继承模板,xpath定位节点
添加library_member/views/book_list_template.xml文件并加入如下代码:
<odoo>
<template id="book_list_extended"
name="Extended Book List"
inherit_id="library_app.book_list_template">
<xpath expr="//span[@t-field='book.publisher_id']" position="after">
<t t-if="not book.is_available">
<b>(Not Available)</b>
</t>
</xpath>
</template>
</odoo>