Odoo中,一切皆模型,连视图都是模型。Odoo将各种数据,如:权限数据、类数据、视图数据等,按照模型分表存储,然后在查看时,按照索引从各个表格读取信息,组合成我们看到的内容。
创建模型
模型对象
Odoo的模型对象在odoo模块的models.py文件中,最基础的对象是BaseModel;
Odoo的模型对象有三个:AbstractModel、Model、TransientModel
BaseModel
BaseModel是一切模型的基础
AbstractModel
AbstractModel = BaseModel;
AbstractModel 是一个抽象模型不会在数据库创建对应表,Model可以继 AbstractModel,AbstractModel为多个Model提供相同属性的统一声明。抽象模型作为可重用的功能集,利用Odoo的继承功能,混入到其他模型。
Model
Model继承自AbstractModel,但是Model的 _auto=True , _abstract = True ;
Model的模型对象在模块安装或升级的时候会自动在数据库中创建相应的数据表
TransientModel
TransientModel继承自Model,但是TransientModel的_transient = True,TransientModel是一种特殊的Model,TransientModel对应的数据表中的数据系统会定时的清理;TransientModel的数据只能做临时数据使用,一般向导对象模型会声明成TransientModel
模型属性
模型属性:模型类可以使用一些属性来控制它们的一些行为
_name :创建odoo模型的内部标识符,必含项。
_description :当用户界面显示模型时,一个方便用户的模型记录标题。
_order :当浏览模型记录或者显示在列表视图时,设置默认顺序。
_rec_name:用来指出引用关联字描述记录的字段,例如多对一关系。 默认情况下,它使用name字段,这是模型中常见的字段。但是这个属性允许我们使用任何其他字段来实现这个目的。
_table:用来支持模型的数据库表名。通常,它是左自动计算,是下划线取代点的模型名称,但也可以设置特定的表名。
_inherit :继承。
_inherits :嵌入式继承。
字段
常用字段
-
Char用于字符串值。
-
Text用于多选字符串值。
-
Selection用于选择列表。这是一个值和描述对的列表。所选择的值存储于数据库中,可以是字符串或整数。描述自动可翻译。
小贴士:在Selection类型的字段中,可以使用整型的键,但应注意Odoo内部将0解释为未设置,不会显示键为0的描述。
-
Html类似于text字段,但通常用于以HTML格式存储的富文本。
-
Binary字段存储二进制文件,如图像或文档。
-
Boolean存储True/False 值。
-
Date存储日期值。它在数据库中以日期进行存储。ORM中以Python date对象的形式对其进行处理。Odoo 12之前的版本中ORM以字符串的形式处理日期。所使用的格式在odoo.fields.DATE_FORMAT中定义。
-
Datetime用于日期时间值。在数据库中以原生UTC时间datetime进行存储。ORM中以Python datetime对象的形式对其进行处理。Odoo 12之前的版本中ORM以字符串的形式处理datetime。所使用的格式在odoo.fields.DATETIME_FORMAT中定义。
-
Integer字段无需过多解释了。
-
Float(浮点)字段存储数值。精度可由位数和小数位数对来定义。
-
Monetary可存储某个币种的数量值。这会在本章中的向模型添加货币字段一节进行讲解。
Date与Datetime特殊方法
Date有如下方法:
- fields.Date.to_date(string_value将字符串解析为一个date对象。
- fields.Date.to_string(date_value)将Date对象表示为字符串。
- fields.Date.today()以字符串格式返回当前日期。这适合用于默认值。
- fields.Date.context_today(record, timestamp)根据记录(或记录集)上下文的时区以字符串格式返回时间戳的日期(或者在省略时间戳时返回当天)。
Datetime有如下方法:
- fields.Datetime.to_datetime(string_value)将字符串解析为datetime对象。
- fields.Datetime.to_string(datetime_value)将datetime对象表示为字符串。
- fields.Datetime.now()以字符串格式返回当天及当前时间。它适合用作默认值。
- fields.Datetime.context_timestamp(record, timestamp)将时间戳原生datetime按照记录上下文的时区转化为对应时区。它不适合用作默认值,但是在向外部系统发送数据等操作时可以使用。
字段属性
- string是字段的标题,在UI视图标签中使用。它是可选项,如未设置,会通过首字母大写及将空格替换为下划线来从字段名获取标签。
- translate,在设置为True时,让字段可翻译,它可根据用户界面的语言保存不同值。
- default是默认值。也可以是一个用于计算默认值的函数,例如default=_compute_default,_compute_default是在定义字段前模型中所定义的一个方法。
- help是在UI提示工具中显示的解释性文本。
- groups让字段仅对安全组可用。它是包含安全组的XML ID逗号分隔列表的一个字符串。
- states允许用户界面依据state字段的值来动态设置readonly, required和invisible的值。因此,它要求存在一个state字段并在表单视图中使用(即便是隐藏的)。state属性的名称是在Odoo硬编码且无法修改的。
- copy标记在复制记录时是否拷贝字段值。对于非关系型字段和Many2one它的默认值是True、对One2many和计算字段它的值是False。
- index,在设置为True时,为该字段创建一个数据库索引,有时可供更快速搜索使用。它取代了已淘汰的select=1属性。
- readonly标记让该字段在用户界面中默认仅为只读。
- required标记强制字段在用户界面中默认为必填。
- sanitize标记用于HTML字段并去除包含不安全标签的内容。使用它会对输入进行全局清理。如果需要更精细的控制,可以使用一些关键字,仅在启用sanitize时生效:
- sanitize_tags=True删除白名单列表以外的标签(默认项)
- sanitize_attributes=True删除白名单列表以外的标签属性
- sanitize_style=True删除白名单列表以外的样式属性
- strip_style=True删除所有样式元素
- strip_class=True删除所有class属性
- company_dependent标记让该字段根据公司存储不同值。它取代了已淘汰的Property字段类型。
默认创建字段
有些字段在Odoo模型中默认添加,因此我们不应在字段中使用这些名称。这些是记录自动生成的标识符的id字段以及一些审计日志字段,如下所示:
- create_date是记录创建的时间戳
- create_uid是创建该记录的用户
- write_date是最近记录的编辑时间戳
- write_uid是最后编辑记录的用户
这些日志字段的自动创建可通过设置模型属性_log_access=False来进行禁用。
关联字段
Odoo之间的关联通过关联字段来体现。有三种不同的关联类型
字段命名规则
Many2one
(多对一字段,也叫外键字段) 的字段命名后缀应为 _id
,例如:partner_id
, message_id
等。
One2many
字段名称请以 _ids
结尾。
Many2many
的字段命名规则与 One2many
命名规则相同,名称都是以 _ids
结尾。
Many2one
Many-to-one字段向模型的数据表中添加了一列,存储关联记录的数据库ID。在数据库级别上,还会创建外键约束,确保保存的ID是对关联表中记录的有效引用 。对这些关联字段不会创建数据库索引,但这可通过添加 index=True 属性来进行完成。
-
comodel_name:关联模型,它会在数据库表中创建一个拥有外键的字段去关联数据库表。
-
string:标题文本
-
ondelete 在关联的记录被删除时,将会触发。默认值是null,也就是说,当关联的记录被删除时,它将被置空。其它值为restrict,当记录被关联时,禁止删除,cascade,删除关联记录的同时删除当前记录。
-
context是一个数据字典,在web客户端视图,通过关联关系访问时,会将上下文传过去。比如,设置默认值。它的详细的介绍,在第六章-视图-设计用户界面中。
-
domain是一个由多个元组组成的列表的域表达式,用来删选关联字段中有效的记录。
-
auto_join=True使用这个参数后,将允许ORM在数据查询是使用SQL的join(拼接,级联)功能。
One2many
One-to-many字段是many-to-one的反向关联,虽然它们像其它字段一样添加在模型中,在数据库中并没有实际的体现。他们仅是编程捷径,启用视图来展现这些关联记录列表。
- comodel_name:关联模型
- inverse_name:关联模型的字段名称
- string:标题文本
Many2many
Many-to-many关联也不会向模型数据表添加列。这类关联在数据库中使用中间关联表进行体现,其中有两列分别存储这两个关联的ID。 Odoo自动处理这一关联表的创建。关联表的名称默认使用两个关联模型名按字母排序加上一个_rel后缀来创建。但我们可以使用relation属性来进行覆盖。
- comodel_name:它的功能与One2many字段中相同
- relation:这是用于支持关联的数据表的名称,覆盖自动定义的名称
- column1:这是连接这个模型的关联表中的Many2one字段的名称
- column2:这是在关联数据表中连接comodel的Many2one字段的名称
- string:标题文本
要在一个多字段上写,我们使用一个三元组列表。每个三重是一个写命令,根据使用的代码做
不同的事情:
(0,_ ,('field': value}) 创建一个新的记录并将其链接到这个记录
(l,id,('field': value}) 更新已链接的记录上的值
(2,id,_) 取消链接并删除相关记录
(3,id,_) 取消链接,但不删除相关记录
(4,id,_) 链接一个已经存在的记录
(5,_,_) 取消链接,但不会删除所有链接的记录
(6,_,[ids]) 用提供的列表替换链接记录的列表
前面列表中使用的下划线符号代表无关的值,通常填充为0或False。
前面列表中使用的下划线符号代表无关的值,通常填充为0或False。
计算字段
compute属性
计算字段就像常规字段一样声明,但是有额外的compute参数,参数值是一个函数名,在函数中计算值并返回。 这个计算方法通过计算self
的每条记录来设置字段的值。
注意
self
是一个记录的有序集合,它支持标准的Python集合操作,如len(self)
和iter(self)
,加上额外的集合操作recs1 + recs2
。迭代过程逐个提供self
记录,其中每个记录本身是大小为1的集合。你可以通过点记号来访问/分配单个记录上的字段record.name
。
当计算依赖于其他字段时,需要@api.depends装饰器。计算字段的值通常取决于所在记录行的其它字段的值。ORM层期望开发人员使用depends()
装饰器来指定计算方法的依赖性。当某些依赖关系被修改后,ORM层通过给定的依赖关系来触发字段的重新计算。
字段a = fields.XX(compute='func')
@api.depends('字段b')
def func(self):
操作
确保compute函数总是为计算字段设置一个值。否则会抛出错误。这在代码中包含if条件而对计算字段设置值失败时会发生。那样会很难进行调试。
onchange
"onchange"机制为客户端界面提供了一种方法:当用户在字段中填写了值,不需要向数据库保存任何内容,就可以更新表单。
eg:
- 假设模型有三个字段
amount
,unit_price
和price
,当数量和单价改变时,自动重新计算价格,并在表单界面更新。要实现这个需求,需要定义一个方法,并使用onchange()
装饰器,onchange()
的参数指定了在那个字段改变时,触发方法。其中self
代表表单视图中的记录,你所做的任何更改,self
都将立刻反应在表单上。 - onchange也可以用于检测字段变化值是否合法。
related
字段 = fields.XX(related='关联模型.关联字段',string='展示名')
关联字段和普通字段相似,但是有一个额外的属性related,带有一个字符串供分隔的字段链遍历。
注意:如果不设置关联字段为只读,字段将可写,用户可能会修改其值。这会产生修改关联出版社城市字段值的影响。这可能会既有用又有副作用,在操作时应小心,这可能会在用户的预料之外。
搜索、写入、存储
计算字段可以通过计算得到字段值,但此时只能被读取显示,还不能被搜索或者写入。为此,我们需要实现函数,并通过字段的search参数、inverse参数来实现该字段的搜索与写入定义。
存储计算字段:通过设置“store= true”的定义,计算字段的值也可以存储在数据库中。
模型约束
Python约束检查函数应该使用@api.constraints(被约束字段) 修饰,说明被检查的字段的列表。当其中任何一个被修改,并且在条件失败时将会抛出一个异常,验证就会被触发。
@api.constrains('字段名')
def 验证函数(self):
if 条件:
raise ValidationError('验证错误信息!')
模型继承
重点:在_manifest_.py中找到depends,加上要继承的模块
类属性中添加 _inherit 字段即可继承
类继承
_name 和 _inherit 的模型名一致,此时可以省略_name属性,因为它继承了父类的_name
。对现有模型的扩展, 添加新功能,都将添加到现有模型中,不会创建新模型。
原型继承
_name 和 _inherit 的模型名不同。 相当于把原模型的属性(字段、方法等)copy了一份,重新创建一个新的模型。
支持多重继承, _inherit = ['mail', 'resource']
委托继承
支持多重继承,并提供透明的子模型字段访问方法,好像模型有子模型字段
class Child0(models.Model):
_name = 'delegation.child0'
field_0 = fields.Integer()
class Child1(models.Model):
_name = 'delegation.child1'
field_1 = fields.Integer()
class Delegating(models.Model):
_name = 'delegation.parent'
_inherits = {
'delegation.child0': 'child0_id',
'delegation.child1': 'child1_id',
}
child0_id = fields.Many2one('delegation.child0', required=True, ondelete='cascade')
child1_id = fields.Many2one('delegation.child1', required=True, ondelete='cascade')
api装饰器
函数在不使用这些装饰器时,默认 self
都是记录集,所以要记得对 self
进行循环处理。
-
当你认为这段代码跟 self 中的数据无关时,就加上
api.model
装饰器,类似 python 中的 classmethod。 -
使用了
api.multi
装饰器,这里是显式的指明_compute_current
传入的self
可以为一个记录集
,所以在处理时需要循环对self
中的每一个记录进行处理。 -
api.one
与api.multi
装饰器类似,但是他将self
处理成一条记录,也就是我们不需要对self
进行循环处理。
API
ORM
create()
接收多个键值对,返回新创建的数据集
self.create({'name': "New Name"})
write()
接收多个键值对,会对指定数据集的所有记录进行修改,不返回:
self.write({'name': "Newer Name"})
search(args, offset=0, limit=None, order=None, count=False)
接收domain表达式参数,返回符合条件的数据集合
- args:domain表达式,为空时返回所有记录
- offset (int) 从第几条记录开始取
- limit (int) 返回记录行数的最大值
- order (str) 排序的字段
- count (bool) 当值为True的时候只返回匹配记录的条数
self.search([('is_company', '=', True), ('customer', '=', True)])
search_count(args)
统计满足条件的数据数量,计算数量时建议使用search_count不用search(count = True)
browse()
根据数据的id或者一组id来查找,返回符合条件的数据集合:
self.browse([7, 18, 12])
name_search(name='', args=None, operator='ilike', limit=100)
在Many2*类型的模型上显示时被调用,返回结果是由id和repr的文本组成的元素列表。 由name_search方法限制条件过滤,然后再把结果传给name_get方法进行显示。
name_get()定义了记录如何显示,那么name_search()则定义了记录如何被查找。
name_search()定义了模型记录在被关联、被搜索时,通过输入的内容进行模糊查找的逻辑。
默认情况下,是通过查找记录的 name 字段值是否与输入内容类似进行比对查找,我们可以改写模型的name_search()函数,把更多的字段加入比对中去。
- name (str) -- 用来匹配的name字符串
- args (list) -- domain表达式列表
- operator (str) -- 用来匹配的操作符,如: 'like' , '='.
- limit (int) -- 可选参数,最多返回的记录行数
name_get()
用于获取模型的显示名称,当在form窗口中打开一个model时会被调用,返回值为一个包含id和名称的元组组成的列表[(id, name), ...]
。 默认是display_name字段。
如果我们想自定义该模型的记录显示的名称,例如:使用 编码+name字段 显示的复合名称,则可以重写name_get()函数
name_create(name)
相当于调用create方法创建一条新记录而只设置一个display_name
default_get(fields_list)
default_get(fields) 函数用于初始化记录的默认值,对于模型的某些字段如果需要设置默认值,可以重写模型的default_get()函数达到目的。返回字典{字段名:默认值}
例如:从表单中携带上下文信息跳转到向导、跳转到一个模型的新建表单视图等,可以在跳转时往context传递数据,然后在向导模型、被跳转创建的模型中重写default_get方法,从context中提前信息,进行字段默认值的初始化。
视图查询
fields_view_get(view_id=None, view_type='form', toolbar=False, submenu=False)
获取请求的视图的详细组成,例如字段,模型,视图架构
- view_id 视图的id或None
- view_type 当view_id参数为空时指定视图类型如form,tree等
- toolbar 参数为true时将上下文动作包含在内
get_formview_action
表单视图获取函数,可以重写该函数,使模型加载某个特定的form视图,甚至可以在加载时传递context值,控制视图的行为。
load_views(views,options)
视图加载函数,可以重写该函数,在加载视图时传递context值,控制视图行为。
记录集
exists()
得到某个数据集中保留在数据库中的那部分(可用于检查数据集是否为空):
if not record.exists():
raise Exception("The record has been deleted")
ensure_one()
检验某数据集是否只包含单条数据,如果不是则报错
records.ensure_one()
ids()
此记录集中的实际记录ID列表(忽略要创建的记录的占位符ID)
filtered(func)
返回满足func参数内条件的记录集合,参数可以是一个函数或者用.分隔的字段列表
sorted(key=None, reverse=False)
返回按key排序之后的记录集,key参数可以是一个返回单个key的函数或字段名称或为空,reverse参数为True时即为倒序
mapped(func)
将func函数应用到所有记录上,并返回记录列表或集合
运行环境
records.env.user
当前用户
records.env.uid
当前用户id
records.env.cr
数据库查询游标
records.env.context
上下文字典
env['model']
返回model的env环境return self._env
sudo()
env['model'].sudo() 返回提供的用户的此记录集的新版本,默认返回超级用户的,用来绕过控制和访问规则
with_context()
一个参数时可用于替换当前运行环境的context,多个参数时通过keyword添加到当前运行环境context或单参数时设置的context
with_env(env)
完整替换当前运行环境
记录集运算符
运算符 | 执行操作 |
---|---|
R1 + R2 | 它返回一个包含 R1中记录的新记录集,后面跟 R2中的记录。这可能会产生记录集中的重复记录 |
R1 - R2 | 它返回一个包含 R1中记录但不包含 R2中记录的新记录集。保留原有排序。 |
R1 & R2 | 它返回一个既属于 R1又属于 R2的记录的新记录集(记录集的交集)。不保留原有排序。 |
R1 | R2 | 它返回一个或属于 R1或属于 R2的记录的新记录集(记录集的并集)。不保留原有排序,且没有重复值。 |
R1 == R2 | 如果两个记录集中包含相同的记录则返回 True。 |
R1 <= R2 R1 in R2 | 如果 R1的记录也存在于 R2中返回 True。两种语法异曲同工。 |
R1 >= R2 R2 in R1 | 如果 R2的记录也存在于 R1中返回 True。两种语法异曲同工。 |
R1 != R2 | 如果R1和 R2不包含相同记录返回 True。 |