odoo 之 结构化应用数据
一.模型深入了解
模型是 Odoo 框架的核心,它们描述应用的数据结构,是应用服务和数据库存储之间的桥梁
模型类型:普通(regular)、临时(transient)和抽象(abstract)类型。
模型属性
### 常用属性
_name # 是我们创建的 Odoo 模型的内部标识符,在创建新模型时为必填。
_description #是对用户友好的模块记录标题,在用户界面中查看模型时显示。可选但推荐添加。
_order # 设置浏览模型记录时或列表视图的默认排序。其值为 SQL 语句中 order by 使用的字符串,所以可以传入符合 SQL 语法的任意值,它有智能模式并支持可翻译及many-to-one字段名。
如:
class Book(models.Model):
_name = 'library.book'
_description = 'Book'
_order = 'name, date_published desc'
### 高级属性
_rec_name # 在从关联字段(如many-to-one关联)中引用时作为记录描述。默认使用模型中常用的 name字段,但可以指定任意其它字段。
_table # 是模型对应的数据表名。默认表名由 ORM 通过替换模块名中的点为下划线来自动定义,但是可通过该属性指定表名。
_log_access=False # 用于设置不自动创建审计追踪字段:create_uid, create_date, write_uid和write_date。
_auto=False # 用于设置不自动创建模型对应的数据表。如有需要,可通过重载init()方法来创建数据库对象:数据表或视图。
模型和 Python 类
Odoo的模型保存在中央注册表(central registry)中,可通过 env 环境对象(老 API 中称为 pool)获取
它是一个数据库保存所有可用模型类引用的字典,其中的词条可通过模型名引用 。
具体来说,模型方法中的代码可使用self.env[‘library.book’]来获取表示 library.book模型的模型类
# 模型名非常重要,因为它是访问该注册表的关键。模型名的规则是以点号连接的小写单词 . 如library.book或library.book.category
# 由于历史原因,有些内核模型没有遵循这一规则,如res.users。
# 1. 模型名必须全局唯一,因此第一个单词应使用模块关联的主应用对应.以图书应用来说,模型的前缀名使用library. 其它内核模块如 project,crm和sale
# 2. 另一方面 Python 类仅为所声明文件本地内容,名称仅需在代码文件中唯一即可。因为类名不会与其它模块中的类产生冲突,也就不需为其添加主应用相关的前缀
# 3. 类的命名遵守驼峰原则CamelCase,与pep8规范一致
临时(Transient)模型和抽象模型
# 临时模型继承models.TransientModel类
1. 作用: 用于向导式的用户交互 , 数据会临时性的存储
2. 清理: 定期运行清空job来清楚这些表中的老数据.
# 抽象模型继承models.AbstractModel类
1. 不带有数据存储, 抽象模型可用于功能集,配合odoo继承功能的其它模型使用
2. 比如:mail.thread是Discuss应用种的一个抽象模型,用于为其它模型添加消息和follower功能
检查已有模型
python类创建的模型和字段,在用户界面中有自己的元标签
Settings > Technical > Database Structure > Models,这里有数据库中的所有模型。点击列表中的模型会打开详情表单
# 上图中在右上角 In Apps字段中可以看到library.book模型的定义来自library_app和library_member两个模块。
# 下方区域中还有几个包含附加信息的标签
Fields可快速查看模型字段
Access Rights是授予不同权限组的访问控制规则
Views显示模型所带的视图列表
# 开发者可通过 View Metadata选项查看模型的外部标识符。
如: library.book模型的外部标识符为model_library_book
在定义安全访问控制列表经常在 CSV 文件中使用到这些XML ID。
二.模型字段深入了解
基本字段类型
#####
Char(string) # 是一个单行文本,唯一位置参数是string字段标签。
Text(string) # 是一个多行文本,唯一位置参数是string字段标签。
Selection(selection, string) # 是一个下拉选择列表。选项位置参数是一个[(‘value’, ‘Title’),]元组列表。元组第一个元素是存储在数据库中的值,第二个元素是展示在用户界面中的描述。该列表可由其它模块使用selection_add关键字参数扩展。
Html(string) # 存储为文本字段,但有针对用户界面 HTML 内容展示的特殊处理。出于安全考虑,该字段会被清洗,但清洗行为可被重载。
Integer(string) # 仅需字段标题字符串参数。
Float(string, digits) # 带有第二个可选参数digits,该字段是一个指定字段精度的(x,y)元组,x 是数字总长,y 是小数位。
Monetary(string, currency_field) # 与浮点字段类似,但带有货币的特殊处理。第二个参数currency_field用于存储所使用货币,默认应传入currency_id字段。
Date(string)和Datetime(string) # 字段只需一个字符串文本位置参数。
Boolean(string) # 的值为True 或False,可传入一个字符串文本位置参数。
Binary(string) # 存储文件类二进制文件,只需一个字符串文本位置参数。它可由Python使用 base64编码字符串进行处理。
##### 文本字符串 Char Text Html的特有属性
size # (Char)设置最大允许尺寸。无特殊原因建议不要使用,例如可用于带有最大允许长度的社保账号。
translate # 使用得字段内容可翻译,带有针对不同语言的不同值。
trim默认值为 True, # 启动在网络客户端中自动去除周围的空格。可通过设置trim=false来取消。
常用字段属性
tring # 是字段的默认标签,在用户界面中使用。除Selection和关联字段外,它都是第一个位置参数,所以大多数情况下它用作关键字参数。如未传入,将由字段名自动生成。
default # 设置字段默认值。可以是具体值(如 active字段中的default=True),或是可调用引用,有名函数或匿名函数均可。
help # 提供 UI 中鼠标悬停字段向用户显示的提示文本。
readonly=True #
会使用户界面中的字段默认不可编辑。在 API 层面并没有强制,模型方法中的代码仍然可以向其写入。仅针对用户界面设置。
required=True # 使得用户界面中字段默认必填。这通过在数据库层面为列添加NOT NULL 约束来实现。
index=True # 为字段添加数据库索引,让搜索更快速,但同时也会部分降低写操作速度。
copy=False # 让字段在使用 ORM copy()方法复制字段时忽略该字段。除 to-many 关联字段外,其它字段值默认会被复制。
groups # 可限制字段仅对一些组可访问并可见。值为逗号分隔的安全组XML ID列表,如groups=’base.group_user,base.group_system’。
states # 传入依赖 state字段值的 UI 属性的字典映射值。可用属性有readonly, required和invisible,例如states={‘done’:[(‘readonly’,True)]}。
##注意
states 字段等价于视图中的 attrs 属性。同时注意视图也支持 states 属性,但用途不同,传入逗号分隔的状态列表来控制元素什么时候可见。
# 例子
name = fields.Char(
'Title',
default=None,
index=True,
help='Book cover title',
readonly=False,
required=True,
translate=False,
)
# default属性 是固定值, 或者引用函数来计算默认值. 对于简单的运算,使用lambda即可. 如下:
last_borrow_date = fields.Datetime(
'Last Borrowed On',
default=lambda self: fields.Datetime.now(),
)
# 默认值也可以是一个函数引用,或者待定义函数名字符串
last_borrow_date = fields.Datetime(
'Last Borrowed On',
default='_default_last_borrow_date',
)
def _default_last_borrow_date(self):
return fields.Datetime.now()
### 当模块数据结构在不同版本种,下列两个属性非常重要
deprecated=True # 在字段被使用时记录一条 warning 日志
oldname=’field’ # 是在新版本中重命名字段时使用,可在升级模块时将老字段中的数据自动拷贝到新字段中
特殊字段名
出于特殊目的作为 ORM 保留字,也是默认字段
# 只要模型中没有设置 _log_access=False , 都会在新模型中自动创建
create_uid为创建记录的用户
create_date是记录创建的日期和时间
write_uid是最后写入记录的用户
write_date是最后修改记录的日期和时间
### 一些内置 API 功能默认需要一些指定字段名。避免在不必要的场合使用这些字段名会让开发更轻松。其中有些字段名被保留并且不能在其它地方使用
name # (通常为 Char)默认作为记录的显示名称。通过是一个 Char,但也可以是 Text 或Many2one字段类型。用作显示名的字段可修改为_rec_name模型属性。
active # (Boolean型)允许我们关闭记录。带有active=False的记录会自动从查询中排除掉。可在当前上下文中添加{‘active_test’: False} 来关闭这一自动过滤。可用作记录存档或假删除(soft delete)。
state # (Selection类型) 表示记录生命周期的基本状态。它允许使用states字段属性来根据记录状态以具备不同的 UI 行为。动态修改视图:字段可在特定记录状态下变为readonly, required或invisible。
parent_id和parent_path Integer和Char型) # 对于父子层级关系具有特殊意义。本文后续会进行讨论。
三.模型关系深入了解
具体的用例就是层级关联,即一个模型中的记录与同模型中的其它记录关联。
Odoo 框架还支持弹性关系,即一个字段可指向其它表中的字段,这称为引用字段。
Many-to-one关联
多对一
# 格式:
publisher_id = fields.Many2one(
'res.partner', string='Publisher')
# 参数:
ondelete # 定义关联记录删除时执行的操作:
set null # (默认值): 关联字段删除时会置为空值
restricted # :抛出错误阻止删除
cascade:# 在关联记录删除时同时删除当前记录
context # 是一个数据字典,可在浏览关联时为网页客户端传递信息,比如设置默认值
domain # 是一个域表达式:使用一个元组列表过滤记录来作为关联记录选项
auto_join=True # 允许ORM在使用关联进行搜索时使用SQL连接。使用时会跳过访问安全规则,用户可以访问安全规则不允许其访问的关联记录,但这样 SQL 的查询会更有效率且更快。
delegate=True # 创建一个关联记录的代理继承。使用时必须设置required=True和ondelete=’cascade’。
One-to-many反向关联
one-to-many关联是many-to-one的反向关联
在图书模型中,publisher_id和parnter是 many-to-one. 同样说明 在partner模型中是有一个one-to-many的反向模型
# 在partner 模型中添加 library_app/models/res_partner.py下代码:
from odoo import fields, models
class Partner(models.Model):
_inherit = 'res.partner'
published_book_ids = fields.One2many(
'library.book', # related model
'publisher_id', # fields for "this" on related model
string='Published Books')
# One2many字段接收三个位置参数说明
关联模型 (comodel_name关键字参数) # 关联的模型
引用该记录的模型字段 (inverse_name关键字参数) # 引用关联模型的字段
字段标签 (string关键字参数) # 提示
# 其它可用的关键字参数与many-to-one字段相同:context, domain和ondelete
Many-to-many关联
书和作者之间是many-to-many关联:一本书可以有多个作者,一个作者可以有多本书
# 图书
class Book(models.Model):
_name = 'library.book'
...
author_ids = fields.Many2many(
'res.partner', string='Authors')
# 作者端
class Partner(models.Model):
_inherit = 'res.partner'
book_ids = fields.Many2many(
'library.book', string='Authored Books')
# 格式:
author_ids = fields.Many2many(
'res.partner', # 关联模型(尾款)
'library_book_res_partner_rel', # 要使用的关联表名
'a_id', # 本记录关联表字段
'p_id', # 关联记录关联表字段
'Authors') # string标签文本
# many2many 默认自动生成第三张表
# 1.模型名称过程,psql默认是63个字符,需要手动指定关联名
# 2.手动创建时,避免冲突
# 参数 和one-to-many类似,还可以使用context, domain和auto_join这些关键字参数
# 在创建抽象模型时,many-to-many中不要使用column1和column2属性。在 ORM 设计中对抽象模型有一个限制,如果指定关联表列名,就无法再被正常继承。
层级关联
父子树状关联使用同一模型中many-to-one关联表示,来将每条记录引用其父级。反向的one-to-many关联对应记录的子级。Odoo 通过域表达式附加的child_of和parent_of操作符改良了对这些层级数据结构的支持。只要这些模型有parent_id字段(或_parent_name有效模型定义)就可以使用这些操作符。
通过设置_parent_store=True和添加parent_path帮助字段可加快层级树的查询速度。该字段存储用于加速查询速度的层级树结构信息。
from odoo import api, fields, models
class BookCategory(models.Model):
_name = 'library.book.category'
_description = 'Book Category'
_parent_store = True
name = fields.Char(translate=True, required=True)
# Hierarchy fields
parent_id = fields.Many2one(
'library.book.category',
'Parent Category',
ondelete='restrict')
parent_path = fields.Char(index=True)
# Optional but good to have:
child_ids = fields.One2many(
'library.book.category',
'parent_id',
'Subcategories')
### 这里定义了一个基本模型,包含引用父级记录的parent_id字段。为启用层级索引来加快树级搜索,添加了一个_parent_store=True 模型属性。使用该属性必须要添加且必须要索引parent_path字段。引用父级的字段名应为parent_id,但如果声明了可选的_parent_name模型属性,则可以使用任意其它字段名。
引用字段的弹性关联
普通关联字段指定固定的引用co-model模型,但是Reference字段类型不受这一点限制,支持弹性关联
因此相同字段不用限制只指向相同的目标模型。
# 图书分类模型来添加引用重点图书或者作者,因此该字段可引用图书或 partner:
class BookCategory(models.Model):
...
highlighted_id = fields.Reference(
[('library.book', 'Book'), ('res.partner', 'Author')],
'Category Highlight'
)
###该字段定义与 selection 字段相似,但这里选择项为该字段中可以使用的模型。在用户界面中,用户会先选择列表中的模型,然后选择模型中的指定记录。
# 技术细节:
# 1. 引用字段在数据库中以model,id字符串形式存储
# 2. read()方法供外部应用使用,以格式化的(‘model_name’, id)元组返回,而不是常用的many-to-one字段的(id, ‘display_name’)形式
四.计算字段
计算字段声明和普通字段类似, 有一个额外的compute参数来定义计算函数
例如: 添加出版商的国别 依赖 出版商的 country_id
class Book(models.Model):
...
publisher_country_id = fields.Many2one(
'res.country', string='Publisher Country',
compute='_compute_publisher_country'
)
@api.depends('publisher_id.country_id')
def _compute_publisher_country(self):
for book in self:
book.publisher_country_id = book.publisher_id.country_id
### 注意
计算字段必须分配值,如果没有分配值则会报错.
五.模型约束
模型约束的目的是: 通过验证保证数据完整性和正确性.
psql支持多可用验证: 避免重复,检查条件是否符合等. 如若要求更复杂的逻辑,还是用python代码实现约束
SQL模型约束
由PostgreSQL直接执行, 由_sql_constraints类属性来定义.
是由元组组成的列表,格式(name,code,error)
# name 是约束标识名
# code 是约束的PostgreSQL语法
# error 是在约束验证未通过时向用户显示的错误消息
_sql_constraints = [
('library_book_name_date_uq', # 约束唯一标识符
'UNIQUE (name, date_published)', # 约束 SQL 语法
'Book title and publication date must be unique'), # 消息
('library_book_check_date',
'CHECK (date_published <= current_date)',
'Publication date must not be in the future.'),
]
Python模型约束
自定义代码来检查条件,@api.constrains装饰器
from odoo.exceptions import ValidationError
class Book(models.Model):
...
@api.constrains('isbn') # 约束的字段
def _constrain_isbn_valid(self):
for book in self:
if book.isbn and not book._check_isbn():
# 抛出异常时,数据自动回滚
raise ValidationError('%s is an invalid ISBN' % book.isbn)
搜索和写入计算字段
刚计算的字段可读取,但不可以搜索和写入. 默认情况下是实时计算,而不是存储在数据库种的.
# 可以通过特殊方法来开启搜索和写入操作
# 计算字段可与compute方法一起设置,实现搜索逻辑的search方法,以及实现写入逻辑的inverse方法
class Book(models.Model):
...
publisher_country_id = fields.Many2one(
'res.country', string='Publisher Country',
compute='_compute_publisher_country',
# store = False, # 默认不在数据库中存储
inverse='_inverse_publisher_country',
search='_search_publisher_country',
)
计算字段的写入是计算的反向逻辑. 因此处理写入的方法成为inverse
# 计算将book.publisher_id.country_id 的值复制给book.publisher_country_id,反向操作是将写入book.publisher_country_id的值拷贝给book.publisher_id.country_id field字段
def _inverse_publisher_country(self):
for book in self:
book.publisher_id.country_id = book.publisher_country_id
# 注意
# 1.由于要修改出版商partner记录数据,因此也会修改相同出版商图书的字段.
# 2.仅对partner模型有写入权限的当前用户才能成功执行此操作
为计算字段开启搜索操作,需要实现search方法. 要将计算字段的搜索转换为使用常规存储字段的搜索域.
def _search_publisher_country(self, opearator, value):
return [('publisher_id.country_id', operator, value)]
存储计算字段
字段参数添加 store = True
将计算字段值保存到数据库中,在任意依赖变更时值就会重新计算.可以像普通字段一样被搜索,不需要使用search方法了
关联字段
依赖 related参数
publisher_country_id = fields.Many2one(
'res.country', string='Publisher Country',
related='publisher_id.country_id',
)
六.base模型
odoo内置了base插件模块,包含
信息仓库(Information Repository) ir.*模型
资源(Resources) res.*模型
# 信息仓库用于存储 odoo 所需数据, 如:菜单,视图,模型,aciton等
ir.actions.act_window用于窗口操作
ir.ui.menu用于菜单项
ir.ui.view用于视图
ir.model用于模型
ir.model.fields用于模型字段
ir.model.data用于XML ID
# 资源包含基本数据,基本上用于应用
res.partner用于业务伙伴,如客户、供应商和地址等等
res.company用于公司数据
res.currency用于货币
res.country用于国家
res.users用于应用用户
res.groups用于应用安全组