- 当使用子字段时,依赖可使用分点路径:
@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
修改其自身。这是客户端限制 - 查看 #2693 odoo.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模式下,用户可以访问任何公司,即使不是在他允许的公司。
这允许触发公司间修改,即使当前用户无权访问目标公司