ORM API¶
- 对象关系映射模块:
分层结构
约束一致性与验证
对象元数据依赖于其状态
通过复杂查询优化处理(一次执行多个操作)
字段默认值
权限优化
持久化对象:PostgreSQL 数据库
数据转换
多级缓存系统
两种不同的继承机制
- 丰富的字段类型:
经典类型(varchar、integer、boolean 等)
关系类型(one2many、many2one、many2many)
功能性
模型¶
模型字段定义为模型本身的属性::
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 类构建的。
每个模型实例都是一个“记录集”,即模型记录的有序集合。记录集由诸如
browse()
、search()
或字段访问等方法返回。记录没有显式表示:一条记录被表示为包含单条记录的记录集。要创建不应被实例化的类,可以将
_register
属性设置为 False。- _auto = False¶
是否应创建数据库表。如果设置为
False
,则需要重写init()
方法以创建数据库表。对于
Model
和TransientModel
,默认值为True
;对于AbstractModel
,默认值为False
。小技巧
要创建不带任何表的模型,请继承
AbstractModel
。
- _register = False¶
注册表可见性
- _abstract = True¶
模型是否为 抽象。
- _transient = False¶
模型是否为 临时。
- _inherits = {}¶
字典 { ‘父模型’ : ‘多对一字段’ },将父业务对象的 _name 映射到相应的外键字段名称以供使用:
_inherits = { 'a.model': 'a_field_id', 'b.model': 'b_field_id' }
实现基于组合的继承:新模型暴露所有继承模型的字段,但不会存储其中任何一个:这些值本身仍然存储在关联的记录中。
警告
如果在
_inherits
的模型中定义了多个同名字段,则继承的字段将对应于最后一个(按 inherits 列表顺序)。
- _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: api.Environment, ids: tuple[IdType, ...], prefetch_ids: Reversible[IdType])[源代码]¶
常规数据库持久化 Odoo 模型的主要超类。
Odoo 模型通过继承此类创建:
class user(Model): ...
系统稍后会在每个数据库(安装了该类模块的数据库)上实例化一次该类。
- _auto = True¶
是否应创建数据库表。如果设置为
False
,则需要重写init()
方法以创建数据库表。对于
Model
和TransientModel
,默认值为True
;对于AbstractModel
,默认值为False
。小技巧
要创建不带任何表的模型,请继承
AbstractModel
。
- _abstract = False¶
模型是否为 抽象。
临时模型¶
- class odoo.models.TransientModel(env: api.Environment, ids: tuple[IdType, ...], prefetch_ids: Reversible[IdType])[源代码]¶
临时记录的模型超类,旨在暂时持久化,并定期清理。
TransientModel 的访问权限管理较为简化,所有用户都可以创建新记录,但只能访问自己创建的记录。超级用户对所有 TransientModel 记录拥有无限制的访问权限。
- _transient_max_count = 0¶
临时记录的最大数量,如果为
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) – 用户看到的字段提示信息
readonly (bool) – 字段是否为只读(默认值:
False
)。这只会影响 UI。代码中的任何字段赋值都有效(如果字段是存储字段或可逆字段)。required (bool) – 字段值是否为必填项(默认值:
False
)index (str) – 字段是否在数据库中建立索引,以及索引的类型。注意:这对非存储字段和虚拟字段无效。可能的值包括:*
"btree"
或True
:标准索引,适用于 many2one 字段 *"btree_not_null"
:不包含 NULL 值的 BTREE 索引(当大多数值为 NULL 或从不搜索 NULL 时有用) *"trigram"
:使用三元组的广义倒排索引(GIN,适用于全文搜索) *None
或False
:无索引(默认值)default (value or callable) – 字段的默认值;可以是一个静态值,也可以是一个接受记录集并返回值的函数;使用
default=None
来丢弃字段的默认值groups (str) – 逗号分隔的组 XML ID 列表(字符串);此设置将字段访问限制为仅限指定组的用户
company_dependent (bool) – 字段值是否依赖于当前公司;值以 JSONB 字典的形式存储在模型表中,键为公司 ID。模型 ir.default 中存储的字段默认值将用作 JSONB 字典中未指定值的回退值。
copy (bool) – 记录复制时是否应复制字段值(默认值:普通字段为
True
,one2many
和计算字段(包括属性字段和关联字段)为False
)store (bool) – 字段是否存储在数据库中(默认值:
True
,计算字段为False
)aggregator (str) –
read_group()
在对字段进行分组时使用的聚合函数。支持的聚合函数包括:*array_agg
:所有值(包括 NULL)拼接成数组 *count
:行数 *count_distinct
:不同行数 *bool_and
:如果所有值均为真则为真,否则为假 *bool_or
:如果至少一个值为真则为真,否则为假 *max
:所有值的最大值 *min
:所有值的最小值 *avg
:所有值的平均值(算术平均数) *sum
:所有值的总和group_expand (str) – 当在当前字段上进行分组时,用于扩展 read_group 结果的函数。对于选择字段,
group_expand=True
会自动为所有选择键扩展分组。 .. code-block:: python @api.model def _read_group_selection_field(self, values, domain): return [‘choice1’, ‘choice2’, …] # 可用的选择项。 @api.model def _read_group_many2one_field(self, records, domain): return records + self.search([custom_domain])
计算字段
- 参数
precompute (bool) –
是否应在记录插入数据库之前计算字段。当字段可以在记录插入前计算时,应手动将某些字段指定为 precompute=True。(例如,避免基于 search/read_group 的统计字段),many2one 链接到上一条记录等(默认值:
False
)。警告
仅当未提供显式值且未提供默认值时,才会发生预计算。这意味着即使字段被指定为 precompute=True,默认值也会禁用预计算。
如果给定模型的记录不是批量创建的,那么预计算字段可能会适得其反。考虑一下逐条创建多条记录的情况。如果字段未预计算,它通常会在 flush() 时批量计算,并且预取机制将有助于提高计算效率。另一方面,如果字段是预计算的,则计算将逐条进行,因此无法利用预取机制的优势。
根据上述说明,预计算字段在一2多关系的行上可能是有意义的,这些行通常由 ORM 本身批量创建,前提是它们是通过写入包含它们的记录来创建的。
compute_sudo (bool) – 字段是否应以超级用户身份重新计算以绕过访问权限(默认情况下,存储字段为
True
,非存储字段为False
)recursive (bool) – 字段是否有递归依赖关系(字段
X
具有类似parent_id.X
的依赖关系);声明字段的递归性必须明确,以确保重新计算的正确性inverse (str) – 用于反向字段的方法名称(可选)
search (str) – 实现字段搜索的方法名称(可选)
related (str) – 字段名称序列
default_export_compatible (bool) – 字段是否必须在兼容导入的导出中默认导出 .. 参见 高级字段/相关字段
基本字段¶
- class odoo.fields.Float[源代码]¶
封装了一个
浮点数
。精度位数由(optional)``digits`` 属性指定。
当浮点数是与计量单位相关联的量时,重要的是使用正确的工具以适当的精度比较或舍入值。
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)
出于历史原因,compare 辅助函数使用 __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
)sanitize_conditional_comments (bool) – 是否移除条件注释(默认值:
True
)sanitize_output_method (bool) – 是否使用 HTML 或 XHTML 进行清理(默认值:
html
)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’,)],结果为 [(‘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
或扩展字段的情况。
日期(时间)字段¶
日期
和 日期时间
是任何业务应用程序中非常重要的字段。它们的误用可能会导致隐性但令人头痛的错误,本节旨在为 Odoo 开发人员提供避免误用这些字段所需的知识。
为日期/日期时间字段赋值时,以下选项是有效的:
date
或datetime
对象。符合服务器格式的字符串:
False
或None
。
日期和日期时间字段类提供了辅助方法,以尝试转换为兼容类型:
to_date()
将转换为datetime.date
to_datetime()
将转换为datetime.datetime
。
Example
解析来自外部源的日期/时间::
fields.Date.to_date(self._context.get('date_from'))
日期/日期时间比较的最佳实践:
日期字段只能与日期对象进行比较。
日期时间字段只能与日期时间对象进行比较。
警告
表示日期和日期时间的字符串可以在彼此之间进行比较,但结果可能不是预期的结果,因为日期时间字符串始终大于日期字符串,因此强烈不建议这样做。
对日期和日期时间的常见操作(如加法、减法或获取时间段的起点/终点)通过 Date
和 Datetime
提供。这些辅助工具也可以通过导入 odoo.tools.date_utils
来使用。
注解
时区
日期时间字段在数据库中存储为 无时区的时间戳
列,并以 UTC 时区存储。这是设计上的选择,因为它使 Odoo 数据库独立于托管服务器系统的时区。时区转换完全由客户端管理。
- class odoo.fields.Date[源代码]¶
封装了一个 Python
date
对象。- static start_of(value: odoo.tools.date_utils.D, granularity: Literal['year', 'quarter', 'month', 'week', 'day', 'hour']) odoo.tools.date_utils.D [源代码]¶
从日期或时间获取时间段的起始时间。
- 参数
value – 初始日期或时间。
granularity – 时间段类型(字符串),可以是年、季度、月、周、日或小时。
- 返回
对应于指定时间段起始时间的日期/时间对象。
- static end_of(value: odoo.tools.date_utils.D, granularity: Literal['year', 'quarter', 'month', 'week', 'day', 'hour']) odoo.tools.date_utils.D [源代码]¶
从日期或时间获取时间段的结束时间。
- 参数
value – 初始日期或时间。
granularity – 时间段类型(字符串),可以是年、季度、月、周、日或小时。
- 返回
对应于指定时间段起始时间的日期/时间对象。
- static add(value: odoo.tools.date_utils.D, *args, **kwargs) odoo.tools.date_utils.D [源代码]¶
返回
value
和relativedelta
的和。- 参数
value – 初始日期或时间。
args – 直接传递给
relativedelta
的位置参数。kwargs – 直接传递给
relativedelta
的关键字参数。
- 返回
生成的日期/时间。
- static subtract(value: odoo.tools.date_utils.D, *args, **kwargs) odoo.tools.date_utils.D [源代码]¶
返回
value
与relativedelta
之间的差值。- 参数
value – 初始日期或时间。
args – 直接传递给
relativedelta
的位置参数。kwargs – 直接传递给
relativedelta
的关键字参数。
- 返回
生成的日期/时间。
- static context_today(record, timestamp=None)[源代码]¶
以适合日期字段的格式返回客户端时区中的当前日期。
注解
此方法可用于计算默认值。
- 参数
record – 用于获取时区的记录集。
timestamp (datetime) – 可选的 datetime 值,用于替代当前日期和时间(必须是 datetime 类型,普通日期无法在时区之间转换)。
- 返回类型
date
- class odoo.fields.Datetime[源代码]¶
封装一个 Python
datetime
对象。- static start_of(value: odoo.tools.date_utils.D, granularity: Literal['year', 'quarter', 'month', 'week', 'day', 'hour']) odoo.tools.date_utils.D [源代码]¶
从日期或时间获取时间段的起始时间。
- 参数
value – 初始日期或时间。
granularity – 时间段类型(字符串),可以是年、季度、月、周、日或小时。
- 返回
对应于指定时间段起始时间的日期/时间对象。
- static end_of(value: odoo.tools.date_utils.D, granularity: Literal['year', 'quarter', 'month', 'week', 'day', 'hour']) odoo.tools.date_utils.D [源代码]¶
从日期或时间获取时间段的结束时间。
- 参数
value – 初始日期或时间。
granularity – 时间段类型(字符串),可以是年、季度、月、周、日或小时。
- 返回
对应于指定时间段起始时间的日期/时间对象。
- static add(value: odoo.tools.date_utils.D, *args, **kwargs) odoo.tools.date_utils.D [源代码]¶
返回
value
和relativedelta
的和。- 参数
value – 初始日期或时间。
args – 直接传递给
relativedelta
的位置参数。kwargs – 直接传递给
relativedelta
的关键字参数。
- 返回
生成的日期/时间。
- static subtract(value: odoo.tools.date_utils.D, *args, **kwargs) odoo.tools.date_utils.D [源代码]¶
返回
value
与relativedelta
之间的差值。- 参数
value – 初始日期或时间。
args – 直接传递给
relativedelta
的位置参数。kwargs – 直接传递给
relativedelta
的关键字参数。
- 返回
生成的日期/时间。
- static context_timestamp(record, timestamp)[源代码]¶
将给定的时间戳转换为客户端的时区时间。
注解
此方法*不*适用于默认初始化器,因为 datetime 字段在客户端显示时会自动转换。对于默认值,应使用
now()
方法。- 参数
record – 用于获取时区的记录集。
timestamp (datetime) – 天真的 datetime 值(以 UTC 表示),将被转换为客户端时区时间。
- 返回
时间戳已转换为上下文时区中的带时区感知的 datetime。
- 返回类型
datetime
关系字段¶
- class odoo.fields.Many2one[源代码]¶
此类字段的值是一个大小为 0(无记录)或 1(单条记录)的记录集。
- 参数
comodel_name (str) – 目标模型的名称,
必填
,除非是相关字段或扩展字段。domain – 客户端候选值的可选域(域或一个将被评估以提供域的 Python 表达式)。
context (dict) – 处理该字段时在客户端使用的可选上下文。
ondelete (str) – 当引用的记录被删除时的操作;可能的值为:
'set null'
、'restrict'
、'cascade'
。auto_join (bool) – 是否在通过该字段搜索时生成 JOIN(默认值:
False
)。delegate (bool) – 将其设置为
True
以使目标模型的字段可以从当前模型访问(对应于_inherits
)。check_company (bool) – 标记该字段以在
_check_company()
中进行验证。根据字段是否为 company_dependent,行为有所不同。对非 company_dependent 字段的约束是:目标记录的 company_id 必须与记录的 company_id 兼容。对 company_dependent 字段的约束是:目标记录的 company_id 必须与当前活动公司兼容。
- 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 不允许在同一模型上具有相同 comodel 的多个隐式关系参数字段,因为这些字段将使用相同的表。ORM 防止两个 many2many 字段使用相同的关系参数,除非
两个字段使用相同的模型、comodel,并且关系参数是显式的;或者
至少有一个字段属于
_auto = False
的模型。
- 参数
domain – 客户端候选值的可选域(域或一个将被评估以提供域的 Python 表达式)。
context (dict) – 处理该字段时在客户端使用的可选上下文。
check_company (bool) – 标记该字段以在
_check_company()
中进行验证。根据字段属性添加默认的公司域。
- class odoo.fields.Command[源代码]¶
One2many
和Many2many
字段需要特殊的命令来操作它们实现的关系。内部表示中,每个命令是一个包含三个元素的元组,其中第一个元素是一个标识命令的必填整数,第二个元素是要应用命令的相关记录 ID(命令 update、delete、unlink 和 link)或 0(命令 create、clear 和 set),第三个元素是要写入记录的
values``(命令 create 和 update)或相关记录的新 ``ids
列表(命令 set),或者是 0(命令 delete、unlink、link 和 clear)。通过 Python,我们鼓励开发者通过此命名空间的各种函数创建新命令。我们也鼓励开发者在比较现有命令的第一个元素时使用命令标识符常量名称。
通过 RPC,无法使用函数或命令常量名称。必须改写为一个包含三个元素的元组,其中第一个元素是命令的整数标识符。
- CREATE = 0¶
- UPDATE = 1¶
- DELETE = 2¶
- UNLINK = 3¶
- LINK = 4¶
- CLEAR = 5¶
- SET = 6¶
- classmethod create(values: dict)[源代码]¶
使用
values
在关联模型中创建新记录,并将创建的记录与self
关联。在
Many2many
关系的情况下,在关联模型中会创建一条唯一的新记录,使得self
中的所有记录都与该新记录关联。在
One2many
关系的情况下,关联模型中会为self
中的每条记录创建一条新记录,使得self
中的每条记录恰好与一条新记录关联。返回命令三元组
(CREATE, 0, values)
- classmethod delete(id: int)[源代码]¶
从数据库中删除关联记录,并解除其与
self
的关系。在
Many2many
关系的情况下,如果记录仍与其他记录关联,则可能无法从数据库中删除该记录。返回命令三元组
(DELETE, id, 0)
伪关系字段¶
计算字段¶
字段可以通过 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)]
在对模型执行实际搜索之前,会调用搜索方法以处理域条件。它必须返回一个等效于条件
字段 操作符 值
的域。
计算字段默认为只读。要允许对计算字段 设置 值,请使用
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()[源代码]¶
通过调用当前活动记录的
toggle_active()
方法,将记录集的active
属性设置为False
。
- Model.action_unarchive()[源代码]¶
通过调用当前非活动记录的
toggle_active()
方法,将记录集的active
属性设置为True
。
- Model.parent_id¶
_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), ...
字段访问¶
记录集提供了一个“活动记录”接口:模型字段可以直接作为属性从记录中读取和写入。
注解
当访问可能包含多个记录的记录集上的非关系字段时,请使用 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
警告
尝试在多个记录上读取字段会引发非关系字段的错误。
访问关系字段(Many2one
、One2many
、 Many2many
) 始终 返回一个记录集,如果字段未设置,则返回空记录集。
记录缓存与预取¶
Odoo 为记录的字段维护了一个缓存,因此并非每次字段访问都会发出数据库请求,否则会对性能造成严重影响。以下示例仅对第一条语句查询数据库::
record.name # first access reads value from database
record.name # second access gets value from cache
为了避免一次读取一条记录的一个字段,Odoo 预取 记录和字段,遵循一些启发式方法以获得良好的性能。一旦需要读取某个字段,ORM 实际上会在更大的记录集上读取该字段,并将返回的值存储在缓存中以供后续使用。预取的记录集通常是通过迭代获取记录的记录集。此外,所有简单的存储字段(布尔值、整数、浮点数、字符、文本、日期、日期时间、选择项、多对一)都会一起提取;它们对应于模型表的列,并在同一查询中高效提取。
考虑以下示例,其中 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
参见
方法 search_fetch()
和 fetch()
可用于填充记录缓存,通常是在预取机制无法良好工作的情况下使用。
方法装饰器¶
Odoo API 模块定义了 Odoo 环境和方法装饰器。
- odoo.api.model(method: odoo.api.T) odoo.api.T [源代码]¶
装饰一种记录风格的方法,其中
self
是一个记录集,但其内容并不重要,只有模型相关。例如:@api.model def method(self, args): ...
- odoo.api.constrains(*args: str) Callable[[T], T] [源代码]¶
装饰一个约束检查器。
每个参数必须是用于检查的字段名称::
@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: str) Callable[[T], T] [源代码]¶
返回一个装饰器,指定“计算”方法(针对新式函数字段)的字段依赖项。每个参数必须是一个由点号分隔的字段名称字符串::
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.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()
方法。警告
one2many
或many2many
字段无法通过 onchange 修改自身。这是 Web 客户端的限制 - 参见 #2693。
- 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)
进行装饰。
- odoo.api.depends_context(*args)[源代码]¶
返回一个装饰器,用于指定非存储的“计算”方法的上下文依赖项。每个参数是上下文字典中的键:
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)
所有依赖项必须是可哈希的。以下键具有特殊支持:
- odoo.api.model_create_multi(method: odoo.api.T) odoo.api.T [源代码]¶
装饰一个接受字典列表并创建多条记录的方法。该方法可以使用单个字典或字典列表调用:
record = model.create(vals) records = model.create([vals, ...])
- odoo.api.ondelete(*, at_uninstall)[源代码]¶
标记一个方法以便在
unlink()
期间执行。此装饰器的目标是,如果从业务角度来看删除某些记录没有意义,则允许在删除记录时出现客户端错误。例如,用户不应能够删除已验证的销售订单。
虽然可以通过简单地覆盖模型上的
unlink
方法来实现这一点,但它有一个缺点,即与模块卸载不兼容。在卸载模块时,覆盖可能会引发用户错误,但我们不应该关心,因为模块正在被卸载,因此与模块相关的 所有 记录都应该被删除。这意味着通过覆盖
unlink
,有很大可能某些表/记录会作为卸载模块的残留数据保留下来。这会使数据库处于不一致状态。此外,如果模块在该数据库上重新安装,则可能存在冲突风险。使用
@ondelete
装饰的方法应在满足某些条件时抛出错误,并且按照惯例,方法应命名为_unlink_if_<条件>
或_unlink_except_<非条件>
。@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
。
环境¶
- class odoo.api.Environment(cr, uid, context, su=False, uid_origin=None)[源代码]¶
环境存储了 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 记录
警告
在 sudo 模式下未应用健全性检查!在 sudo 模式下,用户可以访问任何公司,即使不在其允许的公司范围内。
这允许触发跨公司的修改,即使当前用户无法访问目标公司。
- Environment.companies¶
返回用户启用的公司记录集。
如果未在上下文中指定(
allowed_company_ids
),则回退到当前用户的公司。- 引发
AccessError –
allowed_company_ids
上下文键内容无效或未经授权。- 返回
当前公司(默认值为
self.user.company_ids
),使用当前环境。- 返回类型
res.company 记录集
警告
在 sudo 模式下未应用健全性检查!在 sudo 模式下,用户可以访问任何公司,即使不在其允许的公司范围内。
这允许触发跨公司的修改,即使当前用户无法访问目标公司。
有用的环境方法¶
- Environment.ref(xml_id, raise_if_not_found=True)[源代码]¶
返回与给定
xml_id
对应的记录。- 参数
- 返回
找到的记录或 None
- 引发
ValueError – 如果未找到记录且
raise_if_not_found
为 True
- Environment.execute_query(query: odoo.tools.sql.SQL) list[tuple] [源代码]¶
执行给定的查询,获取其结果并将其作为元组列表返回(如果没有结果则返回空列表)。该方法会自动刷新查询元数据中的所有字段。
修改环境¶
- 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: api.Environment) Self [源代码]¶
返回此记录集的新版本,附加到提供的环境。
- 参数
env (
Environment
) –
注解
返回的记录集与
self
具有相同的预取对象。
SQL 执行¶
环境中的 cr
属性是当前数据库事务的游标,允许直接执行 SQL,无论是因为查询难以用 ORM 表达(例如复杂连接),还是出于性能原因::
self.env.cr.execute("some_sql", params)
警告
执行原始 SQL 会绕过 ORM,从而绕过 Odoo 安全规则。请确保在使用用户输入时对查询进行清理,并在不需要使用 SQL 查询时优先使用 ORM 工具。
构建 SQL 查询的推荐方法是使用包装器对象
- class odoo.tools.SQL(code: str | SQL = '', /, *args, to_flush: Field | None = None, **kwargs)[源代码]¶
一个封装 SQL 代码及其参数的对象,例如:
sql = SQL("UPDATE TABLE foo SET a = %s, b = %s", 'hello', 42) cr.execute(sql)
代码以
%
格式字符串的形式提供,支持位置参数(使用%s
)或命名参数(使用%(name)s
)。不过,转义字符(如"%%"
)不受支持。参数旨在通过%
格式化运算符合并到代码中。SQL 封装器设计为可组合:参数可以是实际参数,也可以是 SQL 对象本身:
sql = SQL( "UPDATE TABLE %s SET %s", SQL.identifier(tablename), SQL("%s = %s", SQL.identifier(columnname), value), )
合并后的 SQL 代码由
sql.code
提供,而相应的合并参数由列表sql.params
提供。这允许组合任意数量的 SQL 条件,而无需单独组合它们的参数,后者可能繁琐、容易出错,并且是psycopg2.sql <https://www.psycopg.org/docs/sql.html>
的主要缺点。封装器的第二个目的是防止 SQL 注入。确实,如果
code
是字符串字面量(而非动态字符串),则使用code
创建的 SQL 对象保证是安全的,前提是其参数中的 SQL 对象本身也是安全的。封装器还可能包含一些元数据
to_flush
。如果不是None
,其值是 SQL 代码所依赖的字段。封装器及其部分的元数据可以通过迭代器sql.to_flush
访问。
关于模型,有一个重要的点需要了解:它们不一定会立即执行数据库更新。事实上,出于性能原因,框架会在修改记录后延迟字段的重新计算。某些数据库更新也会被延迟。因此,在查询数据库之前,必须确保数据库包含与查询相关的数据。这一操作称为 刷新,它会执行预期的数据库更新。
Example
# make sure that 'partner_id' is up-to-date in database
self.env['model'].flush_model(['partner_id'])
self.env.cr.execute(SQL("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 – 可选的字段名称可迭代对象以刷新
- Model.flush_recordset(fnames=None)[源代码]¶
处理记录
self
上的挂起计算和数据库更新。当提供参数时,该方法保证至少将self
上给定的字段刷新到数据库中。不过,可能会刷新更多字段和记录。- 参数
fnames – 可选的字段名称可迭代对象以刷新
由于模型使用相同的游标,并且 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
中的字段返回值。
- Model.name_create(name) record [源代码]¶
通过调用
create()
创建一条新记录,仅提供显示名称作为值。新记录将使用适用于此模型的任何默认值或通过上下文提供的值进行初始化。
create()
的常规行为适用。- 参数
name – 要创建的记录的显示名称
- 返回类型
- 返回
创建记录的 (id, 显示名称) 对值
- Model.write(vals)[源代码]¶
使用提供的值更新
self
中的所有记录。- 参数
vals (dict) – 要更新的字段及其设置的值
- 引发
AccessError – 如果用户不允许修改指定的记录/字段
ValidationError – 如果为选择字段指定了无效值
UserError – 如果操作会导致对象层次结构中出现循环(例如,将对象设置为其自身的父对象)
对于
Many2one
,值应为要设置的记录的数据库标识符。对于
One2many
或Many2many
关系字段,预期值是一个包含Command
的列表,用于操作关系。共有 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 [源代码]¶
在当前环境中返回以参数形式提供的 id 的记录集。
self.browse([7, 18, 12]) res.partner(7, 18, 12)
- Model.search(domain[, offset=0][, limit=None][, order=None])[源代码]¶
搜索满足给定
domain
的记录,详见 search domain。- 参数
- 返回
至多
limit
条符合搜索条件的记录- 引发
AccessError – 如果用户无权访问请求的信息
这是一个高级方法,不应被覆盖。其实际实现由方法
_search()
完成。
- Model.search_count(domain[, limit=None]) int [源代码]¶
返回当前模型中匹配 提供的域 的记录数量。
- 参数
domain – 搜索域 。使用空列表以匹配所有记录。
limit – 要计数的最大记录数(上限)(默认:全部)
这是一个高级方法,不应被覆盖。其实际实现由方法
_search()
完成。
- Model.search_fetch(domain, field_names[, offset=0][, limit=None][, order=None])[源代码]¶
搜索满足给定
domain
搜索域 的记录,并将指定字段提取到缓存中。此方法类似于search()
和fetch()
的组合,但以最少的 SQL 查询完成两项任务。- 参数
- 返回
至多
limit
条符合搜索条件的记录- 引发
AccessError – 如果用户无权访问请求的信息
- Model.name_search(name='', args=None, operator='ilike', limit=100)[源代码]¶
搜索显示名称在使用给定
operator
比较时与给定name
模式匹配的记录,同时还需要匹配可选的搜索域(args
)。例如,用于根据关系字段的部分值提供建议。通常应表现为
display_name
的反向操作,但不保证始终如此。此方法等同于调用基于
display_name
的search()
,并在结果搜索中映射 id 和 display_name。
- Model.fetch(field_names)[源代码]¶
确保
self
中记录的给定字段已在内存中,通过从数据库中提取必要的内容。非存储字段大多会被忽略,除非它们有存储依赖项。此方法应被调用以优化代码。- 参数
field_names – 要提取的字段名称集合
- 引发
AccessError – 如果用户无权访问请求的信息
此方法通过
_search()
和_fetch_query()
方法实现,不应被覆盖。
- Model.read([fields])[源代码]¶
读取
self
中记录的请求字段,并将其值作为字典列表返回。- 参数
- 返回
一个字典列表,将字段名称映射到其值,每个记录对应一个字典
- 返回类型
- 引发
AccessError – 如果用户无权访问请求的信息
ValueError – 如果请求的字段不存在
这是一个高级方法,不应被覆盖。若要修改从数据库读取字段的方式,请参阅方法
_fetch_query()
和_read_format()
。
- Model._read_group(domain, groupby=(), aggregates=(), having=(), offset=0, limit=None, order=None)[源代码]¶
获取由
groupby
字段分组并按domain
过滤记录的aggregates
指定的字段聚合。- 参数
groupby (list) – 记录将按其分组的 groupby 描述列表。groupby 描述可以是一个字段(则按该字段分组),或者是一个字符串
'field:granularity'
。目前支持的粒度仅为'day'
、'week'
、'month'
、'quarter'
或'year'
,并且这些粒度仅对日期/时间字段有意义。aggregates (list) – 聚合规范列表。每个元素是
'field:agg'`(使用聚合函数 `'agg'
的聚合字段)。可能的聚合函数由 PostgreSQL 提供,包括'count_distinct'`(具有预期含义)和 `'recordset'`(类似于 `'array_agg'
并转换为记录集)。having (list) – 一个域,其中有效的“字段”是聚合字段。
offset (int) – 可选的要跳过的组数
limit (int) – 可选的最大返回组数
- 返回
包含组值和聚合值的元组列表(扁平化):
[(groupby_1_value, ... , aggregate_1_value_aggregate, ...), ...]
。如果组是关联字段,则其值将是一个记录集(带有正确的预取设置)。- 返回类型
- 引发
AccessError – 如果用户无权访问请求的信息
- Model.read_group(domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True)[源代码]¶
获取按给定
groupby
字段分组的列表视图中的记录列表。- 参数
fields (list) – 对象上指定的列表视图中存在的字段列表。每个元素可以是 ‘field’(字段名称,使用默认聚合),或 ‘field:agg’(使用聚合函数 ‘agg’ 的聚合字段),或 ‘name:agg(field)’(使用 ‘agg’ 聚合字段并将其作为 ‘name’ 返回)。可能的聚合函数由 PostgreSQL 提供,并包括 ‘count_distinct’,具有预期的含义。
groupby (list) – 记录将按其分组的 groupby 描述列表。groupby 描述可以是一个字段(则按该字段分组)。对于日期和时间字段,您可以使用语法 ‘field:granularity’ 指定粒度。支持的粒度包括 ‘hour’、’day’、’week’、’month’、’quarter’ 或 ‘year’;Read_group 还支持整数日期部分:’year_number’、’quarter_number’、’month_number’、’iso_week_number’、’day_of_year’、’day_of_month’、’day_of_week’、’hour_number’、’minute_number’ 和 ‘second_number’。
offset (int) – 可选的要跳过的组数
limit (int) – 可选的最大返回组数
orderby (str) – 可选的
order by
规范,用于覆盖组的自然排序顺序,另见 :meth:`~.search`(目前仅支持 many2one 字段)。lazy (bool) – 如果为 true,则结果仅按第一个 groupby 分组,其余的 groupby 放在 __context 键中。如果为 false,则所有 groupby 在一次调用中完成。
- 返回
字典列表(每个记录一个字典),包含:* 按
groupby
参数中的字段分组的字段值 * __domain:指定搜索条件的元组列表 * __context:带有类似groupby
参数的字典 * __range:(仅适用于日期/时间字段)以 field_name:granularity 为键的字典,映射到一个包含键 “from”(包含)和 “to”(排除)的字典,映射到组的时间范围的字符串表示形式。- 返回类型
[{‘field_name_1’: value, …}, …]
- 引发
AccessError – 如果用户无权访问请求的信息
字段¶
搜索域¶
域是一个条件列表,每个条件是一个三元组(可以是 list
或 tuple
),格式为 (字段名, 操作符, 值)
,其中:
字段名
(str
)当前模型的字段名,或者是通过
Many2one
使用点号表示法遍历关系的字段名,例如'street'
或'partner_id.country'
。如果字段是日期(时间)字段,还可以使用'字段名.粒度'
指定日期的一部分。支持的粒度包括'年份'
、'季度'
、'月份'
、'ISO周数'
、'星期几'
、'月中的天数'
、'年中的天数'
、'小时数'
、'分钟数'
、'秒数'
。它们均使用整数作为值。
操作符
(str
)用于比较
字段名
和值
的操作符。有效的操作符包括:=
等于
!=
不等于
>
大于
>=
大于或等于
<
小于
<=
小于或等于
=?
未设置或等于(如果
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
记录的子记录(后代)(value
可以是一个项目或项目列表)。考虑模型的语义(即遵循由
_parent_name
命名的关系字段)。parent_of
是
value
记录的父记录(祖先)(value
可以是一个项目或项目列表)。考虑模型的语义(即遵循由
_parent_name
命名的关系字段)。any
如果通过
field_name
(Many2one
、One2many
或Many2many
)的关系遍历中任意记录满足提供的域value
,则匹配。not any
如果通过
field_name
(Many2one
、One2many
或Many2many
)的关系遍历中没有记录满足提供的域value
,则匹配。
value
变量类型,必须可通过
operator
与命名字段进行比较。
域条件可以使用 前缀 形式的逻辑运算符组合:
'&'
逻辑 AND,默认用于组合连续的条件。二元运算(使用接下来的 2 个条件或组合)。
'|'
逻辑 OR,二元运算。
'!'
逻辑 NOT,一元运算。
注解
主要用于否定条件组合。单个条件通常具有否定形式(例如
=
->!=
,<
->>=
),这比否定正向条件更简单。
Example
搜索名称为 ABC 且电话或手机号码包含 7620 的合作伙伴::
[('name', '=', 'ABC'),
'|', ('phone','ilike','7620'), ('mobile', 'ilike', '7620')]
搜索至少有一行产品缺货的待开票销售订单::
[('invoice_status', '=', 'to invoice'),
('order_line', 'any', [('product_id.qty_available', '<=', 0)])]
搜索所有出生月份为二月的合作伙伴::
[('birthday.month_number', '=', 2)]
删除¶
- Model.unlink()[源代码]¶
删除
self
中的记录。- 引发
AccessError – 如果用户不允许删除所有给定记录
UserError – 如果该记录是其他记录的默认属性
记录(集)信息¶
- Model.ids¶
返回与
self
对应的实际记录 ID 列表。
- odoo.models.env¶
返回给定记录集的环境。
- 类型
- Model.get_metadata()[源代码]¶
返回有关给定记录的一些元数据。
- 返回
每个请求记录的所有权字典列表
- 返回类型
包含以下键的字典列表: * id:对象 ID * create_uid:创建记录的用户 * create_date:记录创建日期 * write_uid:最后更改记录的用户 * write_date:记录最后更改的日期 * xmlid:用于引用此记录的 XML ID(如果有),格式为
module.name
* xmlids:字典列表,包含格式为module.name
的 xmlid 和布尔值 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
返回两个记录集的并集,即包含两个来源中所有记录的新记录集。set1 & set2
返回两个记录集的交集,即仅包含两个来源中共有记录的新记录集。set1 - set2
返回一个新记录集,其中仅包含set1
中不在set2
中的记录。
记录集是可迭代的,因此可以使用常见的 Python 工具进行转换(如 map()
、 sorted()
、 ifilter()
等),但这些工具返回的是 list
或 iterator ,从而无法对其结果调用方法或使用集合操作。
因此,记录集提供了以下操作,尽可能返回记录集本身:
过滤¶
- Model.filtered(func) Self [源代码]¶
返回满足
func
的self
中的记录。- 参数
func (callable or 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.mapped(func)[源代码]¶
对
self
中的所有记录应用func
,并返回结果作为列表或记录集(如果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')
排序¶
分组¶
- Model.grouped(key)[源代码]¶
通过
key
急切地将self
中的记录分组,返回从key
的结果到记录集的字典。所有生成的记录集都保证属于同一个预取集。提供了一种便捷的方法来划分现有的记录集,而无需
read_group()
的开销,但不执行聚合。注解
与
itertools.groupby()
不同,它不关心输入顺序,但代价是无法做到惰性处理。
继承与扩展¶
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
的实现或多或少存在问题,如果可以,请尽量避免使用;链式
_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.UserError(message)[源代码]¶
由客户端管理的通用错误。
通常发生在用户尝试执行某些操作,而这些操作在给定记录的当前状态下没有意义。语义上类似于通用的 400 HTTP 状态码。
- exception odoo.exceptions.RedirectWarning(message, action, button_text, additional_context=None)[源代码]¶
带有重定向用户可能性的警告,而不是简单地显示警告消息。