ORM API¶
- 对象关系映射模块:
分层结构
约束一致性和验证
对象元数据取决于其状态
通过复杂查询进行优化处理(一次执行多个操作)
默认字段值
权限优化
持久化对象:DB postgresql
数据转换
多级缓存系统
两种不同的继承机制
- 丰富的字段类型集合:
传统类型(varchar、integer、boolean等)
关系型(一对多,多对一,多对多)
功能性的
模型¶
模型字段定义为模型本身的属性:
from odoo import models, fields
class AModel(models.Model):
_name = 'a.model.name'
field1 = fields.Char()
警告
这意味着您不能定义一个与方法同名的字段,否则后者将会悄悄地覆盖前者。
默认情况下,字段的标签(用户可见名称)是字段名称的大写版本,可以使用 string
参数覆盖。
field2 = fields.Integer(string="Field Label")
对于字段类型和参数的列表,请参见 字段参考。
默认值是作为字段参数定义的,可以是一个值:
name = fields.Char(default="a value")
或者作为一个被调用以计算默认值的函数,该函数应该返回该值:
def _default_name(self):
return self.get_value()
name = fields.Char(default=lambda self: self._default_name())
API
- class odoo.models.BaseModel[源代码]¶
Odoo模型的基类。
Odoo模型是通过继承以下模型之一创建的:
Model
用于常规数据库持久化模型TransientModel
用于存储在数据库中的临时数据,但会定期自动清理AbstractModel
用于被多个继承模型共享的抽象超类
系统会自动为每个数据库实例化每个模型一次。这些实例代表每个数据库上可用的模型,并且取决于该数据库上安装的模块。每个实例的实际类是由创建和继承相应模型的Python类构建的。
每个模型实例都是一个“recordset”,即模型记录的有序集合。Recordsets由诸如
browse()
、search()
或字段访问等方法返回。记录没有显式的表示:一个记录被表示为一个记录集合的一个记录。要创建一个不应该被实例化的类,可以将
_register
属性设置为 False。- _auto = False¶
是否应该创建数据库表。如果设置为
False
,请重写init()
来创建数据库表。自动默认为
True
用于Model
和TransientModel
,为False
用于AbstractModel
。小技巧
要创建一个没有任何表的模型,请继承自
AbstractModel
。
- _sql_constraints = []¶
SQL 约束条件 [(名称,SQL定义,消息)]
- _register = False¶
注册表可见性
- _abstract = True¶
Whether the model is abstract.
另请参阅
- _transient = False¶
模型是否为 transient.
另请参阅
- _name = None¶
模型名称(点符号表示法,模块命名空间)
- _description = None¶
模型的非正式名称
- _inherit = ()¶
Python继承的模型:
- 类型
字符串或字符串列表
- _inherits = {}¶
字典{‘parent_model’: ‘m2o_field’}将父业务对象的_name映射到相应的外键字段名称以使用:
_inherits = { 'a.model': 'a_field_id', 'b.model': 'b_field_id' }
实现基于组合的继承:新模型公开继承模型的所有字段,但不存储它们:值本身仍存储在链接记录上。
警告
如果在
_inherits
-ed模型中定义了多个具有相同名称的字段,则继承的字段将对应于最后一个(按继承列表顺序)。
- _rec_name = None¶
用于标记记录的字段,默认为:
name
- _order = 'id'¶
搜索结果的默认排序字段
- _check_company_auto = False¶
在写入和创建时,调用
_check_company
来确保具有check_company=True
属性的关系字段的公司一致性。
- _parent_name = 'parent_id'¶
用作父级字段的many2one字段
- _parent_store = False¶
设置为True以计算parent_path字段。
在
parent_path
字段旁边,设置了记录的树形结构的索引存储,以便使用child_of
和parent_of
域操作符在当前模型的记录上进行更快的层次查询。
- _fold_name = 'fold'¶
用于确定看板视图中折叠组的字段
抽象模型¶
- odoo.models.AbstractModel[源代码]¶
alias of
odoo.models.BaseModel
模型¶
- class odoo.models.Model(env, ids, prefetch_ids)[源代码]¶
常规数据库持久化Odoo模型的主超类。
Odoo模型是通过继承此类创建的:
class user(Model): ...
系统稍后将在每个安装了该类模块的数据库中实例化该类。
- _auto = True¶
是否应该创建数据库表。如果设置为
False
,请重写init()
来创建数据库表。自动默认为
True
用于Model
和TransientModel
,为False
用于AbstractModel
。小技巧
要创建一个没有任何表的模型,请继承自
AbstractModel
。
- _abstract = False¶
Whether the model is abstract.
另请参阅
瞬态模型¶
- class odoo.models.TransientModel(env, ids, prefetch_ids)[源代码]¶
瞬态记录的模型超类,用于临时持久化,并定期进行清理。
瞬态模型具有简化的访问权限管理,所有用户都可以创建新记录,并且只能访问他们创建的记录。超级用户可以无限制地访问所有瞬态模型记录。
- _transient_max_count = False¶
最大瞬态记录数,如果为
0
则无限制
- _transient_max_hours = 1.0¶
最大空闲生命周期(以小时为单位),如果为
0
则无限制
- _transient_vacuum()[源代码]¶
清理瞬态记录。
每当达到
_transient_max_count
或_transient_max_hours
条件(如果有的话),它会从临时模型表中取消链接旧记录。实际清理只会在每5分钟内发生一次。这意味着可以频繁调用此方法(例如每次创建新记录时)。
同时启用max_hours和max_count的示例:
假设max_hours = 0.2(即12分钟),max_count = 20,表中有55行,其中有10行在最近5分钟内创建/更改,另外12行在5到10分钟之间创建/更改,其余的行创建/更改时间超过12分钟。
基于时间的清理将保留最近12分钟内创建/更改的22行记录
计数基础的清理将会清除另外12行。不仅仅是2行,否则每次添加都会立即导致最大值再次达到。
过去5分钟内创建/更改的10行记录将不会被删除
字段¶
- class odoo.fields.Field[源代码]¶
字段描述符包含字段定义,并管理记录上相应字段的访问和赋值。在实例化字段时,可以提供以下属性:
- 参数
string (str) – 用户可见的字段标签;如果未设置,则ORM将使用类中的字段名称(大写)。
help (str) – 用户可见的字段工具提示
invisible – 字段是否可见(布尔型,默认为
False
)readonly (bool) – 字段是否只读(默认:
False
) 这只对用户界面有影响。代码中的任何字段赋值都将起作用(如果字段是存储字段或可逆字段)。required (bool) – 字段的值是否必需(默认值:
False
)index (str) – 字段是否在数据库中建立索引,以及索引的类型。注意:这对于非存储和虚拟字段没有影响。可能的值有: *
"btree"
或True
:标准索引,适用于many2one *"btree_not_null"
:没有NULL值的BTREE索引(当大多数值为NULL或者从不搜索NULL时有用) *"trigram"
:带有trigrams的广义倒排索引(适用于全文搜索) *None
或False
:无索引(默认)default (value or callable) – 字段的默认值;可以是静态值,也可以是一个接收记录集并返回值的函数;使用
default=None
来丢弃字段的默认值。states (dict) – 将状态值映射到UI属性值对列表的字典;可能的属性有:
readonly
、required
、invisible
。 .. warning:: 任何基于状态的条件都需要在客户端UI上可用的state
字段值。通常通过将其包含在相关视图中来实现,如果对最终用户不相关,则可能将其设置为不可见。groups (str) – 逗号分隔的组xml id列表(字符串);这将限制字段访问仅限于给定组的用户
company_dependent (bool) – 是否依赖于当前公司的字段值;该值不存储在模型表中。它被注册为
ir.property
。当需要company_dependent字段的值时,会搜索与当前公司(和当前记录,如果存在)关联的ir.property
。如果在记录上更改了值,则会修改当前记录的现有属性(如果存在),或为当前公司和res_id创建一个新的属性。如果在公司端更改了值,则会影响所有未更改该值的记录。copy (bool) – 是否在复制记录时复制字段值(默认情况下,对于普通字段为
True
,对于one2many
和计算字段(包括属性字段和相关字段)为False
)store (bool) – 字段是否存储在数据库中(默认值:
True
,计算字段为``False``)group_operator (str) – 用于
read_group()
方法在对该字段进行分组时使用的聚合函数。支持的聚合函数有: *array_agg
:将值(包括空值)连接成一个数组 *count
:行数 *count_distinct
:不同行的数量 *bool_and
:如果所有值都为真,则为真;否则为假 *bool_or
:如果至少有一个值为真,则为真;否则为假 *max
:所有值的最大值 *min
:所有值的最小值 *avg
:所有值的平均值 *sum
:所有值的总和group_expand (str) – 用于在当前字段分组时扩展read_group结果的函数。.. code-block:: python @api.model def _read_group_selection_field(self, values, domain, order): return [‘choice1’, ‘choice2’, …] # 可用的选择项。 @api.model def _read_group_many2one_field(self, records, domain, order): return records + self.search([custom_domain])
计算字段
- 参数
compute (str) – 计算字段的方法的名称 .. seealso:: 高级 字段/计算字段
precompute (bool) – 是否在记录插入数据库之前计算字段。当字段可以在记录插入之前计算时,应该手动将某些字段指定为precompute=True。(例如,避免基于搜索/读取组的统计字段,many2one链接到上一个记录等)(默认值:
False
).. warning:: 仅当create()未提供显式值和默认值时,预计算才会发生。这意味着默认值会禁用预计算,即使字段被指定为precompute=True。如果给定模型的记录不是批量创建的,则预计算字段可能会适得其反。考虑一次创建多条记录的情况。如果字段未预计算,它通常会在flush()时批量计算,并且预取机制将有助于使计算效率高。另一方面,如果字段被预计算,计算将逐个进行,因此无法利用预取机制。根据上述备注,预计算字段可能在one2many的行上很有趣,这些行通常由ORM自身批量创建,前提是它们是通过写入包含它们的记录来创建的。compute_sudo (bool) – 字段是否应该作为超级用户重新计算以绕过访问权限(对于存储字段默认为
True
,对于非存储字段默认为False
)recursive (bool) – 字段是否具有递归依赖关系(字段
X
具有类似parent_id.X
的依赖关系);声明字段为递归必须是明确的,以确保重新计算的正确性inverse (str) – 反转字段的方法名称(可选)
search (str) – 实现字段搜索的方法名称(可选)
related (str) – 字段名称的顺序
default_export_compatible (bool) – 是否默认在兼容导入的导出中导出字段 .. seealso:: 高级字段/相关字段
基本字段¶
- class odoo.fields.Float[源代码]¶
封装了一个
float
。精度数字由(可选的)
digits
属性给出。当浮点数与计量单位相关联时,使用正确的工具以正确的精度比较或舍入值非常重要。
Float类提供了一些静态方法来实现此目的:
round()
to round a float with the given precision.is_zero()
to check if a float equals zero at the given precision.compare()
to compare two floats at the given precision.示例
按照计量单位的精度四舍五入数量:
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.Binary[源代码]¶
封装二进制内容(例如文件)。
- 参数
attachment (bool) – 字段是否应存储为
ir_attachment
或模型表的列(默认值:True
)。
- class odoo.fields.Html[源代码]¶
封装 HTML 代码内容。
- 参数
sanitize (bool) – 值是否需要进行清理(默认:
True
)sanitize_overridable (bool) – 是否允许属于
base.group_sanitize_override
组的用户绕过清洁处理(默认值:False
)sanitize_tags (bool) – 是否对标签进行清理(只接受属性的白名单,默认值:
True
)sanitize_attributes (bool) – 是否对属性进行清理(只接受属性的白名单,默认值:
True
)sanitize_style (bool) – 是否清理样式属性(默认:
False
)strip_style (bool) – 是否剥离样式属性(被移除,因此未经过清理,默认值:
False
)strip_classes (bool) – 是否剥离类属性(默认:
False
)
- class odoo.fields.Image[源代码]¶
封装一个图像,扩展
Binary
。如果图像大小超过像素的
max_width
/max_height
限制,图像将按照保持纵横比的方式调整大小到限制大小。- 参数
注解
如果未指定
max_width
/max_height``(或设置为0),并且 ``verify_resolution
为 False,则字段内容将不会被验证,应使用Binary
字段。
- class odoo.fields.Selection[源代码]¶
封装了不同值之间的互斥选择。
- 参数
selection (list(tuple(str,str)) or callable or str) – 指定此字段的可能值。它可以是一个由
(value, label)
对组成的列表,也可以是一个模型方法或方法名。selection_add (list(tuple(str,str))) – 提供了对覆盖字段的选择扩展。它是一个由
(value, label)
或单个值(value,)
组成的列表,其中单个值必须出现在覆盖的选择中。新值按照与覆盖选择一致的顺序插入到此列表中:: selection = [(‘a’, ‘A’), (‘b’, ‘B’)] selection_add = [(‘c’, ‘C’), (‘b’,)] > result = [(‘a’, ‘A’), (‘c’, ‘C’), (‘b’, ‘B’)]ondelete – 提供了一个回退机制,用于任何带有selection_add的覆盖字段。它是一个字典,将selection_add中的每个选项映射到一个回退操作。这个回退操作将应用于所有selection_add选项映射到它的记录。这些操作可以是以下任何一种: - ‘set null’ – 默认值,所有具有此选项的记录将其选择值设置为False。 - ‘cascade’ – 所有具有此选项的记录将与该选项本身一起被删除。 - ‘set default’ – 所有具有此选项的记录将被设置为字段定义的默认值 - ‘set VALUE’ – 所有具有此选项的记录将被设置为给定值 - <callable> – 一个可调用的函数,其第一个和唯一的参数将是包含指定Selection选项的记录集,用于自定义处理
属性
selection
是必需的,除非是related
或扩展字段的情况。
日期(时间)字段¶
Dates
和 Datetimes
是任何类型的商业应用程序中非常重要的字段。它们的错误使用可能会导致看不见但痛苦的错误,本节旨在为Odoo开发人员提供避免错误使用这些字段所需的知识。
当给日期/时间字段赋值时,以下选项是有效的:
一个
date
或datetime
对象。一个符合服务器格式的字符串:
False
或None
.
Date和Datetime字段类有助手方法,尝试转换为兼容类型:
to_date()
将转换为datetime.date
Example
解析来自外部来源的日期/日期时间::
fields.Date.to_date(self._context.get('date_from'))
日期/时间比较最佳实践:
日期字段 只能 与日期对象进行比较。
Datetime字段 只能 与datetime对象进行比较。
警告
表示日期和日期时间的字符串可以相互比较,但结果可能不是预期的结果,因为日期时间字符串始终大于日期字符串,因此 强烈 不建议这种做法。
日期和日期时间的常见操作,如加法、减法或获取一个周期的开始/结束,都可以通过 Date
和 Datetime
进行操作。这些辅助函数也可以通过导入 odoo.tools.date_utils
来使用。
注解
时区
日期时间字段在数据库中以 timestamp without timezone
列的形式存储,并以UTC时区存储。这是有意设计的,因为它使Odoo数据库独立于托管服务器系统的时区。时区转换完全由客户端管理。
- class odoo.fields.Date[源代码]¶
封装了一个python :class:`date <datetime.date>`对象。
- static add(value, *args, **kwargs)[源代码]¶
返回
value
和relativedelta
的和。- 参数
value – 初始日期或日期时间。
args – 传递给
relativedelta
的位置参数。kwargs – 传递给
relativedelta
的关键字参数。
- 返回
结果日期/日期时间。
- static context_today(record, timestamp=None)[源代码]¶
返回当前日期,以客户端时区为准,格式适合日期字段。
注解
此方法可用于计算默认值。
- 参数
record – 获取时区的记录集。
timestamp (datetime) – 可选的日期时间值,用于代替当前日期和时间(必须是日期时间,普通日期无法在时区之间转换)。
- 返回类型
date
- static end_of(value, granularity)[源代码]¶
从日期或日期时间获取时间段的结束时间。
- 参数
value – 初始日期或日期时间。
granularity – 时间段类型字符串,可以是年、季度、月、周、日或小时。
- 返回
指定时间段开始的日期/日期时间对象。
- static start_of(value, granularity)[源代码]¶
从日期或日期时间获取时间段的开始。
- 参数
value – 初始日期或日期时间。
granularity – 时间段的类型,可以是年、季度、月、周、日或小时。
- 返回
一个对应于指定时间段开始的日期/日期时间对象。
- static subtract(value, *args, **kwargs)[源代码]¶
返回
value
和relativedelta
之间的差异。- 参数
value – 初始日期或日期时间。
args – 传递给
relativedelta
的位置参数。kwargs – 传递给
relativedelta
的关键字参数。
- 返回
结果日期/日期时间。
- static to_date(value)[源代码]¶
尝试将
value
转换为date
对象。警告
如果传入的值是一个日期时间对象,它将被转换为日期对象,所有日期时间特定的信息将会丢失(如时分秒,时区等)。
- class odoo.fields.Datetime[源代码]¶
封装了一个python :class:`datetime <datetime.datetime>`对象。
- static add(value, *args, **kwargs)[源代码]¶
返回
value
和relativedelta
的和。- 参数
value – 初始日期或日期时间。
args – 传递给
relativedelta
的位置参数。kwargs – 传递给
relativedelta
的关键字参数。
- 返回
结果日期/日期时间。
- static context_timestamp(record, timestamp)[源代码]¶
返回给定的时间戳转换为客户端时区。
注解
此方法 不 适用于默认初始化程序,因为日期时间字段在客户端显示时会自动转换。对于默认值,应该使用
now()
代替。- 参数
record – 获取时区的记录集。
timestamp (datetime) – 将表示为UTC的本地日期时间值转换为客户端时区。
- 返回
将时间戳转换为上下文时区的带时区感知的日期时间。
- 返回类型
datetime
- static end_of(value, granularity)[源代码]¶
从日期或日期时间获取时间段的结束时间。
- 参数
value – 初始日期或日期时间。
granularity – 时间段类型字符串,可以是年、季度、月、周、日或小时。
- 返回
指定时间段开始的日期/日期时间对象。
- static start_of(value, granularity)[源代码]¶
从日期或日期时间获取时间段的开始。
- 参数
value – 初始日期或日期时间。
granularity – 时间段的类型,可以是年、季度、月、周、日或小时。
- 返回
一个对应于指定时间段开始的日期/日期时间对象。
- static subtract(value, *args, **kwargs)[源代码]¶
返回
value
和relativedelta
之间的差异。- 参数
value – 初始日期或日期时间。
args – 传递给
relativedelta
的位置参数。kwargs – 传递给
relativedelta
的关键字参数。
- 返回
结果日期/日期时间。
关系型字段¶
- class odoo.fields.Many2one[源代码]¶
这样的字段的值是一个大小为0(没有记录)或1(单个记录)的记录集。
- 参数
comodel_name (str) – 目标模型的名称
必填
,除了相关或扩展字段之外。domain – 在客户端上设置在候选值上的可选域(域或字符串)
context (dict) – 在处理该字段时,在客户端使用的可选上下文
ondelete (str) – 当引用的记录被删除时的操作;可能的值有:
'set null'
,'restrict'
,'cascade'
auto_join (bool) – 是否在通过该字段进行搜索时生成JOIN(默认值:
False
)delegate (bool) – 将其设置为
True
以使目标模型的字段可从当前模型访问(对应于_inherits
)check_company (bool) – 在
_check_company()
中标记要进行验证的字段。根据字段属性添加默认的公司域。
- class odoo.fields.One2many[源代码]¶
One2many字段;此类字段的值是
comodel_name
中所有记录的记录集,其中字段inverse_name
等于当前记录。- 参数
除了相关字段或字段扩展的情况外,
comodel_name
和inverse_name
属性是必需的。
- class odoo.fields.Many2many[源代码]¶
Many2many字段;此字段的值为记录集。
- 参数
属性
relation
、column1
和column2
是可选的。如果未提供,名称将从模型名称自动生成,前提是model_name
和comodel_name
不同!请注意,在给定模型上具有多个具有隐式关系参数的字段,且具有相同的关联模型是不被ORM接受的,因为这些字段将使用相同的表。ORM防止两个many2many字段使用相同的关系参数,除非
两个字段使用相同的模型、关联模型和关系参数是明确的;或者
至少有一个字段属于一个具有
_auto = False
的模型。
- 参数
domain – 在客户端上设置在候选值上的可选域(域或字符串)
context (dict) – 在处理该字段时,在客户端使用的可选上下文
check_company (bool) – 在
_check_company()
中标记要进行验证的字段。根据字段属性添加默认的公司域。
- class odoo.fields.Command[源代码]¶
One2many
和Many2many
字段 期望一个特殊的命令来操作它们所实现的关系。内部上,每个命令都是一个由3个元素组成的元组,其中第一个元素是一个必需的整数,用于标识命令,第二个元素要么是相关记录的id(用于update、delete、unlink和link命令),要么是0(用于create、clear和set命令),第三个元素要么是要写入记录的
values
(用于create和update命令),要么是新的相关记录的ids
列表(用于set命令),要么是0(用于delete、unlink、link和clear命令)。通过Python,我们鼓励开发人员通过此命名空间的各种函数来创建新命令。我们还鼓励开发人员在比较现有命令的第一个元素时使用命令标识符常量名称。
通过RPC,无法使用函数或命令常量名称。必须改为编写字面量3元组,其中第一个元素是命令的整数标识符。
- CREATE = 0¶
- UPDATE = 1¶
- DELETE = 2¶
- UNLINK = 3¶
- LINK = 4¶
- CLEAR = 5¶
- SET = 6¶
- classmethod create(values: dict)[源代码]¶
使用
values
在 comodel 中创建新记录,将创建的记录链接到self
。在
Many2many
关系中,会在 comodel 中创建一个唯一的新记录,使得self
中的所有记录都与新记录关联。在
One2many
关系中,为self
中的每个记录在 comodel 中创建一个新记录,以便self
中的每个记录都与新记录中的一个记录关联。返回命令三元组
(CREATE, 0, values)
- classmethod delete(id: int)[源代码]¶
从数据库中删除相关记录并删除与
self
的关联。在
Many2many
关系中,如果记录仍然与其他记录关联,从数据库中删除记录可能会被阻止。返回命令三元组
(DELETE, id, 0)
伪关系字段¶
计算字段¶
字段可以使用``compute``参数进行计算(而不是直接从数据库中读取)。它必须将计算后的值赋给字段。如果它使用其他*字段*的值,应该使用:func:`~odoo.api.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)]
搜索方法在执行实际搜索之前,在处理域时被调用。它必须返回一个等价于条件
field operator value
的域。
默认情况下,计算字段是只读的。要允许在计算字段上 设置 值,请使用
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
警告
虽然可以在多个字段中使用相同的计算方法,但不建议在反向方法中这样做。
在计算反向关系时, 所有 使用该反向关系的字段都受到保护,这意味着即使它们的值不在缓存中,也无法计算它们的值。
如果访问这些字段并且它们的值不在缓存中,ORM 将简单地为这些字段返回默认值 False
。这意味着除了触发反向方法的字段之外的反向字段的值可能不会给出它们的正确值,这可能会破坏反向方法的预期行为。
自动字段¶
访问日志字段¶
这些字段在启用 _log_access
时会自动设置和更新。可以禁用它以避免在不需要它们的表上创建或更新这些字段。
默认情况下, _log_access
的值与 _auto
相同
警告
_log_access
必须 在 TransientModel
上启用。
保留字段名称¶
一些字段名称被保留用于预定义的行为,超出了自动化字段的范畴。当需要相关的行为时,应在模型上定义它们:
- Model.active¶
切换记录的全局可见性,如果
active
设置为False
,则该记录在大多数搜索和列表中都是不可见的。特殊方法:
- Model.action_archive()[源代码]¶
将记录集的
active
设置为False
,通过调用 其当前活动记录的toggle_active()
方法。
- Model.action_unarchive()[源代码]¶
将记录集的
active
设置为True
,通过调用 其当前非活动记录的toggle_active()
方法。
- Model.parent_id¶
default_value of
_parent_name
, 用于组织树形结构中的记录,并在域中启用child_of
和parent_of
运算符。
- Model.parent_path¶
当
_parent_store
设置为 True 时,用于存储反映_parent_name
的树结构的值,并优化搜索域中的child_of
和parent_of
运算符。必须使用index=True
声明以确保正常运行。
记录集¶
与模型和记录的交互是通过记录集进行的,记录集是相同模型的记录的有序集合。
警告
与其名称所暗示的相反,目前记录集中可能包含重复项。这在未来可能会改变。
在模型上定义的方法在记录集上执行,它们的 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'))
字段值也可以像字典项一样访问,这比使用动态字段名的 getattr()
更加优雅和安全。设置字段的值会触发对数据库的更新:
>>> record.name
Example Name
>>> record.company_id.name
Company Name
>>> record.name = "Bob"
>>> field = "name"
>>> record[field]
Bob
警告
尝试读取多个记录上的字段将会对非关系型字段引发错误。
记录缓存和预取¶
Odoo 维护记录字段的缓存,以便不是每个字段访问都会发出数据库请求,这对性能来说是可怕的。以下示例仅查询第一个语句的数据库:
record.name # first access reads value from database
record.name # second access gets value from cache
为了避免逐个读取每个记录的每个字段,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)[源代码]¶
装饰一个方法,使其被每日的清理定时任务(模型
ir.autovacuum
)调用。这通常用于类似垃圾回收的任务,不需要特定的定时任务。
- 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
(value in context or current company id),uid
(current user id and superuser flag),active_test
(value in env.context or value in field.context).
- odoo.api.model(method)[源代码]¶
装饰一个记录式方法,其中
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'}, }
如果类型设置为通知,则警告将显示在通知中。否则,默认情况下将在对话框中显示。
警告
@onchange
仅支持简单字段名称,不支持点分字段名称(例如关联字段的字段partner_id.tz
),将被忽略危险
由于
@onchange
返回伪记录的记录集,对上述记录集调用任何一个CRUD方法 (create()
,read()
,write()
,unlink()
) 都是未定义的行为,因为它们在数据库中可能尚不存在。相反,只需像上面的示例中所示设置记录的字段或调用
update()
方法。警告
不可能通过 onchange 来修改
one2many
或many2many
字段本身。这是一个 web 客户端的限制 - 请参见 #2693。
- odoo.api.ondelete(*, at_uninstall)[源代码]¶
标记一个方法,在
unlink()
执行期间被调用。这个装饰器的目的是允许在取消关联记录时出现客户端错误,如果从业务角度来看,删除这些记录是没有意义的。例如,用户不应该能够删除已验证的销售订单。
虽然可以通过简单地覆盖模型上的
unlink
方法来实现,但它的缺点是与模块卸载不兼容。在卸载模块时,覆盖可能会引发用户错误,但我们不应该关心,因为模块正在被卸载,因此与模块相关的 所有 记录都应该被删除。这意味着通过覆盖
unlink
,有很大的机会导致一些表/记录作为未安装模块的剩余数据保留下来。这将使数据库处于不一致的状态。此外,如果该模块在该数据库上重新安装,存在冲突的风险。使用
@ondelete
修饰的方法应该在某些条件下引发错误,并且按照约定,该方法的名称应该是_unlink_if_<condition>
或_unlink_except_<not_condition>
。@api.ondelete(at_uninstall=False) def _unlink_if_user_inactive(self): if any(user.active for user in self): raise UserError("Can't delete an active user!") # same as above but with _unlink_except_* as method name @api.ondelete(at_uninstall=False) def _unlink_except_active_user(self): if any(user.active for user in self): raise UserError("Can't delete an active user!")
- 参数
at_uninstall (bool) – 装饰的方法是否在实现该方法的模块被卸载时调用。通常应为
False
,以避免模块卸载时触发这些错误。
危险
如果您实现的检查也适用于卸载模块,请将参数
at_uninstall
仅设置为True
。例如,卸载
sale
时,删除已验证的销售订单并不重要,因为与sale
相关的所有数据都应该被删除,在这种情况下,at_uninstall
应该设置为False
。然而,如果没有安装其他语言,防止删除默认语言是有意义的,因为删除默认语言将破坏很多基本行为。在这种情况下,
at_uninstall
应该设置为True
。
- odoo.api.returns(model, downgrade=None, upgrade=None)[源代码]¶
返回一个装饰器,用于返回
model
实例的方法。- 参数
model – 一个模型名称,或者
'self'
表示当前模型downgrade – 一个函数
downgrade(self, value, *args, **kwargs)
用于将 记录样式的value
转换为传统样式的输出upgrade – 一个函数
upgrade(self, value, *args, **kwargs)
用于将传统风格的value
转换为记录风格的输出
参数
self
、*args
和**kwargs
是以记录样式传递给方法的参数。该装饰器将方法的输出适应于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)
请注意,被装饰的方法必须符合这个约定。
这些装饰器会自动 继承 :覆盖已装饰的现有方法的方法将使用相同的
@returns(model)
装饰。
环境¶
- class odoo.api.Environment(cr, uid, context, su=False)[源代码]¶
环境存储ORM使用的各种上下文数据:
cr
: 当前数据库游标(用于数据库查询);uid
: 当前用户ID(用于访问权限检查);context
: 当前上下文字典(任意元数据);su
: 是否处于超级用户模式。
它通过实现从模型名称到模型的映射来提供对注册表的访问。它还持有记录的缓存和管理重新计算的数据结构。
>>> records.env
<Environment object ...>
>>> records.env.uid
3
>>> records.env.user
res.user(3)
>>> records.env.cr
<Cursor object ...>
在从另一个记录集创建记录集时,环境会被继承。环境可用于在另一个模型中获取空记录集,并查询该模型:
>>> 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.user¶
返回当前用户(作为实例)。
- 返回
当前用户 - 已使用sudo命令
- 返回类型
res.users 记录
- Environment.company¶
返回当前公司(作为实例)。
如果在上下文中未指定(
allowed_company_ids
),则回退到当前用户的主要公司。- 引发
AccessError – 无效或未授权的
allowed_company_ids
上下文键内容。- 返回
当前公司(默认=`self.user.company_id`),与当前环境一起
- 返回类型
res.company record
警告
在sudo模式下不适用任何健全性检查!在sudo模式下,用户可以访问任何公司,即使不在他允许的公司列表中。
这允许触发跨公司修改,即使当前用户没有访问目标公司的权限。
- Environment.companies¶
返回用户启用的公司的记录集。
如果在上下文中未指定(
allowed_company_ids
),则回退到当前用户的公司。- 引发
AccessError – 无效或未授权的
allowed_company_ids
上下文键内容。- 返回
当前公司(默认=`self.user.company_ids`),与当前环境
- 返回类型
res.company recordset
警告
在sudo模式下不适用任何健全性检查!在sudo模式下,用户可以访问任何公司,即使不在他允许的公司列表中。
这允许触发跨公司修改,即使当前用户没有访问目标公司的权限。
有用的环境方法¶
- Environment.ref(xml_id, raise_if_not_found=True)[源代码]¶
Return the record corresponding to the given
xml_id
.- 参数
- 返回
Found record or None
- 引发
ValueError – 如果记录未找到且
raise_if_not_found
为 True
修改环境¶
- Model.with_context([context][, **overrides]) Model [源代码]¶
返回一个新版本的记录集,附加到扩展上下文。
扩展上下文是指提供的
context
中合并overrides
或者 当前 上下文中合并overrides
,例如:# 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_company(company)[源代码]¶
返回一个带有修改上下文的新记录集版本,使其如下所示:
result.env.company = company result.env.companies = self.env.companies | company
- 参数
company (
res_company
or int) – 新环境的主公司。
警告
当当前用户使用未授权的公司时,在非sudo环境下访问环境中的公司可能会触发AccessError。
- Model.with_env(env)[源代码]¶
返回一个附加到提供环境的新版本记录集。
- 参数
env (
Environment
) –
注解
返回的记录集与
self
具有相同的预取对象。
SQL 执行¶
在环境中, cr
属性是当前数据库事务的游标,允许直接执行 SQL,无论是为了难以使用 ORM 表达的查询(例如复杂的连接)还是出于性能原因:
self.env.cr.execute("some_sql", params)
警告
执行原始 SQL 会绕过 ORM,从而绕过 Odoo 的安全规则。请确保在使用用户输入时对查询进行过滤,并且如果您不真正需要使用 SQL 查询,则优先使用 ORM 工具。
关于模型,需要知道的一件重要的事情是它们不一定立即执行数据库更新。为了性能原因,框架会延迟在修改记录后重新计算字段的操作。而且一些数据库更新也会被延迟。因此,在查询数据库之前,必须确保它包含了查询所需的相关数据。这个操作被称为 flushing ,它执行了预期的数据库更新。
Example
# make sure that 'partner_id' is up-to-date in database
self.env['model'].flush_model(['partner_id'])
self.env.cr.execute("SELECT id FROM model WHERE partner_id IN %s", [ids])
ids = [row[0] for row in self.env.cr.fetchall()]
在执行每个 SQL 查询之前,必须刷新该查询所需的数据。刷新有三个级别,每个级别都有自己的 API。可以刷新所有内容、模型的所有记录或某些特定记录。由于延迟更新通常可以提高性能,因此建议在刷新时要 具体 。
- Model.flush_model(fnames=None)[源代码]¶
处理
self
模型上的待处理计算和数据库更新。 当给定参数时,该方法保证至少将给定字段刷新到数据库。 虽然可以刷新更多字段。- 参数
fnames – optional iterable of field names to flush
- Model.flush_recordset(fnames=None)[源代码]¶
处理记录
self
上的待处理计算和数据库更新。当给定参数时,该方法保证至少将记录self
上的给定字段刷新到数据库。但是,可以刷新更多的字段和记录。- 参数
fnames – optional iterable of field names to flush
因为模型使用相同的游标和 Environment
保存了各种缓存,所以在使用原始 SQL 修改数据库时,必须使这些缓存失效,否则模型的进一步使用可能会变得不一致。在使用 SQL 的 CREATE
、UPDATE
或 DELETE
时,需要清除缓存,但不需要在使用 SELECT
时(它只是读取数据库)。
Example
# make sure 'state' is up-to-date in database
self.env['model'].flush_model(['state'])
self.env.cr.execute("UPDATE model SET state=%s WHERE state=%s", ['new', 'old'])
# invalidate 'state' from the cache
self.env['model'].invalidate_model(['state'])
与刷新类似,可以使整个缓存失效,使模型的所有记录的缓存失效,或使特定记录的缓存失效。甚至可以使某些记录的特定字段或模型的所有记录的特定字段失效。由于缓存通常可以提高性能,我们建议在使缓存失效时要 具体 。
- Environment.invalidate_all(flush=True)[源代码]¶
使所有记录的缓存失效。
- 参数
flush – 是否在失效之前刷新挂起的更新。默认为
True
,可确保缓存一致性。除非您知道自己在做什么,否则不要使用此参数。
- Model.invalidate_model(fnames=None, flush=True)[源代码]¶
使
self
的模型的所有记录的缓存失效,当缓存的值不再与数据库的值相对应时。如果给定了参数,则只有给定的字段会从缓存中失效。- 参数
fnames – 要使无效的字段名称的可选可迭代对象
flush – 是否在失效之前刷新挂起的更新。默认为
True
,可确保缓存一致性。除非您知道自己在做什么,否则不要使用此参数。
- Model.invalidate_recordset(fnames=None, flush=True)[源代码]¶
使
self
中的记录缓存失效,当缓存的值不再与数据库的值相对应时。如果给定了参数,则只有self
上的给定字段会从缓存中失效。- 参数
fnames – 要使无效的字段名称的可选可迭代对象
flush – 是否在失效之前刷新挂起的更新。默认为
True
,可确保缓存一致性。除非您知道自己在做什么,否则不要使用此参数。
上述方法可以使缓存和数据库保持一致。但是,如果计算字段的依赖关系在数据库中被修改,就必须通知模型重新计算计算字段。框架需要知道的唯一信息是 哪些 记录上的 哪些 字段已经发生了变化。
Example
# make sure 'state' is up-to-date in database
self.env['model'].flush_model(['state'])
# use the RETURNING clause to retrieve which rows have changed
self.env.cr.execute("UPDATE model SET state=%s WHERE state=%s RETURNING id", ['new', 'old'])
ids = [row[0] for row in self.env.cr.fetchall()]
# invalidate the cache, and notify the update to the framework
records = self.env['model'].browse(ids)
records.invalidate_recordset(['state'])
records.modified(['state'])
需要找出哪些记录已被修改。有许多方法可以做到这一点,可能涉及额外的 SQL 查询。在上面的示例中,我们利用了 PostgreSQL 的 RETURNING
子句,在不进行额外查询的情况下检索信息。在通过失效使缓存一致之后,使用已更新的字段调用已修改记录上的 modified
方法。
常见 ORM 方法¶
创建/更新¶
- Model.create(vals_list) records [源代码]¶
为模型创建新记录。
新记录使用字典列表
vals_list
中的值进行初始化,如果需要还可以使用default_get()
中的值。- 参数
vals_list (Union[list[dict], dict]) – 模型字段的值,作为字典列表:: [{‘field_name’: field_value, …}, …] 为了向后兼容,
vals_list
可以是一个字典。它被视为一个单例列表[vals]
,并返回一个单个记录。 详见write()
- 返回
已创建的记录
- 引发
AccessError – 如果当前用户没有权限创建指定模型的记录
ValidationError – 如果用户尝试为选择字段输入无效值
ValueError – 如果在创建数值中指定的字段名称不存在。
UserError – 如果在对象层次结构中创建了循环,操作的结果(例如将对象设置为其自身的父级)
- Model.copy(default=None)[源代码]¶
复制记录
self
并使用默认值更新它- 参数
default (dict) – 在复制记录的原始值中覆盖字段值的字典,例如:
{'field_name': overridden_value, ...}
- 返回
新记录
- Model.default_get(fields_list) default_values [源代码]¶
返回
fields_list
中字段的默认值。默认值由上下文、用户默认值和模型本身决定。- 参数
fields_list (list) – names of field whose default is requested
- 返回
如果字段有默认值,则将字段名称映射到相应的默认值的字典。
- 返回类型
注解
未请求的默认值不会被考虑,在
fields_list
中没有字段名的字段不需要返回值。
- Model.name_create(name) record [源代码]¶
通过调用
create()
方法并提供一个值(即新记录的显示名称)来创建新记录。新记录将使用适用于此模型的任何默认值进行初始化,或通过上下文提供。适用于
create()
的通常行为。- 参数
name – 创建记录的显示名称
- 返回类型
- 返回
创建记录的
name_get()
对值
- Model.write(vals)[源代码]¶
使用提供的值更新
self
中的所有记录。- 参数
vals (dict) – 需要更新的字段及其对应的值
- 引发
AccessError – 如果用户没有权限修改指定的记录/字段
ValidationError – 如果选择字段指定了无效值
UserError – 如果在对象层次结构中创建了循环,操作的结果(例如将对象设置为其自身的父级)
对于
Many2one
,值应该是要设置的记录的数据库标识符预期的值是一个
One2many
或Many2many
关系字段的列表,它操作实现的关系。总共有 7 个命令:create()
、update()
、delete()
、unlink()
、link()
、clear()
和set()
。对于
Date
和~odoo.fields.Datetime
,值应该是一个日期(时间)或字符串。警告
如果为日期(时间)字段提供了字符串,则必须仅使用UTC,并按照
odoo.tools.misc.DEFAULT_SERVER_DATE_FORMAT
和odoo.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
进行格式化其他非关系型字段使用字符串作为值
搜索/读取¶
- Model.browse([ids]) records [源代码]¶
在当前环境中,返回提供的参数所对应的记录集。
self.browse([7, 18, 12]) res.partner(7, 18, 12)
- Model.search(domain[, offset=0][, limit=None][, order=None][, count=False])[源代码]¶
基于
domain
search domain 进行记录搜索。- 参数
domain – A search domain. Use an empty list to match all records.
offset (int) – 忽略的结果数量(默认值:无)
limit (int) – 返回的最大记录数(默认:全部)
order (str) – 排序字符串
count (bool) – 如果为True,则仅计算并返回匹配记录的数量(默认值:False)
- 返回
最多返回符合搜索条件的
limit
条记录- 引发
AccessError – 如果用户无权访问请求的信息
- Model.search_count(domain) int [源代码]¶
返回当前模型中与 提供的领域 匹配的记录数。
- 参数
domain – A search domain. Use an empty list to match all records.
limit – 最大记录数(上限)(默认:全部)
- Model.name_search(name='', args=None, operator='ilike', limit=100) records [源代码]¶
搜索与给定的
name
模式匹配的显示名称的记录,与给定的operator
进行比较,同时也匹配可选的搜索域(args
)。这用于例如基于关系字段的部分值提供建议。通常应该与
name_get()
的反向行为相同,但这不是保证。此方法等同于使用基于
display_name
的搜索域调用search()
方法,然后在搜索结果上调用name_get()
方法。
- Model.read([fields])[源代码]¶
读取
self
中记录的请求字段,低级/RPC 方法。- 参数
- 返回
一个字典列表,将字段名映射到它们的值,每个记录一个字典
- 返回类型
- 引发
AccessError – 如果用户无权访问请求的信息
ValueError – 如果请求的字段不存在
- Model.read_group(domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True)[源代码]¶
获取按给定的
groupby
字段分组的列表视图中的记录列表。- 参数
domain (list) – A search domain. Use an empty list to match all records.
fields (list) – 在对象上指定的列表视图中存在的字段列表。每个元素可以是 ‘field’(字段名称,使用默认聚合),或者 ‘field:agg’(使用聚合函数 ‘agg’ 的聚合字段),或者 ‘name:agg(field)’(使用 ‘agg’ 聚合字段并将其作为 ‘name’ 返回)。可能的聚合函数是由 PostgreSQL 提供的函数和 ‘count_distinct’,具有预期的含义。
groupby (list) – 记录将按照分组描述进行分组。分组描述可以是一个字段(那么它将按照该字段进行分组),也可以是一个字符串“field:granularity”。目前,唯一支持的粒度是“day”、“week”、“month”、“quarter”或“year”,并且它们只对日期/时间字段有意义。
offset (int) – 可选的跳过记录数
limit (int) – 可选的最大返回记录数
orderby (str) – 可选的
order by
规范,用于覆盖组的自然排序顺序,另请参阅search()
(目前仅支持 many2one 字段)lazy (bool) – 如果为真,则结果仅按第一个groupby分组,其余的groupby放在__context键中。如果为假,则所有groupby在一次调用中完成。
- 返回
包含字典列表(每个记录一个字典),其中包含: * 按
groupby
参数分组的字段值 * __domain:指定搜索条件的元组列表 * __context:带有groupby
参数的字典 * __range:(仅限日期/时间)字典,以field_name:granularity为键,映射到具有键的字典:”from”(包含)和”to”(不包含),映射到组的时间边界的字符串表示- 返回类型
[{‘field_name_1’: value, …}, …]
- 引发
AccessError – 如果用户无权访问请求的信息
字段¶
搜索域¶
域是一个条件列表,每个条件都是一个三元组(可以是 list
或 tuple
),其中: (field_name, operator, value)
field_name
(str
)当前模型的字段名称,或通过点符号导航到
Many2one
的关系遍历,例如'street'
或'partner_id.country'
operator
(str
)用于将
field_name
与value
进行比较的运算符。有效的运算符有:=
等于
!=
不等于
>
大于
>=
大于或等于
<
小于
<=
小于或等于
=?
未设置或等于(如果
value
为None
或False
,则返回true,否则的话就像=
一样)=like
使用
value
模式匹配field_name
。模式中的下划线_
代表(匹配)任意单个字符;百分号%
匹配零个或多个字符的任意字符串。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
记录的子级(后代)(value 可以是一个项目或项目列表)。考虑模型的语义(即遵循由
_parent_name
指定的关系字段)。parent_of
是
value
记录的父级(祖先)(value 可以是一个项目或项目列表)。考虑模型的语义(即遵循由
_parent_name
指定的关系字段)。
value
变量类型,必须与命名字段通过
operator
进行比较。
域准则可以使用 前缀 形式的逻辑运算符进行组合:
'&'
逻辑 AND,默认操作是将相继的条件组合在一起。Arity 2(使用下2个条件或组合)。
'|'
逻辑 OR,arity 2.
'!'
逻辑 NOT,arity 1.
注解
主要用于否定条件的组合。个别条件通常有一个否定形式(例如
=
->!=
,<
->>=
),这比否定肯定形式更简单。
Example
要搜索名为 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)
取消链接¶
- Model.unlink()[源代码]¶
删除
self
中的记录。- 引发
AccessError – 如果用户没有权限删除所有给定的记录
UserError – 如果该记录是其他记录的默认属性
记录(集合)信息¶
- Model.ids¶
返回与
self
对应的实际记录 id 列表。
- odoo.models.env¶
返回给定记录集的环境。
- 类型
- Model.name_get()[源代码]¶
返回
self
中记录的文本表示,每个输入记录输出一个项目,顺序相同。警告
虽然
name_get()
可以使用上下文数据进行更丰富的上下文格式化,但由于它是display_name
的默认实现,如果上下文键为空/缺失,重置为“默认”行为非常重要。
- Model.get_metadata()[源代码]¶
返回有关给定记录的一些元数据。
- 返回
每个请求记录的所有权字典列表
- 返回类型
包含以下键的字典列表: * id:对象ID * create_uid:创建记录的用户 * create_date:记录创建日期 * write_uid:最后更改记录的用户 * write_date:记录的最后更改日期 * xmlid:用于引用此记录的XML ID(如果有),格式为
module.name
* xmlids:包含xmlid的字典列表,格式为module.name
,并且noupdate为布尔值 * noupdate:一个布尔值,指示记录是否将被更新
操作¶
记录集是不可变的,但是可以使用各种集合操作来组合同一模型的集合,返回新的记录集。
record in set
返回record``(必须是一个包含一个元素的记录集)是否存在于 ``set
中。record not in set
是其相反操作set1 <= set2
和set1 < set2
返回set1
是否是set2
的子集 (分别是子集和严格子集)set1 >= set2
和set1 > set2
返回set1
是否是set2
的超集 (分别是严格超集)set1 | set2
returns the union of the two recordsets, a new recordset containing all records present in either sourceset1 & set2
returns the intersection of two recordsets, a new recordset containing only records present in both sourcesset1 - set2
返回一个新的记录集,其中只包含set1
中的记录,这些记录在set2
中*不存在*。
Recordsets 是可迭代的,因此通常的 Python 工具可用于转换 (map()
, sorted()
, ifilter()
, …) 但是这些返回的是 list
或者 iterator,这样就无法对其结果调用方法或使用集合操作。
因此,记录集提供以下操作,返回记录集本身(如果可能):
筛选¶
- Model.filtered(func)[源代码]¶
返回满足
func
的self
中的记录。- 参数
func (callable or str) – 一个函数或由字段名用点号分隔的序列
- 返回
符合条件的记录集,可能为空。
# 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)[源代码]¶
返回满足筛选条件并保持原有顺序的
self
记录。- 参数
domain – A search domain.
地图¶
- Model.mapped(func)[源代码]¶
将
func
应用于self
中的所有记录,并将结果作为列表或记录集返回(如果func
返回记录集)。在后一种情况下,返回的记录集的顺序是任意的。- 参数
func (callable or str) – 一个函数或由字段名用点号分隔的序列
- 返回
如果 func 为假,则返回 self;否则返回应用于所有
self
记录的 func 的结果。- 返回类型
list or 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版本开始,支持多关系字段访问,并且与映射调用类似:
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')
排序¶
继承和扩展¶
Odoo 提供了三种不同的机制以模块化的方式扩展模型:
从现有模型创建一个新模型,向副本添加新信息,但保留原始模块不变
在原地扩展在其他模块中定义的模型,替换先前的版本
将模型的一些字段委托给其包含的记录
经典继承¶
当同时使用 _inherit
和 _name
属性时,Odoo 会基于已有的模型 (通过 _inherit
提供)创建一个新的模型。新模型将继承 基础模型的所有字段、方法和元信息(默认值和其他)。
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()
将产生:
“这是模型0记录A” “这是模型1记录B”
第二个模型继承了第一个模型的 check
方法和 name
字段,但是重写了 call
方法,就像使用标准的 Python 继承 一样。
扩展¶
当使用 _inherit
但是省略了 _name
时,新模型将替换现有模型,本质上是在原地扩展它。这对于向现有模型(在其他模块中创建的)添加新字段或方法非常有用,或者用于自定义或重新配置它们(例如更改它们的默认排序顺序):
class Extension0(models.Model):
_name = 'extension.0'
_description = 'Extension zero'
name = fields.Char(default="A")
class Extension1(models.Model):
_inherit = 'extension.0'
description = fields.Char(default="Extended")
record = env['extension.0'].create({})
record.read()[0]
将产生:
{'name': "A", 'description': "Extended"}
注解
它还将产生各种 自动字段,除非它们已被禁用
委托¶
第三种继承机制提供了更大的灵活性(可以在运行时进行修改),但功能较弱:使用 _inherits
属性,一个模型将 委托 查找当前模型上未找到的任何字段 给”子”模型。委托是通过在父模型上自动设置的 Reference
字段来执行的。
主要区别在于含义。使用委托时,模型 有一个 而不是 是一个 ,将关系转变为组合而不是继承:
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
is more or less implemented, avoid it if you can;链式
_inherits
实际上未被实现,我们无法保证最终行为。
字段增量定义¶
字段是在模型类上定义的类属性。如果模型被扩展,也可以通过在子类上重新定义具有相同名称和相同类型的字段来扩展字段定义。在这种情况下,字段的属性来自父类,并被子类中给定的属性覆盖。
例如,下面的第二个类只在字段 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")
错误管理¶
Odoo 异常模块定义了几个核心异常类型。
这些类型被RPC层理解。任何其他异常类型在传递到RPC层之前都将被视为“服务器错误”。
注解
如果您考虑引入新的异常,请查看 odoo.addons.test_exceptions
模块。
- exception odoo.exceptions.AccessDenied(message='Access Denied')[源代码]¶
登录/密码错误。
注解
没有追踪信息。
示例
当您尝试使用错误的密码登录时。
- exception odoo.exceptions.RedirectWarning(message, action, button_text, additional_context=None)[源代码]¶
警告,可以重定向用户而不仅仅是显示警告消息。