odoo ORM API学习总结兼orm学习教程
环境
odoo-14.0.post20221212.tar
ORM API学习总结/学习教程
模型(Model)
Model字段被定义为model自身的属性
from odoo import models, fields
class AModel(models.Model):
_name = 'a.model.name'
field1 = fields.Char()
警告
字段的名称和方法的名称不能相同,最后定义的方法、函数名称会覆盖前面定义的相同名称。
默认的,字段的标签(Lable,即用户可见字段名称)为对应字段名称开头字母改成大写后的值,可通过 string
字段属性改成修改字段Label
field2 = fields.Integer(string="Field Label")
可通过default
,定义默认值:
name = fields.Char(default="a value")
默认值也可以通过函数获取:
def _default_name(self):
return 'Title'
name = fields.Char(default=lambda self: self._default_name())
API
BaseModel
class odoo.models.BaseModel
[源代码]
Odoo模型的基类。Odoo mode可通过继承一下类来创建Model:
Model
用于常规数据库持久化模型TransientModel
用于临时数据,存储在数据库中,但每隔一段时间就会自动清空AbstractModel
用于多继承模块共享的抽象父类,不会在数据库中创建模型表
系统为每个数据库自动实例化每个模型一次。这些实例表示每个数据库上的可用模型,取决于该数据库上安装的模块。每个实例的实际类都是从创建和继承相应模型的Python类构建的。
每个模型实例都是一个“记录集(recordset)”,即模型记录的有序集合。记录集由 browse()
, search()
或字段访问等方法返回。记录没有显式的表示:单条记录表示为一条记录的记录集。
要创建不需要实例化的类,可以将 _register
属性设置为False
_auto= False
是否应该创建数据库表。如果设置为
False
, 应该重写init()
来创建数据库表。默认设。针对Model
和TransientModel
自动设置为False
,针对AbstractModel
自动设置为False
。可通过继承AbstractModel
来创建不需要任何数据表的模型_log_access
ORM是否自动生成和更新 Access Log fields。默认
_auto
的值。_table= None
模型对应的数据库表的名称。如果
_auto
设置为True
的话。_sequence= None
用于ID字段的SQL序列
_sql_constraints= []
sql约束,格式:
[(name, sql_def, message)]
_register= True
registry visibility
_abstract= True
是否为抽象模型
_transient= False
是否为transient模型
_name= None
模型名称(以 点分式命名的模块名称,比如
estate.users
)_description= None
模块描述,非整数名称
_inherit= None
继承的Python模型:需要继承模型的名称(
_name
属性值)或者名称列表(list
类型)_inherits= {}
(不太理解)dictionary {‘parent_model’: ‘m2o_field’} mapping the _name of the parent business objects to the names of the corresponding foreign key fields to use:
_inherits = {
'a.model': 'a_field_id',
'b.model': 'b_field_id'
}
implements composition-based inheritance: the new model exposes all the fields of the inherited models but stores none of them: the values themselves remain stored on the linked record.
警告
if multiple fields with the same name are defined in the
_inherits
-ed models, the inherited field will correspond to the last one (in the inherits list order)._rec_name= None
用于标记记录的字段,默认值:
name
_order= 'id'
用于搜索结果的默认排序字段
_check_company_auto= False
执行
write
和create
, 对拥有check_company=True
属性的关联字段调用_check_company
以确保公司一致性_parent_name= 'parent_id'
用作父字段的many2one字段
_parent_store= False
设置为
True
以计算parent_path
字段。与parent_path
字段一起,设置记录树结构的索引存储,以便使用child_of
和parent_of
域运算符对当前模型的记录进行更快的分层查询_date_name= 'date'
用于默认日历视图的字段
_fold_name= 'fold'
用于确定看板视图中折叠组的字段
AbstractModel
odoo.models.AbstractModel
[源代码]
odoo.models.BaseModel
的别名
Model
class odoo.models.Model
[源代码]
常规数据库持久化Odoo模型的主要父类。
通过继承此类来创建Odoo模型的:
class user(Model):
...
系统将为安装了该类模块的每个数据库实例化一次类
_auto= True
是否应该创建数据库表。如果设置为
False
, 应该重写init()
来创建数据库表。默认设。针对Model
和TransientModel
自动设置为False
,针对AbstractModel
自动设置为False
。可通过继承AbstractModel
来创建不需要任何数据表的模型_abstract= False
是否为抽象模型
_transient= False
是否为transient模型
TransientModel
class odoo.models.TransientModel
[源代码]
用于临时记录的父类模型,旨在暂时保持,并定期进行清理
TransientModel
具有简化的访问权限管理,所有用户都可以创建新记录,并且只能访问他们创建的记录。超级用户可以无限制地访问所有TransientModel
记录。
_auto= True
是否应该创建数据库表。如果设置为
False
, 应该重写init()
来创建数据库表。默认设。针对Model
和TransientModel
自动设置为False
,针对AbstractModel
自动设置为False
。可通过继承AbstractModel
来创建不需要任何数据表的模型_abstract= False
是否为抽象模型
_transient= False
是否为transient模型
字段(Fields)
class odoo.fields.Field[源代码]
字段拥有以下属性
string (str) – 用户看到的字段的标签;如果未设置,ORM将采用类中的字段名开头字母改成大写后的
help (str) – 用户看到的字段的提示条(设置该属性后,当鼠标悬停在字段标签上方时,会自动浮现提示条,显示该属性的文字内容)。
invisible – 字段是否可见。默认为
False
,即可见readonly (bool) – 字段在用户界面是否只读,默认值
False
,仅对UI起作用required (bool) – 字段在用户界面是否必填,默认
False
。这通过在数据库层面为列添加NOT NULL
约束来实现index (bool) – 是否为字段添加索引。注意:对不存储、虚拟字段不起作用。默认值:
False
default (值或者可调用对象) – 设置字段的默认值。可以是静态值,或者以结果集为入参,返回某个值的函数。使用
default=None
舍弃该字段的默认值。states (dict) –将
state
值映射到UI属性-值对列表的字典映射,简单说就是允许用户界面依据state
字段的值来动态设置对应字段的UI属性,因此,它要求存在一个state
字段并在视图中使用(即使是隐藏的),state
属性的名称是在odoo硬编码且不允许修改的,可用属性有:readonly
,required
,invisible
。例如states={'done':[('readonly',True)]}
,表示当state
值为done
时,将用户界面states
所在字段在设置为只读(仅针对UI层面)用法举例:
state = fields.Selection([
('draft', 'To Submit'),
('cancel', 'Cancelled'),
('confirm', 'To Approve'),
('refuse', 'Refused'),
('validate1', 'Second Approval'),
('validate', 'Approved')
], string='Status', readonly=True, copy=False, default='confirm')
date_from = fields.Datetime(
'Start Date', readonly=True, index=True, copy=False,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
groups (str) – 值为逗号分隔的组XML ID列表,如
groups='base.group_user,base.group_system'
,可限制字段只能被给定组用户访问。company_dependent (bool) –
字段值是否依赖于当前公司,如果设置为
True
,则表示依赖当前公司,即字段值和公司绑定。这个属性的作用就是让同一字段,可以根据不同公司,存储不同的值,假设一个用户属于多个公司,他在不同公司的职务也不一样,此时就可以设置该属性为True
。该值未存储在当前模型表中。它注册为
ir.property
,也就是说它的值存储在ir_property
表中,通过查询该表来获取该字段的值。copy (bool) – 当记录重复时,该字段值是否被拷贝(在使用 ORM
copy()
方法复制并生成新记录时,不复制该字段的值)。 (针对普通字段,默认值为:True
,针对one2many
和计算字段,包括属性字段(property fields
,个人理解注册ir.property
的字段)和关系字段,默认值为False
store (bool) – 该字段是否存储到数据库,针对计算字段,默认值为
False
,其它字段默认为True
group_operator (str) –
在当前字段上分组时,供
read_group()
使用的聚合函数支持的聚合函数:
array_agg
: 值,包括空值,连接成一个数组count
: 记录数count_distinct
: 不重复记录数bool_and
: 如果所有值都为真,则为真,否则为假bool_or
: 如果至少有一个值为真,则为真,否则为假max
: 所有值的最大值min
: 所有值的最小值avg
:所有值的平均值(算术平均值)sum
: 所有值的总和
group_expand (str) –
用于在当前字段上分组时用于扩展
read_group
结果的函数@api.model
def _read_group_selection_field(self, values, domain, order):
return ['choice1', 'choice2', ...] # available selection choices. @api.model
def _read_group_many2one_field(self, records, domain, order):
return records + self.search([custom_domain])
基础字段
class odoo.fields.Boolean[源代码]
bool
的封装
class odoo.fields.Char[源代码]
基本字符串字段,长度有限,通常在客户端显示为单行字符串
参数:
size(int) – 为该字段可存储最大值
trim(bool) – 说明该值是否被修剪(默认情况下,
True
)。请注意,修剪操作仅由 Web 客户端应用。translate(bool 或者可调用对象)
– 启用字段值的翻译;用于
translate=True整体翻译字段值;
translate也可以是可调用的,从而使得
translate(callback,value)通过使用
callback(term)来检索术语的翻译来翻译
value`
class odoo.fields.Float[源代码]
float
的封装
精度数字由可选的digitals
属性给出。
参数
digits (
tuple
(int
,int
), 或者str
) – 一个元组(total, decimal)
或者引用DecimalPrecision
记录的字符串digits=(8,2) 表示总的8位,小数点占2位
Float类为此提供了一些静态方法:
round()
以给定精度对浮点值进行舍入。is_zero()
检查浮点值在给定精度下是否等于零。compare()
按给定精度比较两个浮点值。
例子:
fields.Float.round(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
fields.Float.is_zero(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
field.Float.compare(self.product_uom_qty, self.qty_done, precision_rounding=self.product_uom_id.rounding)
比较助手出于历史目的使用__cmp_
语义,因此使用此助手的正确惯用方式如下:
如果result==0,则第一个和第二个浮点数相等,如果result<0,第一个浮点数小于第二个,如果result>0,第一个浮动点数大于第二个浮动点数
class odoo.fields.Integer[源代码]
int
的封装
高级字段
class odoo.fields.Binary[源代码]
封装二进制内容(比如一个文件)。
参数:
- attachment(bool)
– 字段是否存储为
ir_attachment还是该model表的一列(默认为:
True`,即存储为前者。
class odoo.fields.Html[源代码]
html代码内容的封装
参数:略
class odoo.fields.Image[源代码]
图片的封装,扩展Binary
如果图像大小大于像素的max_width/max_height
限制,则通过保持纵横比将图像大小调整到该限制。
参数:
max_width(int ) – 图像的最大宽度(默认值:
0
,无限制)max_height ( int) – 图像的最大高度(默认值:
0
,无限制)verify_resolution ( bool) – 是否应验证图像分辨率以确保它不会超过最大图像分辨率(默认值:
True
。最大图像分辨率请参阅odoo.tools.image.ImageProcess
(默认值:50e6
)。参数
如果没有指定 max_width
/max_height
或者设置为0,且verify_resolution
为False
,则不会验证字段内容,此时应该使用Binary
字段。
class odoo.fields.Monetary[源代码]
封装以给定res_currency
表示的浮点值。
小数精度和货币符号取自currency_field
属性。
参数:
- currency_field (str) –拥有表示该货币字段的
res_currency
的Many2one
字段名称(默认:'currency_id'
)
class odoo.fields.Selection[源代码]
封装不同值之间的互斥选择。
说明:Selection
字段的可选值,存储在public.ir_model_fields_selection
表中,通过field_id
字段通过public.ir_model_fields
表进行
-- 查询Selection字段ID
SELECT id FROM public.ir_model_fields
where model = 'stock.quality' and name='state'
-- 查询Selection字段可选值
select * from public.ir_model_fields_selection where field_id = 13028; -- 13028为Selection字段ID
参数:
selection (list(tuple(str, str)) 或者可调用对象 或者 str)) – 指定字段的可选值。其值为包含2元组的列表,或者返回前者模型方法,或者方法名称
selection_add (list(tuple(str, str)) –
在重写字段的情况下,提供
selection
的扩展。它是一个包含二元组(value, label)
或者单元组(value,)
的列表,其中,单元组中的value
必须作为value
出现在selection
列表中的元组中。新值插入顺序和原有selection
中元组顺序保持一致:selection = [('a', 'A'), ('b', 'B')]
selection_add = [('c', 'C'), ('b',)]
> result = [('a', 'A'), ('c', 'C'), ('b', 'B')]
ondelete –
为带有
selection_add
的任何重写字段提供回退机制。这是一个将selection_add
中的每个选项映射到回退操作的dict。此回退操作将应用于其
selection_add
选项映射到该操作的所有记录。这些操作可以是以下任一操作:
set null
默认情况下,具有此选项的所有记录的选择值都将设置为False。cascade
–具有此选项的所有记录将与选项本身一起删除。set default
-具有此选项的所有记录都将设置为字段定义的默认值<callable>
-一个可调用对象,其第一个也是唯一的参数将是包含指定的Selection选项的记录集,用于自定义处理
selection
属性选择是强制性的,除非是related
或扩展的字段
class odoo.fields.Text[源代码]
类似Char
,用于更长的内容,没有大小,通常展示为多行文本框。
参数:
translate (bool 或者可调用对象) – 同 Char
Date(time) 字段
当将一个值赋值给 Date
/Datetime
字段时,以下选择是合法的:
date
或datetime
对象.- 正确格式的字符:
Date
字段采用YYYY-MM-DD
Datetime
字段采用YYYY-MM-DD HH:MM:SS
False
或者None
.
Date
和Datetime
字段类拥有以下辅助函数,用于尝试转换为兼容类型:
to_date()
转换为datetime.date
to_datetime()
转换为datetime.datetime
.
示例
解析来自外部的日期/日期时间:
fields.Date.to_date(self._context.get('date_from'))
Date
/Datetime
比较最佳实践:
Date
字段只能和date对象比较Datetime
字段只能和datetime对象比较
Datetime
字段在数据库中存储为不带时区的时间戳,并以UTC时区存储。因为这样可使Odoo数据库独立于托管服务器系统的时区。时区转换完全由客户端管理。
Common operations with dates and datetimes such as addition, subtraction or fetching the start/end of a period are exposed through both Date
and Datetime
. These helpers are also available by importing odoo.tools.date_utils
.
class odoo.fields.Date源代码
Python date
对象的封装
static add(value, *args, **kwargs)
返回
value
和relativedelta
之和参数
value – 初始
date
或datetime
args – 传递给
relativedelta
的位置参数kwargs – 传递给
relativedelta
的关键词参数返回
date/datetime结果对象
示例:
from odoo.fields import Date print(Date.add(datetime.now(), years=1)) # 输出形如:2024-01-03
# 常见参数:
# years, months, days, leapdays, weeks, hours, minutes, seconds, microseconds
static subtract(value, *args, **kwargs)
[源代码]返回
value
和relativedelta
之差参数
value – 初始
date
或者datetime
args – 传递给
relativedelta
位置参数kwargs – 传递给
relativedelta
的关键词参数返回
date/datetime
结果对象
static context_today(record, timestamp=None)
[源代码]按客户端时区以适合
date
字段的格式返回当前日期注解
该方法可能用于计算默认值
参数
record – 从中获取时区的记录集
timestamp (
datetime
) – 替代当前日期时间(datetime)的可选的datetime
对象返回类型
date
static end_of(value, granularity)
[源代码]从日期或日期时间获取时间段的结束
参数
value – 初始
date
或datetime
granularity – 字符串表示的时间段类型, 可以是
year
,quarter
,month
,week
,day
或者hour
。返回
与指定时段的起始对应的
date/datetime
对象
示例:
print(datetime.now()) # 2023-01-03 10:12:32.332208
print(Date.end_of(datetime.now(), 'year')) # 输出形如:2023-12-31 23:59:59.999999
print(Date.end_of(datetime.now(), 'month')) # 输出形如:2023-01-31 23:59:59.999999
static start_of(value, granularity)
[源代码]从日期或日期时间获取时间段的开始
参数
value – 初始
date
或datetime
granularity – 字符串表示的时间段类型, 可以是
year
,quarter
,month
,week
,day
或者hour
。返回
与指定时段的起始对应的
date/datetime
对象
示例:
print(datetime.now()) # 2023-01-03 10:18:57.071276
print(Date.start_of(datetime.now(), 'year')) # 输出形如:2023-01-01 00:00:00
print(Date.start_of(datetime.now(), 'month')) # 输出形如:2023-01-01 00:00:00
print(Date.start_of(datetime.now(), 'hour')) # 输出形如:2023-01-03 10:00:00
static to_date(value)
[源代码]尝试转换
value
为date
对象警告
如果value为
datetime
对象,它将被转换为date
对象,且所有日期时间特定信息(HMS, TZ, …)都会丢失。参数
value (str 或 date 或 datetime) –需要转换的值
返回
代表
value
的对象返回类型
date
类型或者None
static to_string(value)
[源代码]将
date
或者datetime
对象转为字符串参数
value – 需要转换的日期或者日期时间对象
返回
以服务器日期格式返回代表
value
的字符串。如果value
为datetime
类型,自动舍弃小时,分,秒,时区信息。返回类型:str
示例:
print(Date.to_string(datetime.now())) # 输出形如:2023-01-03
static today(*args)
[源代码]返回当前日期
示例:
print(Date.today()) # 格式形如:2023-01-03
class odoo.fields.Datetime[源代码]
Python datetime
对象的封装
static context_timestamp(record, timestamp)
[源代码]返回转换为客户端时区的给定时间戳。
注解
此方法不是用作默认初始值设定项,因为
datetime
字段在客户端显示时会自动转换。对于默认值,应使用now()
参数
record – 从中获取时区的记录集。
timestamp (datetime) – 待转换为客户端时区的naive
datetime
值 (UTC
表示的)返回
按上下文时区转换为时区敏感的
datetime
返回类型
datetime
static add(value, *args, **kwargs)
[源代码]参考
Date.add
static subtract(value, *args, **kwargs)
[源代码]参考
Date.subtract
static end_of(value, granularity)
[源代码]参考
Date.end_of
static start_of(value, granularity)
[源代码]参考
Date.start_of
static to_string(value)
[源代码]参考
Date.to_string
static today(args)
[源代码]返回当天,午夜 (00:00:00)
示例:
from odoo.fields import Datetime print(Datetime.today()) # 输出形如:2023-01-03 00:00:00
print(Datetime.now()) # 输出当前时间 2023-01-03 12:33:00
static to_datetime(value)
[源代码]将ORM
value
转为datetime
值参数
value (str 或者 date 或者 datetime) – 需要转换的值
返回
代表
value
的对象返回类型
datetime
或者None
关系字段(Relational Fields)
class odoo.fields.Many2one
[源代码]
Many2one
字段的值是大小为0(无记录)或1(单个记录)的记录集。
参数:
- comodel_name (str) – 目标模型的名称,
comodel_name
是必选参数,除非是相关或扩展字段(不太理解,原文:name of the target modelMandatory
except for related or extended fields) - domain – 用于设置客户端侧候选值的可选 domain (domain 或者字符串)
- context (dict) – 处理该字段时供客户端使用的上下文
- ondelete (str) – 当引用的记录被删除时,怎么处理:可选值有:
'set null'
,'restrict'
,'cascade'
- auto_join (bool) – 是否在搜索该字段时生成
JOIN
(默认:False
) - delegate (bool) – 将其设置为
True
以标记可通过当前模型访问目标模型的字段(对应_inherits
) - check_company (bool) – 标记需要在
_check_company()
中校验的字段。取决于字段属性,添加一个默认的公司domain
class odoo.fields.One2many
[源代码]
One2many
字段的值为 comodel_name
中所有满足条件的记录的结果集,而目标模型中的 inverse_name
则等价于当前记录。
参数:
- comodel_name (str) – 目标模型的名称
- inverse_name (str) – 目标模型中反向
Many2one
字段名称,根据该字段反向查询记录 - domain – 用于设置客户端候选值的条件 (domain 或者字符串),可选
- context (dict) – 处理该字段时供客户端使用的上下文
- auto_join (bool) – 是否在搜索该字段时生成
JOIN
(默认:False
) - limit (int) – 读取时用的可选限制
comodel_name
和inverse_name
参数是必选参数,除非是相关或者扩展字段
class odoo.fields.Many2many
[源代码]
Many2many
字段的值为一个结果集。
参数:
- comodel_name – 目标模型的名称,必选参数,除非是关联或者扩展字段
- relation (str) – 数据库中存储关系的表名,可选参数。
- column1 (str) –
relation
表中引用"这些"记录的列名,可选参数 - column2 (str) –
relation
表中引用"那些"记录的列名,可选参数
relation
, column1
和column2
参数可选。 如果未给定,自动根据模型名称生成,提供的不同的model_name
和comodel_name
。
注意,ORM不支持在给定模型,使用同样的comodel
,创建多个省略了relation
参数的字段,因为这些字段将使用相同的表。ORM阻止两个Many2many
字段使用相同的relation
参数,除非:
- 两个字段都使用相同的模型,
comodel
并显示指定relation
参数,否则 - 至少有一个字段属于携带
_auto = False
的模型
参数:
- domain – 用于设置客户端候选值的条件 (domain 或者字符串),可选
- context (dict) – 处理该字段时供客户端使用的上下文
- check_company (bool) – 标记需要在
_check_company()
中校验的字段。取决于字段属性,添加一个默认的公司条件 - limit (int) – 读取时用的可选限制
注意:odoo不会在当前模型对应表中为One2many
,Many2many
类型的属性建立对应的表字段,但会为Many2one
类型的属性建立对应表字段,针对Many2many
类型的属性,odoo会建立一张辅助表,表名默认格式为model1_table_name_model2_table_name_rel
,该表拥有两列,一列为当前模型表主键ID(model1_table_name_id
),一列为关系字段关联模型表的主键ID(model2_table_name_id
),这样通过两表记录ID就可以查询所需记录了
伪关系字段
class odoo.fields.Reference
[源代码]伪关系字段(数据库中没有FK)。该字段值存储为数据库中遵循模式
"res_model,res_id"
的字符串。class odoo.fields.Many2oneReference
[源代码]该字段的值存储为数据库中的一个整数。与
odoo.fields.Reference
字段相反,必须在Char
类型字段中指定模型,其中,该字段的名称必须在当前Many2oneReference
字段中的model_field
属性中指定参数:model_field (str) – 存储模型的字段名称。
计算字段
可以使用 compute
参数计算字段(而不是直接从数据库中读取)它必须将计算值分配给字段。如果它使用其他字段的值,则应使用depends()
指定这些字段
from odoo import api
total = fields.Float(compute='_compute_total')
@api.depends('value', 'tax')
def _compute_total(self):
for record in self:
record.total = record.value + record.value * record.tax
当使用子字段时,依赖可使用分点路径:
@api.depends('line_ids.value')
def _compute_total(self):
for record in self:
record.total = sum(line.value for line in record.line_ids)
默认情况下,不存才计算字段。他们在请求时被计算并返回。 设置
store=True
将在数据库中存储计算及字段并启动开启字段搜索。也可以通过设置
search
参数开启在计算字段上的搜索。该参数值为一个返回搜索条件的方法名称 。upper_name = field.Char(compute='_compute_upper', search='_search_upper') def _search_upper(self, operator, value):
if operator == 'like':
operator = 'ilike'
return [('name', operator, value)]
在对模型进行实际搜索之前处理domain时调用该搜索方法。它必须返回与条件
field operator value
等效的domain计算字段默认值。为了允许对计算字段进行设置,使用
inverse
参数。该参数值为反向计算并设置相关字段的函数的名称:document = fields.Char(compute='_get_document', inverse='_set_document') def _get_document(self):
for record in self:
with open(record.get_document_path) as f:
record.document = f.read()
def _set_document(self):
for record in self:
if not record.document: continue
with open(record.get_document_path()) as f:
f.write(record.document)
可以用同一方法同时计算多个字段,只需对所有字段使用同一方法并设置所有字段
discount_value = fields.Float(compute='_apply_discount')
total = fields.Float(compute='_apply_discount') @api.depends('value', 'discount')
def _apply_discount(self):
for record in self:
# compute actual discount from discount percentage
discount = record.value * record.discount
record.discount_value = discount
record.total = record.value - discount
警告
虽然可以对多个字段使用相同的计算方法,但不建议对
reverse
方法使用相同的方法。在
reverse
的计算过程中,所有使用所述inverse的字段都受到保护,这意味着即使它们的值不在缓存中,也无法计算它们。如果访问了这些字段中的任何一个字段,且并且其值不在缓存中,ORM将简单的为这些字段返回默认值
False
。这意味着这些inverse
字段的值(触发inverse
方法的值除外)可能不会给出正确的值,这可能会破坏inverse
方法的预期行为
相关字段(Related fields)
计算字段的一种特殊情况是相关(代理)字段,它提供当前记录上子字段的值。它们是通过设置related
参数来定义的,与常规计算字段一样,它们可以存储:
nickname = fields.Char(related='user_id.partner_id.name', store=True)
related
字段的值是通过遍历一系列关系字段并读取所访问模型上的字段来给出的。要遍历的字段的完整序列由related
属性指定
如果未重新定义某些字段属性,则会自动从源字段中复制这些属性:string
、help
、required
(仅当序列中的所有字段都是必需的时)、groups
、digits
、size
、translate
、cleaning”、“selection
、comodel_name
、domain
和context
。所有无语义属性都从源字段复制。
默认的, related字段:
- 不被存储
- 不被复制
- 只读
- 超级用户模式下被计算
像计算字段那样,添加 store=True
以存储related
字段。当其依赖被修改时,会自动重新计算related
字段。
小技巧
如果不希望在任何依赖项更改时重新计算related
字段,则可以指定精确的字段依赖项:
nickname = fields.Char(
related='partner_id.name', store=True,
depends=['partner_id'])
# nickname仅在partner_id被修改时才会被重新计算,而不会在partner名称被修改时重新计算
警告
不可以在related
字段依赖项中包含 Many2many
或者 One2many
字段
related
可以用于引用另一个模型中的 One2many
或Many2many
字段,前提是通过当前模型的一个Many2one
关系来实现的。 One2many
和Many2many
不被支持,无法正确的汇总结果:
m2o_id = fields.Many2one()
m2m_ids = fields.Many2many()
o2m_ids = fields.One2many()
# Supported
d_ids = fields.Many2many(related="m2o_id.m2m_ids")
e_ids = fields.One2many(related="m2o_id.o2m_ids")
# Won't work: use a custom Many2many computed field instead
f_ids = fields.Many2many(related="m2m_ids.m2m_ids")
g_ids = fields.One2many(related="o2m_ids.o2m_ids")
自动生成的字段
odoo.fields.id
ID字段
如果当前记录集长度为1,返回记录集中唯一记录的ID。否则抛出一个错误
访问日志字段
如果启用_log_access
,自动设置并更新这些字段。当未用到这些字段时,以禁用它以阻止创建或更新表中这些字段。
默认的 _log_access
被设置为 _auto
的值。
odoo.fields.create_date
创建记录时存储创建时间,
Datetime
类型odoo.fields.create_uid
存储记录创建人,
Many2one
to ares.users
odoo.fields.write_date
存储记录最后更新时间,
Datetime
类型odoo.fields.write_uid
存储记录最后更新人,
Many2one
to ares.users
.
警告
必须对odoo.models.TransientModel
模型开启_log_access
保留字段名称
除了自动字段之外,还有一些字段名是为预定义行为保留的。当需要相关行为时,应在模型上定义它们:
odoo.fields.name
_rec_name
的默认值,用于在需要代表性“命名”的上下文中显示记录。odoo.fields.Char
类型odoo.fields.active
切换记录的全局可见性,如果
active
设置为False
,则记录在大多数搜索和列表中不可见。odoo.fields.Boolean
类型odoo.fields.state
对象的声明周期阶段,供
fields.[Selection
的states
属性使用odoo.fields.parent_id
_parent_name
的默认值,用于以树结构组织记录,并在domain中启用child_of
和parent_of
运算符。Many2one
字段。odoo.fields.parent_path
当
_parent_store
设置为True
时,用于存储反映[_parent_name
]树结构的值,并优化搜索domain中的child_of
和parent_of
运算符。必须使用index=True
声明才能正确操作。odoo.fields.Char
类型odoo.fields.company_id
用于Odoo多公司行为的主字段名。供
:meth:~Odoo.models._check_company
用于检查多公司一致性。定义记录是否在公司之间共享(没有值)还是仅由给定公司的用户访问。Many2one
:类型:res_company
记录集(Recordset)
与模型和记录的交互是通过记录集执行的,记录集是同一模型的记录的有序集合。
警告
与名称所暗示的相反,记录集当前可能包含重复项。这在未来可能会改变。
在模型上定义的方法是在记录集上执行的,方法的self
是一个记录集:
class AModel(models.Model):
_name = 'a.model'
def a_method(self):
# self can be anything between 0 records and all records in the
# database
self.do_operation()
对记录集进行迭代将产生新的单条记录的记录集,这与对Python字符串进行迭代产生单个字符的字符串非常相似:
def do_operation(self):
print(self) # => a.model(1, 2, 3, 4, 5)
for record in self:
print(record) # => a.model(1), then a.model(2), then a.model(3), ...
字段访问
记录集提供了一个“Active Record” 接口:模型字段可直接作为记录的属性直接读取和写入。
注解
当访问潜在多条记录的记录集上的非关系字段时,使用mapped()
,该函数返回一个列表:
total_qty = sum(self.mapped('qty')) # mapped返回一个列表,形如[2,4,5]
字段值也可以像字典项一样访问。设置字段的值会触发对数据库的更新:
>>> record.name
Example Name
>>> record.company_id.name
Company Name
>>> record.name = "Bob"
>>> field = "name"
>>> record[field]
Bob
警告
- 尝试读取多条记录上的字段将引发非关系字段的错误。
- 访问一个关系字段(
Many2one
,One2many
,Many2many
),总是返回记录集,如果未设置字段的话,则返回空记录集。
记录缓存和预取
Odoo为记录的字段维护一个缓存,这样,不是每个字段的访问都会发出数据库请求。
以下示例仅为第一条语句查询数据库:
record.name # 第一次访问从数据库获取值
record.name # 第二次访问从缓存获取值
为了避免一次读取一条记录上的一个字段,Odoo会按照一些启发式方法预取个记录和字段,以获得良好的性能。一旦必须在给定记录上读取字段,ORM实际上会在更大的记录集上读取该字段,并将返回的值存储在缓存中以供后续使用。预取的记录集通常是通过迭代获得记录的记录集。此外,所有简单的存储字段(布尔值、整数、浮点值、字符、文本、日期、日期时间、选择、many2one)都会被提取;它们对应于模型表的列,并在同一查询中高效地获取。
考虑以下示例,其中partners
为包含1000条记录的记录集。如果不进行预取,循环将对数据库进行2000次查询。使用预取,只进行一次查询
for partner in partners:
print partner.name # first pass prefetches 'name' and 'lang'
# (and other fields) on all 'partners'
print partner.lang
预取也适用于辅助记录:当读取关系字段时,它们的值(即记录)将被订阅以供将来预取。访问这些辅助记录之一将预取同一模型中的所有辅助记录。这使得以下示例仅生成两个查询,一个用于合作伙伴,另一个用于国家/地区:
countries = set()
for partner in partners:
country = partner.country_id # first pass prefetches all partners
countries.add(country.name) # first pass prefetches all countries
方法修饰器
Odoo API模块定义了Odoo环境和方法修饰符
odoo.api.autovacuum(method)
[源代码]修饰一个方法,使其由日常vacuum cron作业(模型
ir.autovacuum
)调用。这通常用于垃圾收集之类的不需要特定cron作业的任务odoo.api.constrains(*args)
[源代码]装饰一个约束检查器
每个参数必须是校验使用的字段名称:
@api.constrains('name', 'description')
def _check_description(self):
for record in self:
if record.name == record.description:
raise ValidationError("Fields name and description must be different")
当记录的某个命名字段被修改时调用装饰器函数。
如果校验失败,应该抛出
ValidationError
警告
@constrains
仅支持简单的字段名称,不支持并忽略点分名称(关系字段的字段,比如partner_id.customer
)@constrains
仅当修饰方法中声明的字段包含在create
或write
调用中时才会触发。这意味着视图中不存在的字段在创建记录期间不会触发调用。必须重写create
,以确保始终触发约束(例如,测试是否缺少值)odoo.api.depends(*args)
[源代码]返回一个装饰器,该装饰器指定
compute
方法的字段依赖关系(对于新型函数字段)。参数支持是由点分隔的字段名序列组成的字符串:pname = fields.Char(compute='_compute_pname') @api.depends('partner_id.name', 'partner_id.is_company')
def _compute_pname(self):
for record in self:
if record.partner_id.is_company:
record.pname = (record.partner_id.name or "").upper()
else:
record.pname = record.partner_id.name
有的也可能传递一个函数作为参数,这种情况下,依赖通过调用 在这种情况下,通过使用字段的模型调用函数来提供依赖项
odoo.api.depends_context(*args)
[源代码]返回一个修饰符,该修饰符指定非存储的“compute”方法的上下文依赖项。每个参数都是上下文字典中的键:
price = fields.Float(compute='_compute_product_price') @api.depends_context('pricelist')
def _compute_product_price(self):
for product in self:
if product.env.context.get('pricelist'):
pricelist = self.env['product.pricelist'].browse(product.env.context['pricelist'])
else:
pricelist = self.env['product.pricelist'].get_default_pricelist()
product.price = pricelist.get_products_price(product).get(product.id, 0.0)
所有依赖项都必须是可哈希的。以下键具有特殊支持:
company
(上下文中的值或当前公司id),uid
(当前用户ID和超级用户标记),active_test
(env.context
或者field.context
中的值).
odoo.api.model(method)
[源代码]修饰一个record-style的方法,其中
self
是一个空记录集,但其内容不相关,只有模型相关,可以理解为不会创建对应数据库记录的模型对象。模型层面的操作需要添加此修饰器,相当于类静态函数@api.model
def method(self, args):
...
odoo.api.model_create_multi(method)
[源代码]修饰一个以字典列表为参数,并创建多条记录的方法。可能仅通过一个字典或者字典列表调用该方法:
record = model.create(vals)
records = model.create([vals, ...])
odoo.api.onchange(*args)
[源代码]返回一个修饰器来修饰给定字段的onchange方法。
在出现字段的表单视图中,当修改某个给定字段时,将调用该方法。在包含表单中存在的值的伪记录上调用该方法。该记录上的字段赋值将自动返回客户端。
每个参数必须是字段名:
@api.onchange('partner_id')
def _onchange_partner(self):
self.message = "Dear %s" % (self.partner_id.name or "")
return {
'warning': {'title': "Warning", 'message': "What is this?", 'type': 'notification'},
}
如果类型设置为通知(
notification
),则警告将显示在通知中。否则,它将作为默认值显示在对话框中警告
@onchange
仅支持简单的字段名称,不支持并自动忽略点分名称(关系字段的字段,比如partner_id.tz
)危险
由于
@onchange
返回伪记录的记录集,对上述记录集调用任何一个CRUD方法(create()
,read()
,write()
,unlink()
)都是未定义的行为,因为它们可能还不存在于数据库中。相反,只需像上面的示例中所示那样设置记录的字段或调用update()
方法警告
one2many
或者many2many
字段不可能通过onchange
修改其自身。这是客户端限制 - 查看 #2693odoo.api.returns(model, downgrade=None, upgrade=None)
[源代码]为返回
model
实例的方法返回一个修饰器参数
model – 模型名称,或者表示当前模型的
'self'
downgrade – 一个用于转换record-style的
value
为传统风格输出的函数downgrade(self, value, *args, **kwargs)
upgrade – 一个用于转换传统风格(traditional-style)的
value
为record-style的输出的函数upgrade(self, value, *args, **kwargs)
参数
self
,*args
和**kwargs
以record-style方式传递给方法修饰器将方法输出适配api风格:
id
,ids
或者False
对应传统风格,而记录集对应记录风格:@model
@returns('res.partner')
def find_partner(self, arg):
... # return some record # output depends on call style: traditional vs record style
partner_id = model.find_partner(cr, uid, arg, context=context) # recs = model.browse(cr, uid, ids, context)
partner_record = recs.find_partner(arg)
注意,被修饰的方法必须满足那约定。
这些修饰器是自动继承的:重写被修饰的现有方法的方法将被相同的
@return(model)修饰
环境(Environment)
Environment
存储ORM使用的各种上下文数据:数据库游标(用于数据库查询)、当前用户(用于访问权限检查)和当前上下文(存储任意元数据)。环境还存储缓存。
所有记录集都有一个环境,它是不可变的,可以使用env
访问,并提供对以下的访问:
- 当前用户 (
user
) - 游标 (
cr
) - 超级用户标识(
su
) - 或者上下文 (
context
)
>>> records.env
<Environment object ...>
>>> records.env.user
res.user(3)
>>> records.env.cr
<Cursor object ...)
>>> self.env.context # 返回字典数据,等价于 self._context
{'lang': 'en_US', 'tz': 'Europe/Brussels'}
>>> self._context
{'lang': 'en_US', 'tz': 'Europe/Brussels'}
从其他记录集创建记录集时,将继承环境。环境可用于获取其他模型中的空记录集,并查询该模型:
>>> self.env['res.partner']
res.partner()
>>> self.env['res.partner'].search([['is_company', '=', True], ['customer', '=', True]])
res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74)
Environment.ref(xml_id, raise_if_not_found=True)
[源代码]
返回与给定xml_id
对应的记录。
Environment.lang
返回当前语言代码。返回类型str
Environment.user
返回当前用户(作为一个实例)。返回类型res_users
Environment.company
返回当前公司(作为一个实例)
如果未在上下文 (allowed_company_ids
)中指定, 返回当前用户的主公司(If not specified in the context(allowed_company_ids
), fallback on current user companies)
引发
AccessError – 非法或者为授权
allowed_company_ids
上下文key内容返回
当前公司(默认值=
self.user.company_id
)返回类型
res.company
警告
在sudo模式下没有应用健康检查!在sudo模式下,用户可以访问任何公司,即使不是在他允许的公司。
这允许触发公司间修改,即使当前用户无权访问目标公司
Environment.companies
返回用户启用的公司的记录集。
如果未在上下文 (allowed_company_ids
)中指定, 返回当前用户的主公司(If not specified in the context(allowed_company_ids
), fallback on current user companies)
引发
AccessError – 非法或者为授权
allowed_company_ids
上下文key内容返回
当前公司(默认值=
self.user.company_id
)返回类型
res.company
警告
在sudo模式下没有应用健康检查!在sudo模式下,用户可以访问任何公司,即使不是在他允许的公司。
这允许触发公司间修改,即使当前用户无权访问目标公司
修改环境
Model.with_context([context][, **overrides])
-> records[源代码]返回附加到扩展上下文的此记录集的新版本。
扩展上下文是提供的合并了
overrides
的context
,或者是合并了overrides
当前context
# current context is {'key1': True}
r2 = records.with_context({}, key2=True)
# -> r2._context is {'key2': True}
r2 = records.with_context(key2=True)
# -> r2._context is {'key1': True, 'key2': True}
需要注意的是,上下文是和记录集绑定的,修改后的上下文并不会在其它记录集中共享。
Model.with_user(user)
[源代码]以非超级用户模式返回附加到给定用户的此记录集的新版本,即传入一条用户记录并返回该用户的环境,除非
user
是超级用户(按照约定,超级用户始终处于超级用户模式)Model.with_company(company)
[源代码]返回具有已修改上下文的此记录集的新版本,这样:
result.env.company = company
result.env.companies = self.env.companies | company
参数
company (
res_company
或者 int) – 新环境的主公司
警告
当当前用户使用未经授权的公司时,如果不是在sudoed环境中访问该公司,则可能会触发
AccessError
Model.with_env(env)
[源代码]返回附加到所提供环境的此记录集的新版本。
参数
env (
Environment
) –
警告
新环境将不会从当前环境的数据缓存中受益,因此稍后的数据访问可能会在从数据库重新获取数据时产生额外的延迟。返回的记录集具有与
self
相同的预取对象。Model.sudo([flag=True])
[源代码]根据
flag
,返回启用或禁用超级用户模式的此记录集的新版本。超级用户模式不会更改当前用户,只是绕过访问权限检查。警告
使用
sudo
可能会导致数据访问跨越记录规则的边界,可能会混淆要隔离的记录(例如,多公司环境中来自不同公司的记录)。这可能会导致在多条记录中选择一条记录的方法产生不直观的结果,例如获取默认公司或选择物料清单。
注解
因为必须重新评估记录规则和访问控制,所以新的记录集将不会从当前环境的数据缓存中受益,因此以后的数据访问可能会在从数据库重新获取时产生额外的延迟。返回的记录集具有与
self
相同的预取对象。
SQL执行
环境上的cr
属性是当前数据库事务的游标,允许直接执行SQL,无论是对于难以使用ORM表达的查询(例如复杂join),还是出于性能原因
self.env.cr.execute("some_sql", params)
由于模型使用相同的游标,并且Environment
保存各种缓存,因此当在原始SQL中更改数据库时,这些缓存必须失效,否则模型的进一步使用可能会变得不连贯。在SQL中使用CREATE
、UPDATE
或DELETE
,但不使用SELECT
(只读取数据库)时,必须清除缓存。
注解
可以使用 invalidate_cache()
执行缓存的清理
Model.invalidate_cache(fnames=None, ids=None)
[源代码]修改某些记录后,使记录缓存无效。如果
fnames
和ids
都为None
,则清除整个缓存。参数:
fnames–已修改字段的列表,
None
表示所有字段ids–修改的记录ID的列表,
None
表示所有记录
警告
执行原始SQL绕过ORM,从而绕过Odoo安全规则。请确保在使用用户输入时对查询进行了清洗,如果确实不需要使用SQL查询,请使用ORM实用程序。
常用ORM方法Common ORM methods
创建/更新(Create/update)
Model.create(vals_list)
→ records[源代码]为模型创建新记录
使用字典列表
vals_list
中的值初始化新记录,如果需要,使用default_get()
中的值参数
vals_list (list) --模型字段的值,作为字典列表:
[{'field_name':field_value,…},…]
为了向后兼容,vals_list
可以是一个字典。它被视为单个列表[vals]
,并返回一条记录。有关详细信息请参见write()
返回
创建的记录
引发
如果用户对请求的对象没有创建权限
如果用户尝试绕过访问规则在请求的对象上创建
ValidationError – 如果用户尝试为字段输入不在选择范围内的无效值
UserError–如果将在对象层次结构中创建循环,操作的一个结果(例如将对象设置为其自己的父对象)
Model.copy(default=None)
[源代码]使用默认值更新拷贝的记录
self
参数
default (dict) – 用于覆盖复制记录的原始值的字段值的字典,形如:
{'field_name': overridden_value, ...}
返回
新记录
Model.default_get(fields_list)
→ default_values[源代码]返回
fields_list
中字段的默认值。默认值由上下文、用户默认值和模型本身决定参数
fields_list (list) – 需要获取其默认值的字段名称
返回
将字段名映射到相应的默认值(如果它们具有的话)的字典。
返回类型
dict
注解
不考虑未请求的默认值,不需要为名称不在
fields_list
中的字段返回值。Model.name_create(name)
→ record[源代码]通过调用
create()
创建新记录,调用时create()
时只提供一个参数值:新记录的显示名称。新记录将使用适用于此模型的任何默认值初始化,或通过上下文提供。
create()
的通常行为适用参数
name – 要创建记录的显示名称
返回类型
元组
返回
创建的记录的
name_get()
成对值
Model.write(vals)
[源代码]使用提供的值更新当前记录集中的所有记录
参数:
vals (dict) –需要更新的字段及对应的值,比如:
{'foo': 1, 'bar': "Qux"}
,将设置foo
值为1
,bar
为"Qux"
,如果那些为合法的话,否则将触发错误。需要特别注意的是,需要更新的字段越多,更新速度越慢(笔者实践时发现的,但是没验证是否和字段类型有关,特别是关系字段,关系字段的更新可能会调用对应模型的write
方法,该方法如果被重写了,也可能会导致耗时的增加,总的来说,遵守一个原则,仅更新需要更新的字段)引发
如果用户对请求的对象没有创建权限
如果用户尝试绕过访问规则在请求的对象上创建
ValidationError – 如果用户尝试为字段输入不在选择范围内的无效值
UserError–如果将在对象层次结构中创建循环,操作的一个结果(例如将对象设置为其自己的父对象)(官方原文:if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
对于数字型字段(
odoo.fields.Integer
,odoo.fields.Float
) ,值必须为对应类型对于
odoo.fields.Boolean
, 值必须为bool
类型对于
odoo.fields.Selection
, 值必须匹配选择值(通常为str
,有时为int
)对于
odoo.fields.Many2one
,值必须为记录的数据库标识其它非关系字段,使用字符串值
危险
出于历史和兼容性原因,
odoo.fields.Date
和odoo.fields.Datetime
字段使用字符串作为值(写入和读取),而不是date
或datetime
。这些日期字符串仅为UTC格式,并根据odoo.tools.misc.DEFAULT_SERVER_DATE_FORMAT
和odoo.tools.miisc.DEFAULT_SERVER _DATETIME_FORMAT
进行格式化odoo.fields.One2many
和odoo.fields.Many2many
使用特殊的“命令”格式来操作存储在字段中/与字段关联的记录集。这种格式是一个按顺序执行的三元组列表,其中每个三元组都是要对记录集执行的命令。并非所有命令都适用于所有情况。可能的命令有:
(0, 0, values)
从提供的
values
字典创建新记录,形如(0, 0, {'author': user_root.id, 'body': 'one'})
。(1, id, values)
使用
values
字典中的值更新id值为给定id
值的现有记录。不能在create()
中使用。(2, id, 0)
从记录集中删除id为指定
id
的记录,然后(从数据库中)删除它不能在
create()
中使用。(3, id, 0)
从记录集中删除id为指定
id
的记录,但不删除它。不能在create()
中使用。(4, id, 0)
添加一条id为指定
id
的已存在记录到记录集(5, 0, 0)
从结果集移除所有记录, 等价于显示的对每条记录使用命令
3
。 不能在create()
中使用。(6, 0, ids)
根据
ids
列表,替换所有已存在记录, 等价于使用命令(5, 0, 0)
,随后对ids
中的每个id使用命令(4, id, 0)
。实践发现,针对One2many字段,如果ids
对应记录的Many2one
字段没存储当前模型主键ID值时,无法使用该命令。
实际使用时,这些命令可以组合使用,如下,给
fieldName
设置值时,会先指定命令5
,在执行命令0
Model.write({'fieldName': [(5, 0, 0), (0, 0, dict_value)]})
Model.flush(fnames=None, records=None)
[源代码]处理所有待定的计算(在所有模型上),并将所有待定的更新刷新到数据库中(Process all the pending computations (on all models), and flush all the pending updates to the database)。
参数
fnames – 需要刷新的字段名称列表。如果给定,则将处理范围限制为当前模型的给定字段。
records – 如果给定 (协同
fnames
), 限制处理范围为给定的记录
搜索/读取(Search/Read)
Model.browse([ids])
→ records[源代码]在当前环境中查询
ids
参数指定的记录并返回记录结果集,如果为提供参数,或者参数为[]
,则返回空结果集self.browse([7, 18, 12])
res.partner(7, 18, 12)
参数
ids (int 或者 list(int) 或 None) – id(s)
返回
recordset
Model.search(args[, offset=0][, limit=None][, order=None][, count=False])
[源代码]基于
args
搜索域搜索记录参数
args – 搜索域。使用
[]
代表匹配所有记录。offset (int) – 需要忽略的结果记录数 (默认: 0)
limit (int) – 最大返回记录数 (默认返回所有)
order (str) – 排序字符串
count (bool) – 如果为
True
,仅计算并返回匹配的记录数 (默认: False)返回
最多
limit
条符合搜索条件的记录引发
AccessError –如果用户尝试绕过访问规则读取请求的对象
Model.search_count(args)
→ int[源代码]返回当前模型中匹配提供的搜索域
args
的记录数.Model.name_search(name='', args=None, operator='ilike', limit=100)
→ records[源代码]搜索比较显示名称与给定
name
匹配(匹配方式为给定operator
),且匹配搜索域args
的记录例如,这用于基于关系字段的部分值提供建议。有时被视为
name_get()
的反函数,但不能保证是。此方法等效于使用基于
display_name
的搜索域调用search()
,然后对搜索结果执行“name_get()”关于搜索结果参数
name (str) – 需要匹配的名称
args (list) – 可选的搜索域, 进一步指定限制
operator (str) – 用于匹配
name
的域操作,比如'like'
或者'='
limit (int) – 可选参数,返回最大记录数
返回类型
list
返回
所有匹配记录的对值
(id, text_repr)
列表
Model.read([fields])
[源代码]读取
self
中记录的指定字段, 低阶/RPC方法。Python代码中,优选browse()
.参数
fields – 需要返回的字段名称(默认返回所有字段)
返回
字典的列表,该字典为字段名称同其值映射,每条记录一个字典
引发
AccessError – 如果用户没有给定记录的读取权限
Model.read_group(domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True)
[源代码]获取列表视图中按给定
groupby
字段分组的记录列表。参数
domain (list) – 搜索域。使用
[]
表示匹配所有fields (list) – 对象上指定的列表视图中存在的字段列表。每个元素要么是“field”(字段名,使用默认聚合),要么是“field:agg”(使用聚合函数“agg”聚合字段),要么就是“name:agg(field)”(使用“agg'聚合字段并将其当做“name”返回)。可能的聚合函数为PostgreSQL提供的函数(https://www.postgresql.org/docs/current/static/functions-aggregate.html),且“count_distict”,具有预期含义。
groupby (list) – 记录分组依据的分组依据描述列表。groupby描述要么是字段(然后将按该字段分组),要么是字符串“field:groupby_function”。目前,唯一支持的函数是
day
、week
、month
、quarter
或year
,它们只适用于date/datetime
字段offset (int) – 需要跳过的记录数,可选参数。
limit (int) – 需要返回的最大记录数,可选参数
orderby (str) – 排序字符串(当前仅支持
Many2one
字段)。可选参数。lazy (bool) – 如果为
True
,则结果只按第一个groupby分组,其余groupby放入__context
键中。如果为False
,则在一个调用中完成所有groupby。返回
字典列表(每条记录一个字典)。包含:按
groupby
参数中指定字段分组后的字段的值__domain
: 指定搜索条件的元组的列表__context
: 拥有类似groupby
参数的字典返回类型
[{‘field_name_1’: value, …]
引发
如果用户对所请求的对象没有读取权限,
如果用户尝试绕过对访问规则读取所请求对象
Model.copy_data()
拷贝当前模型记录的数据,返回一个字典,字典key为模型字段名称,key值为对应的字段值。注意:返回字典key不包含Odoo系统自动生成的模型表字段:
create_uid
,create_date
,write_date
,write_uid
,id
字段/视图(Fields/Views)s
Model.fields_get([fields][, attributes])
[源代码]返回每个字段的定义
返回的值是包含字典的字典(按字段名索引)。包括继承字段。将转换string、help和selection(如果存在)属性
参数
fields – 字段列表, 如果未提供或者为
[]
则表示所有attributes – 每个字段需要返回的属性描述列表。 如果未提供或者为
[]
则表示所有
Model.fields_view_get([view_id | view_type='form'])
[源代码]获取所请求视图的详细组成,如字段、模型、视图架构
参数
view_id (int) – 视图的ID或者None
view_type (str) – 返回视图的类型,如果
view_id
为None
的话(‘form’, ‘tree’, …)toolbar (bool) – 设置为
True
以包含上下文操作submenu – 已弃用
返回
请求视图的组成(包括继承的视图和扩展)
返回类型
dict
引发
如果继承的视图具有除“before”、“after”、“inside”、“replace”以外的未知位置
则如果在父视图中找到除“position”以外的标记
Invalid ArchitectureError – 如果框架中有定义form, tree, calendar, search 等以外的视图
搜索域(Search domains)
域是一个标准列表,每个标准都是(field_name,operator,value)
的三元组(一个“列表”或“元组”),其中:
field_name
(str
)当前模块的字段名称 或通过
Many2one
,使用点符号的关系遍历,例如'street'
或者'partner_id.country'
operator
(str
)用于比较
field_name
与value
的运算符。有效运算符为:=
等于
!=
不等于
>
大于
>=
大于等于
<
小于
<=
小于等于
=?
未设置或者等于(如果
value
为None
或者False
则返回True
,否则与=
一样)=like
将
field_name
同value
模式匹配。模式中的下划线_
匹配任何单个字符;百分号%
匹配任何零个或多个字符的字符串like
将
field_name
同%value%
模式匹配。类似=like
,但是匹配前使用%
包装value
not like
不匹配
%value%
模式ilike
大小写敏感的
like
not ilike
大小写敏感的
not like
=ilike
大小写敏感的
=like
in
等于
value
中的任意项,value
应该为项列表not in
不等于
value
中的任意项child_of
是
value
记录的child(后代)(value
可以是一个项或一个项列表)。考虑模型的语义(即遵循由_parent_name
命名的关系字段)。parent_of
是
value
记录的parent(祖先)(value
可以是一个项或一个项列表)。考虑模型的语义(即遵循由_parent_name
命名的关系字段)
value
变量类型,必须可同命名字段比较(通过
operator
)
可以使用前缀形式的逻辑运算符组合域条件:
'&'
逻辑 AND, 默认操作,以将条件相互结合。Arity 2 (使用下2个标准或组合)
'|'
逻辑 OR arity 2
'!'
逻辑 *NOT * arity 1
例子:
搜索来自比利时或德国名为ABC,且语言不为英语的合作伙伴:
[('name','=','ABC'),
('language.code','!=','en_US'),
'|',('country_id.code','=','be'),
('country_id.code','=','de')]
该域被解释为:
(name is 'ABC')
AND (language is NOT english)
AND (country is Belgium OR Germany)
unlink
Model.unlink()
[源代码]删除当前记录集中的记录
引发
如果用户没有所请求对象的unlink权限
如果用户尝试绕过访问规则对请求对象执行unlink
UserError –如果记录为其它记录的默认属性
记录(集)信息
Model.ids
返回与
self
对应的真实记录IDodoo.models.env
返回给定记录集的环境。类型
Environment
Model.exists()
→ records[源代码]返回self中存在的记录子集并将删除的记录标记为缓存中的记录. 可用作对记录的测试:
if record.exists():
...
按约定,将新记录作为现有记录返回
Model.ensure_one()
[源代码]验证当前记录集只拥有一条记录
引发odoo.exceptions.ValueError –
len(self) != 1
Model.name_get()
→ [id, name, ...][源代码]返回
self
中记录的文本表示形式。默认情况下,为display_name
字段的值。返回
每个记录的
(id, text_repr)
对值列表返回类型
list(tuple)
Model.get_metadata()
[源代码]返回关于给定记录的元数据
返回
每个请求记录的所有权字典列表 list of ownership dictionaries for each requested record
返回类型
具有以下关键字的字典列表:
- id: 对象ID
- create_uid: 创建记录的用户
- create_date: 创建记录的日期
- write_uid: 上次更改记录的用户
- write_date: 上次更改记录的日期
- xmlid: 用于引用此记录的XML ID(如果有),格式为
module.name
- noupdate: 一个布尔值,指示记录是否将被更新
操作
记录集是不可变的,但可以使用各种集合操作组合同一模型的集合,从而返回新的记录集
record in set
返回record
(必须为只包含一个元素的记录集) 是否在set
中。record not in set
则刚好相反set1 <= set2
andset1 < set2
返回set1
是否是set2
的子集set1 >= set2
andset1 > set2
返回set1
是否是set2
的超集set1 | set2
返回两个记录集的并集。一个包含出现在两个源记录集中的所有记录的记录集set1 & set2
返回两个记录集的交集。一个只包含同时存在两个源记录集中的记录的记录集。set1 - set2
返回一个包含仅出现在set1
中的记录的记录集
记录集是可迭代的,因此通常的Python工具可用于转换(map()
,sorted()
,ifilter()
,…),然后这些函数返回list
或iterator
,删除对结果调用方法或使用集合操作的能力。
因此,记录集提供以下返回记录集本身的操作(如果可能):
Filter
Model.filtered(func)
[源代码]参数
func (可调用对象 或者 str) – 一个函数或者点分字段名称序列
返回
满足func的记录集,可能为空。
# only keep records whose company is the current user's
records.filtered(lambda r: r.company_id == user.company_id) # only keep records whose partner is a company
records.filtered("partner_id.is_company")
Model.filtered_domain(domain)
[源代码]
Map
Model.mapped(func)
[源代码]对
self
中的所有记录应用func
,并将结果作为列表或记录集返回(如果func
返回记录集)。后者返回的记录集的顺序是任意的。参数
func (可调用对象 或 str) – 一个函数或者点分字段名称序列
返回
如果
func
为False
则返回self
作用于所有self
中记录的func
的返回结果返回类型
list 或 recordset
# returns a list of summing two fields for each record in the set
records.mapped(lambda r: r.field1 + r.field2)
提供的函数可以是获取字段值的字符串:
# returns a list of names
records.mapped('name') # returns a recordset of partners
records.mapped('partner_id') # returns the union of all partner banks, with duplicates removed
records.mapped('partner_id.bank_ids')
注解
V13开始, 支持多多关系字段访问,像mapped调用那样工作:
records.partner_id # == records.mapped('partner_id')
records.partner_id.bank_ids # == records.mapped('partner_id.bank_ids')
records.partner_id.mapped('name') # == records.mapped('partner_id.name')
Sort
Model.sorted(key=None, reverse=False)
[源代码]返回按
key
排序的记录集self
参数
key (可调用对象或者str 或者
None
) – 一个参数的函数,为每个记录返回一个比较键,或字段名,或None
,如果为None
,记录按照默认模型的顺序排序reverse (bool) – 如果为
True
, 返回逆序排序的结果
# sort records by name
records.sorted(key=lambda r: r.name)
继承与扩展(Inheritance and extension)
Odoo提供三种不同的机制,以模块化方式扩展模型:
- 从现有模型创建新模型,向副本中添加新信息,但保留原始模块
- 扩展其他模块中定义的模型,替换以前的版本
- 将模型的一些字段委派给它包含的记录
经典继承
当同时使用_inherit
和 _name
属性时,Odoo使用现有模型(通过_inherit
提供)作为base创建新模型。新模型从其base中获取所有字段、方法和元信息(默认值等)。
class Inheritance0(models.Model):
_name = 'inheritance.0'
_description = 'Inheritance Zero'
name = fields.Char()
def call(self):
return self.check("model 0")
def check(self, s):
return "This is {} record {}".format(s, self.name)
class Inheritance1(models.Model):
_name = 'inheritance.1'
_inherit = 'inheritance.0'
_description = 'Inheritance One'
def call(self):
return self.check("model 1")
使用它们:
a = env['inheritance.0'].create({'name': 'A'})
b = env['inheritance.1'].create({'name': 'B'})
a.call()
b.call()
输出:
“This is model 0 record A” “This is model 1 record B”
第二个模型继承了第一个模型的check
方法及其name
字段,但重写了call
方法,就像使用标准Python继承一样。
说明:
以上为官方文档给出的案例,笔者实践发现是无法直接运行的。
模型继承会继承父类中的所有属性,会拷贝字段、属性和方法。
可以同时继承多个模型,比如:
_inherit = ['res.partner', 'md.status.mixin']
扩展
当使用_inherit
但省略_name
时,新模型将替换现有模型,实质上就是在原有模型上扩展。这对于将新字段或方法添加到现有模型(在其他模块中创建)或自定义或重新配置它们(例如更改其默认排序顺序)非常有用:
class Extension0(models.Model):
_name = 'extension.0'
_description = 'Extension zero'
name = fields.Char(default="A")
def func():
print('test a')
class Extension1(models.Model):
_inherit = 'extension.0'
description = fields.Char(default="Extended")
def func(): # 重写函数
print('test b')
record = env['extension.0'].create({})
record.read()[0]
返回:
{'name': "A", 'description': "Extended"}
注解
它还会返回各种自动生成的字段,除非它们被禁用了。
env['extension.0'].func({})
返回:
test b
注意:
如果同时继承抽象模块和非抽象模块,并把_name
配置为非抽象模块,抽象模块的字段也会添加到非抽象模块对应的表
委托(Delegation)
第三种继承机制提供了更大的灵活性(可以在运行时更改),但威力更小:使用_inherits
模型,将当前模型中未找到的任何字段的查找委托给“children”模型。委托通过Reference
执行在父模型上自动设置的字段。
主要区别在于意义。使用委托时,模型has one而不是is one,从而将关系转换为组合而不是继承:
class Screen(models.Model):
_name = 'delegation.screen'
_description = 'Screen'
size = fields.Float(string='Screen Size in inches')
class Keyboard(models.Model):
_name = 'delegation.keyboard'
_description = 'Keyboard'
layout = fields.Char(string='Layout')
class Laptop(models.Model):
_name = 'delegation.laptop'
_description = 'Laptop'
_inherits = {
'delegation.screen': 'screen_id',
'delegation.keyboard': 'keyboard_id',
}
name = fields.Char(string='Name')
maker = fields.Char(string='Maker')
# a Laptop has a screen
screen_id = fields.Many2one('delegation.screen', required=True, ondelete="cascade")
# a Laptop has a keyboard
keyboard_id = fields.Many2one('delegation.keyboard', required=True, ondelete="cascade")
record = env['delegation.laptop'].create({
'screen_id': env['delegation.screen'].create({'size': 13.0}).id,
'keyboard_id': env['delegation.keyboard'].create({'layout': 'QWERTY'}).id,
})
record.size
record.layout
将产生结果:
13.0
'QWERTY'
可以直接修改委托字段:
record.write({'size': 14.0})
警告
使用委托继承时,方法不是被继承的,只有字段
警告
_inherits
或多或少已实现,如果可以的话避免用它(_inherits
is more or less implemented, avoid it if you can)- 链式的
_inherits
基本上没有实现,我们不对最终行为做任何保证。(chained_inherits
is essentially not implemented, we cannot guarantee anything on the final behavior)
字段增量定义
字段定义为模型类的类属性。如果扩展了模型,还可以通过在子类上重新定义具有相同名称和类型的字段来扩展字段定义。在这种情况下,字段的属性取自父类,并由子类中给定的属性覆盖。
例如,下面的第二个类仅在state
字段上添加工具提示:
class First(models.Model):
_name = 'foo'
state = fields.Selection([...], required=True)
class Second(models.Model):
_inherit = 'foo'
state = fields.Selection(help="Blah blah blah")
入门实践
模型定义
odoo14\custom\estate\models\estate_property_tag.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields
class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'estate property tag'
_order = 'name'
name = fields.Char(string='tag', required=True)
color = fields.Integer(string='Color')
odoo14\custom\estate\models\estate_property_offer.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields
class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'estate property offer'
property_id = fields.Many2one('estate.property', required=True)
price = fields.Integer()
odoo14\custom\estate\models\estate_property_type.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields
class EstatePropertyType(models.Model):
_name = 'estate.property.type'
_description = 'estate property type'
name = fields.Char(string='name', required=True)
odoo14\custom\estate\models\estate_property.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from odoo import models, fields
class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'estate property table'
_order = 'id desc'
name = fields.Char(required=True)
property_type_id = fields.Many2one("estate.property.type", string="PropertyType")
tag_ids = fields.Many2many("estate.property.tag")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="PropertyOffer")
ORM操作实践
>>> self.env['estate.property.type']
estate.property.type()
# 创建单条记录
>>> self.env['estate.property.type'].create({'name':'house'})
estate.property.type(1,)
# 按id查询记录
>>> self.env['estate.property.type'].browse([1])
estate.property.type(1,)
# 未给定id列表,或者未提供参数的情况下,返回空记录集
>>> self.env['estate.property.type'].browse()
estate.property.type()
>>> self.env['estate.property.type'].browse([])
estate.property.type()
# 复制记录
>>> self.env['estate.property.type'].browse([1]).copy({'name':'garden'})
estate.property.type(2,)
# 针对仅获取单条记录的记录集,可通过 records.fieldName 的方式引用对应字段(读取字段值,或者给字段赋值)
>>> self.env['estate.property.type'].browse([2]).name
'garden'
# 更新记录
>>> self.env['estate.property.type'].browse([1]).name
'house'
>>> self.env['estate.property.type'].browse([1]).write({'name':'garden'})
True
>>> self.env['estate.property.type'].browse([1]).name
'garden'
# 针对仅获取单条记录的记录集,可通过 records.fieldName 的方式引用对应字段(读取字段值,或者给字段赋值)
>>> self.env['estate.property.type'].browse([1]).name = 'house'
>>> self.env['estate.property.type'].browse([1]).name
'house'
# 不能直接通过以下方式,试图在write函数指定id的方式来更新记录 # 不会修改任何记录,也未新增任何记录
>>> self.env['estate.property.type'].write({'id':1, 'name':'apartment'})
True
>>> self.env['estate.property.type'].browse([1]).name
'house'
# 通过search api查询记录集
>>> self.env['estate.property.type'].search([])
estate.property.type(1, 2)
# 批量创建记录
# 创建测试用数据
>>> self.env['estate.property.tag'].create([{'name': 'tag1', 'color': 1}, {'name': 'tag1', 'color': 2}, {'name': 'tag1', 'color': 3}])
estate.property.tag(1, 2, 3)
# 注意:Many2one类型字段的值,必须设置为对应记录的主键id
>>> self.env['estate.property'].create({'name': 'house in beijing', 'property_type_id': 1, 'tag_ids':[(0,0, {'name': 'tag1', 'color': 3})]})
estate.property(1,)
>>> self.env['estate.property'].search([])
estate.property(1,)
# 查询关系字段值
>>> self.env['estate.property'].browse([1]).property_type_id # Many2one
estate.property.type(1,)
>>> self.env['estate.property'].browse([1]).tag_ids # Many2many
estate.property.tag(4,)
# 更新Many2many关系字段值
>>> self.env['estate.property'].browse([1]).tag_ids.write({'name': 'tag4', 'color': 4})
True
>>> self.env['estate.property'].browse([1]).tag_ids.color
4
>>> self.env['estate.property.tag'].search([])
estate.property.tag(1, 2, 3, 4)
# 查询关系字段值
>>> self.env['estate.property'].browse([1]).offer_ids # One2many
estate.property.offer()
## 更新One2many关系字段值
# 为关系字段创建关联记录
# (0, 0, values)
# 从提供的`values`字典创建新记录。
>>> self.env['estate.property'].browse([1]).offer_ids = [(0, 0, {'property_id':1})]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(1,)
>>> self.env['estate.property'].browse([1]).offer_ids.property_id
estate.property(1,)
# 更新关系字段所代表记录对象的属性值
# (1, id, values)
# 使用 values 字典中的值更新id值为给定 id 值的现有记录。不能在create()中使用。
>>> self.env['estate.property'].browse([1]).offer_ids = [(1, 1, {'price': 30000})]
>>> self.env['estate.property'].browse([1]).offer_ids.price
30000
# 删除关系字段关联记录
# (3, id, 0)
# 从记录集中删除id为id的记录,但不从数据库中删除它,可以理解为仅解除关联。不能在create()中使用。
>>> self.env['estate.property'].browse([1]).offer_ids = [(3,1,0)]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer()
# 将已存在记录同关系字段关联
# (4, id, 0)
# 添加一条id为id已存在记录到记录集
>>> self.env['estate.property.offer'].browse([1])
estate.property.offer(1,)
>>> self.env['estate.property'].browse([1]).offer_ids = [(4,1,0)]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(1,)
# 为关系字段一次创建多条关联记录
>>> self.env['estate.property'].browse([1]).offer_ids = [(0, 0, {'property_id':1, 'price': 100000}),(0, 0, {'property_id':1, 'price': 200000}), (0, 0, {'property_id':1, 'price': 200000}), (0, 0, {'property_id':1, 'price': 300000})]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(1, 2, 3, 4, 5)
# 替换关系字段关联的记录
# (6, 0, ids)
# 根据ids列表,替换所有已存在记录, 等价于使用命令(5, 0, 0),随后对ids中的每个id使用命令(4, id, 0)。
>>> self.env['estate.property'].browse([1]).offer_ids = [(3,1,0),(3,2,0)]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(3, 4, 5)
>>> self.env['estate.property'].browse([1]).offer_ids = [(6, 0, [1,2])] # 报错, 因为ID 1,2 对应的记录,其Many2one字段值为null
# 为Many2many关系字段创建多条关联记录
>>> self.env['estate.property'].create({'name': 'house in shanghai'})
estate.property(2,)
>>> self.env['estate.property'].browse([2])
estate.property(2,)
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag()
>>> self.env['estate.property'].browse([2]).tag_ids = [(0, 0, {'name': 'tag5', 'color': 5}), (0, 0, {'name': 'tag6', 'color': 6}), (0, 0, {'name': 'tag7', 'color': 7})]
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag(5, 6, 7)
# 删除关系字段关联的记录
# (2, id, 0)
# 从记录集中删除id为id的记录,然后(从数据库中)删除它,不能在create()中使用
>>> self.env['estate.property'].browse([2]).tag_ids = [(2, 5, 0)]
2023-01-29 08:48:25,491 15984 INFO odoo odoo.models.unlink: User #1 deleted estate.property.tag records with IDs: [5]
>>> print( self.env['estate.property.tag'].browse([5]).exists())
estate.property.tag()
>>> if self.env['estate.property.tag'].browse([5]).exists():
... print('exists record with id equal 5')
...
>>>
# 创建测试用数据
>>> self.env['estate.property.tag'].create({'name': 'tag8', 'color': 8})
estate.property.tag(8,)
>>> self.env['estate.property.tag'].create({'name': 'tag9', 'color': 9})
estate.property.tag(9,)
>>> self.env['estate.property'].browse([2])
estate.property(2,)
# 替换关系字段关联的记录
# (6, 0, ids)
# 根据ids列表,替换所有已存在记录, 等价于使用命令(5, 0, 0),随后对ids中的每个id使用命令(4, id, 0)。
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag(6, 7)
>>> self.env['estate.property'].browse([2]).tag_ids = [(6, 0 , [8, 9])]
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag(8, 9)
>>>
# 通过mapped获取记录字段值(关联记录的属性值)列表
>>> self.env['estate.property'].browse([2]).tag_ids.mapped('name')
['tag8', 'tag9']
>>> self.env['estate.property'].browse([2]).mapped('tag_ids')
estate.property.tag(8, 9)
>>> self.env['estate.property'].browse([2]).mapped('tag_ids').mapped('id'))
[8, 9]
# search api 应用
# 搜索域
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)])
estate.property.tag(6, 7, 8, 9)
# 偏移
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1)
estate.property.tag(7, 8, 9)
# 限制返回记录集中的最大记录数
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2)
estate.property.tag(7, 8)
# 返回记录集中的记录排序
# 降序
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id desc')
estate.property.tag(8, 7)
# 升序
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id')
estate.property.tag(7, 8)
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id asc')
estate.property.tag(7, 8)
# 仅返回记录数
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], count=True)
4
# 利用search_count api实现等价效果
>>> self.env['estate.property.tag'].search_count(args=[('id', '>', 5)])
4
# 搜索域条件组合
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5),('color', '<', 8)])
estate.property.tag(6, 7)
# 获取记录(集)信息
# ids
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).ids
[6, 7, 8, 9]
# env
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).env
<odoo.api.Environment object at 0x0000020E31C80080>
# name_get api 使用
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).name_get()
[(6, 'tag6'), (7, 'tag7'), (8, 'tag8'), (9, 'tag9')]
# get_metadata api 使用
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).get_metadata()
[{'id': 6, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8,41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 7, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 8, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023,1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 9, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}]
# 利用 read_group 实现按组读取
>>> self.env['estate.property.tag'].create({'name': 'tag10', 'color': 9})
estate.property.tag(10,)
>>> self.env['estate.property.tag'].read_group([], fields=['color'], groupby=['color'])
[{'color_count': 1, 'color': 6, '__domain': [('color', '=', 6)]}, {'color_count': 1, 'color': 7, '__domain': [('color', '=', 7)]}, {'color_count': 1, 'color': 8, '__domain': [('color', '=', 8)]}, {'color_count': 2, 'color': 9, '__domain': [('color', '=', 9)]}]
# 获取字段定义
>>> self.env['estate.property.tag'].fields_get(['name'])
{'name': {'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': True, 'searchable': True, 'sortable': True
, 'store': True, 'string': 'tag', 'translate': False, 'trim': True}}
# 回滚
>>> self.env.cr.rollback()
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id')
estate.property.tag()
# 执行 sql
self.env.cr.execute('TRUNCATE TABLE estate_property_tag_test CASCADE;')
self.env.cr.commit()
# 重置自增主键ID 为1(每个表的主键ID存在名为 tableName_id_seq 的序列中)
self.env.cr.execute('ALTER SEQUENCE estate_property_tag_test_id_seq RESTART WITH 1;')
self.env.cr.commit()
>>> self.env['estate.property.tag'].create([{'name': 'tag1', 'color': 1}, {'name': 'tag2', 'color': 2}, {'name': 'tag3', 'color': 3}])
estate.property.tag(1, 2, 3)
# 批量更新记录字段值 #记录集存在多条记录的情况下,不能通过 records.fieldName = 目标值 实现批量更新
>>> self.env['estate.property.tag'].browse([1,3]).write({'color':1})
True
>>> self.env['estate.property.tag'].browse([1,3]).mapped('color')
[1, 1]
# 修改查询记录集context
>>> self.env['estate.property.tag'].browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels'}
>>> self.env['estate.property.tag'].with_context(is_sync=False).browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}
# with_context和sudo共存时的使用方式
>>> self.env['estate.property.tag'].with_context(is_sync=False).sudo().browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}
>>> self.env['estate.property.tag'].sudo().with_context(is_sync=False).browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}
# 修改创建记录时返回记录的context(更新记录(write)也是一样的用法)
# 如此,可以通过重写对应模型的create或者write方法,并在方法中通过self.env.context获取目标key值,进而执行需求实现需要采取的动作,参见下文
>>> self.env['estate.property.tag'].with_context(is_sync=False).create({'name': 'tag4', 'color': 4}).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}
# 删除记录
>>> self.env['estate.property.tag'].search([])
estate.property.tag(1, 2, 3, 4)
>>> self.env['estate.property.tag'].search([('id', '>', 2)]).unlink()
2023-01-29 09:55:47,796 15984 INFO odoo odoo.models.unlink: User #1 deleted estate.property.tag records with IDs: [3, 4]
True
# 遍历记录集
>>> for record_set in self. self.env['estate.property.tag.test'].search([]):
... print(record_set)
...
estate.property.tag.test(1,)
estate.property.tag.test(2,)
获取context上下文目标key值示例
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields,api
class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'estate property tag'
_order = 'name'
name = fields.Char(string='tag', required=True)
color = fields.Integer(string='Color')
@api.model
def create(self, vals_list):
res = super(EstatePropertyTag, self).create(vals_list)
# 获取上下文目标key值
if not self.env.context.get('is_sync', True):
# do something you need
return res
参考连接
https://www.odoo.com/documentation/14.0/zh_CN/developer/reference/addons/orm.html#
odoo ORM API学习总结兼orm学习教程的更多相关文章
- 【原创】Odoo开发文档学习之:ORM API接口(ORM API)(边Google翻译边学习)
官方ORM API开发文档:https://www.odoo.com/documentation/10.0/reference/orm.html Recordsets(记录集) New in vers ...
- Odoo : ORM API
记录集 model的数据是通过数据集合的形式来使用的,定义在model里的函数执行时它们的self变量也是一个数据集合 class AModel(models.Model): _name = 'a.m ...
- Python学习---django之ORM语法[对象关系映射]180124
ORM语法[对象关系映射] ORM: 用面向对象的方式去操作数据库的创建表以及增删改查等操作. 优点:1 ORM使得我们的通用数据库交互变得简单易行,而且完全不用考虑该死的SQL语句.快速开发. 2 ...
- 【Python学习之八】ORM
ORM 什么是ORM呢? ORM全称是:Object-Relational Mapping.即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表.这样,写代码更简单,不用直接 ...
- Django (学习第二部 ORM 模型层)
Django对数据库的操作 Django的 ORM 简介 ORM操作 (增删改查) ORM操作数据库的增删改查 ORM创建表关系 ORM中常用字段及参数 数据库的查询优化 ORM中如何开启事务 ORM ...
- ArcGIS API for JavaScript 4.2学习笔记[0] AJS4.2概述、新特性、未来产品线计划与AJS笔记目录
放着好好的成熟的AJS 3.19不学,为什么要去碰乳臭未干的AJS 4.2? 4.2全线基础学习请点击[直达] 4.3及更高版本的补充学习请关注我的博客. ArcGIS API for JavaScr ...
- Go:学习笔记兼吐槽(3)
Go:学习笔记兼吐槽(1) Go:学习笔记兼吐槽(2) Go:学习笔记兼吐槽(3) 数组 Golang 中,数组是值类型. 数组的声明 var arr [10]int 数组的初始化 var arr1 ...
- Go:学习笔记兼吐槽(2)
Go:学习笔记兼吐槽(1) Go:学习笔记兼吐槽(2) Go:学习笔记兼吐槽(3) 基本数据类型和string之间的转换 (1) 基本类型转string 使用 fmt.Sprintf(“%参数”, 表 ...
- Go:学习笔记兼吐槽(1)
Go:学习笔记兼吐槽(1) Go:学习笔记兼吐槽(2) Go:学习笔记兼吐槽(3) 自动添加分号 在很多其他的编程语言中,每一行代码的结尾都必须有分号(假设一行中只有一句代码),Golang 的开 ...
- web API .net - .net core 对比学习-使用Swagger
根据前两篇的介绍,我们知道.net web api 和 .net core web api在配置方面的不同如下: 1. .net web api的配置是在 App_Stat文件夹里面添加对应的配置类, ...
随机推荐
- python学习笔记-初始python(1)
1.运行程序 python 使用cmd.exe 运行程序. 例子: python +[文件路径] 2.注释 当行注释:# 被注释内容 多行注释:'''被注释内容''',或者""& ...
- Windows日常快捷键
Windows: 环境变量: Win+R,--> sysdm.cpl 计算器: Win+R,-->calc 服务:Win+R,-->services.msc 远程:Win+R,--& ...
- 建议收藏| 学python的看过来,Python 史上最全第三方库收集
发现一个宝藏网站: GitHub 上有一个 Awesome - XXX 系列的资源整理,这个系列以"全"闻名,但凡是有一定知识度的领域.语言.框架等,都有自己的 awesome-x ...
- 服务器性能测试工具ab
ab指令 ab -n 1000 -c 20 http://127.0.0.1/
- python3中的负数整除、求余问题
注:小白问题,大神们请忽略先看示例,非整除: >>> -10/3-3.3333333333333335>>> 10/-3-3.3333333333333335> ...
- dpkt 简单应用
import dpktfrom dpkt.utils import mac_to_str,inet_to_strcap=f'D:/test_pacp/6.pcap'with open(cap,'rb' ...
- 记一次因为关键字OUT 导致的后台"sql injection violation" 报错的问题
在navicat和mssm中执行用字段别名'out'均没有问题,但是在mybatis里使用就会报 "sql injection violation, syntax error: ERROR. ...
- CentOS7 64位 部署AVA项目:jar包方式
步骤:1.挂载磁盘2.安装jdk1.83.安装mysql5.74.导入数据库5.防火墙端口放行5.运行jar文件 1.挂载磁盘https://www.cnblogs.com/xiang96/p/102 ...
- gitbash 本地文件提交为一个新的项目 到 gitlab
此篇操作的环境: 已经配置好一个本地仓库,且可成功的将本地项目提交到gitlab上的对应的远程仓库. 这意味着此时你的电脑已经安装好git,有一个本地仓库存放你的项目,成功配置好一个对应的远程仓库,且 ...
- SpringMVC配置文件applicationContext.xml头信息
applicationContext.xml头信息 <?xml version="1.0" encoding="UTF-8"?> <beans ...