一、Recordsets(记录集)
与模型和记录的交互是通过记录集执行的,记录集是同一模型的有序记录集。模型上定义的方法在记录集上执行,它们的self是记录集:
class AModel(models.Model):
_name = 'a.model'
def a_method(self):
# self can be anywhere 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), ...
1.字段访问
记录集提供“活动记录”界面:模型字段可以作为属性直接从记录中读取和写入,但仅限于单个记录(单记录记录集)。字段值也可以像dict项一样访问,这比动态字段名称的getattr()更优雅,更安全。设置字段的值会触发对数据库的更新:
>>> record.name
Example Name
>>> record.company_id.name
Company Name
>>> record.name = "Bob"
>>> field = "name"
>>> record[field]
Bob
2.记录集的缓存和预取
Odoo为记录字段维护一个缓存,因此并非每个字段访问都会发出数据库请求,这对性能来说太糟糕了。以下示例仅针对第一个语句查询数据库:
record.name # first access reads value from database
record.name # second access gets value from cache
为了避免一次在一条记录上读取一个字段,Odoo会根据一些启发式方法预取记录和字段以获得良好的性能。一旦必须在给定记录上读取字段,ORM实际上会在较大的记录集上读取该字段,并将返回的值存储在缓存中供以后使用。预取记录集通常是记录集,记录来自迭代。
Moreover, all simple stored fields (boolean, integer, float, char, text, date, datetime, selection, many2one) are fetched altogether; they correspond to the columns of the model's table, and are fetched efficiently in the same query.
中文(简体)
此外,所有简单的存储字段(布尔,整数,浮点数,字符,文本,日期,日期时间,选择,许多2)都被完全取出;它们对应于模型表的列,并在同一查询中有效获取。请考虑以下示例,其中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)
3.集合操作
Recordsets are immutable, but sets of the same model can be combined using various set operations, returning new recordsets. Set operations do not preserve order.
中文(简体)
记录集是不可变的,但可以使用各种set操作组合相同模型的集合,返回新的记录集。设置操作不保留顺序。
① set中的record返回set中是否存在record(必须是1元素记录集)。记录不在set中是反向操作。
② set1 <= set2和set1
④ set1 | set2返回两个记录集的并集,一个包含任一源中存在的所有记录的新记录集
⑤ set1和set2返回两个记录集的交集,一个新的记录集只包含两个源中的记录
⑥ set1 - set2返回一个新记录集,其中只包含set1中不在set2中的记录
4.其他记录集的操作
记录集是可迭代的,因此常用的Python工具可用于转换(map(),sorted(),ifilter(),...)但是这些工具返回列表或迭代器,删除了在结果上调用方法的能力,或者使用集合操作。因此,记录集提供这些操作返回记录集本身(如果可能):
① filtered()
返回仅包含满足提供的谓词函数的记录的记录集。谓词也可以是一个字符串,按字段为true或false进行过滤:
# 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")
②sorted()
返回按提供的键函数排序的记录集。如果未提供密钥,请使用模型的默认排序顺序:
# sort records by name
records.sorted(key=lambda r: r.name)
③mapped()
将提供的函数应用于记录集中的每个记录,如果结果是记录集,则返回记录集:
# 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
record.mapped('partner_id')
# returns the union of all partner banks, with duplicates removed
record.mapped('partner_id.bank_ids')
二、Environment
environment存储ORM使用的各种上下文数据:数据库游标(用于数据库查询),当前用户(用于访问权限检查)和当前上下文(存储任意元数据)。environment还存储缓存。所有记录集都有一个不可变的environment,可以使用env访问,并允许访问当前用户(用户),游标(cr)或上下文(context):
>>> records.env
>>> records.env.user
res.user(3)
>>> records.env.cr
从其他记录集创建记录集时,将继承environment。该environment可用于在其他模型中获取空记录集,并查询该模型:
>>> 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
可以从记录集中自定义environment。这将使用更改的environment返回记录集的新版本。
①sudo()
使用提供的用户集创建新environment,如果未提供任何用户,则使用管理员(绕过安全上下文中的访问权限/规则),使用新environment返回调用它的记录集的副本:
# create partner object as administrator
env['res.partner'].sudo().create({'name': "A Partner"})
# list partners visible by the "public" user
public = env.ref('base.public_user')
env['res.partner'].sudo(public).search([])
②with_context()
1.可以采用单个位置参数,它替换当前环境的上下文
2.可以通过关键字获取任意数量的参数,这些参数将添加到当前environment的上下文或步骤1中设置的上下文中
# look for partner, or create one with specified timezone if none is
# found
env['res.partner'].with_context(tz=a_tz).find_or_create(email_address)
③with_env()
完全取代现有environment
三、常见ORM操作
①search()
采用搜索域,返回匹配记录的记录集。可以返回匹配记录的子集(偏移和限制参数)并进行排序(顺序参数):
>>> # searches the current model
>>> self.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)
>>> self.search([('is_company', '=', True)], limit=1).name
'Agrolait'
②create()
获取许多字段值,并返回包含创建的记录的记录集:
>>> self.create({'name': "New Name"})
res.partner(78)
③write()
获取许多字段值,将它们写入其记录集中的所有记录。不返回任何东西:
self.write({'name': "Newer Name"})
④browse()
获取数据库ID或id列表并返回记录集,当从外部Odoo获取记录ID(例如,通过外部系统往返)或在旧API中调用方法时非常有用:
>>> self.browse([7, 18, 12])
res.partner(7, 18, 12)
⑤exists()
返回仅包含数据库中存在的记录的新记录集。可用于检查记录(例如从外部获得)是否仍然存在:
if not record.exists():
raise Exception("The record has been deleted")
或者在调用可能删除了一些记录的方法之后:
records.may_remove_some()
# only keep records which were not deleted
records = records.exists()
⑥ref()
environment方法返回与提供的外部id匹配的记录:
>>> env.ref('base.group_public')
res.groups(2)
⑦ensure_one()
检查记录集是否为单例(仅包含单个记录),否则会引发错误:
records.ensure_one()
# is equivalent to but clearer than:
assert len(records) == 1, "Expected singleton"
四、创建Models
模型字段定义为模型本身的属性:
from odoo import models, fields
class AModel(models.Model):
_name = 'a.model.name'
field1 = fields.Char()
字段定义常用参数
1.string
默认情况下,字段的标签(用户可见名称)是字段名称的大写版本,可以使用字符串参数覆盖:
field2 = fields.Integer(string="an other field")
有关各种字段类型和参数,请参阅字段参考。
2. default
默认值被定义为字段上的参数,值为:
a_field = fields.Char(default="a value")
或者一个被调用来计算默认值的函数,它应该返回该值:
def compute_default_value(self):
return self.get_value()
a_field = fields.Char(default=compute_default_value)
3.computed fields
可以使用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会将它们存储在数据库中并自动启用搜索
也可以通过设置搜索参数来启用在计算字段上搜索。该值是返回域的方法名称:
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')
@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
4. related fields
计算字段的特殊情况是相关(代理)字段,其提供当前记录上的子字段的值。它们是通过设置相关参数来定义的,就像它们可以存储的常规计算字段一样:
nickname = fields.Char(related='user_id.partner_id.name', store=True)
5. onchange:动态更新UI
当用户更改表单中的字段值(但尚未保存表单)时,根据该值自动更新其他字段可能很有用。更改税额或添加新发票行时更新最终总计
计算字段会自动检查并重新计算,它们不需要更改。
对于非计算字段,onchange()装饰器用于提供新的字段值:
@api.onchange('field1', 'field2') # if these fields are changed, call method
def check_change(self):
if self.field1 < self.field2:
self.field3 = True
6.Low-level SQL
环境中的cr属性是当前数据库事务的游标,并允许直接执行SQL,对于难以使用ORM表达的查询(例如复杂连接)或出于性能原因:
self.env.cr.execute("some_sql", param1, param2, param3)
由于模型使用相同的游标,而环境包含各种缓存,因此在原始SQL中更改数据库时,这些缓存必须无效,否则模型的进一步使用可能会变得不连贯。
在SQL中使用CREATE,UPDATE或DELETE时必须清除缓存,而不是SELECT(它只是读取数据库)。
五、Compatibility between new API and old API(新API和旧API之间的兼容性)
Odoo目前正在从较旧(较不常规)的API转换,可能需要手动从一个API手动桥接到另一个:
1.RPC层(XML-RPC和JSON-RPC)都是用旧API表示的,纯粹在新API中表示的方法不能通过RPC获得
2.可以从仍旧用旧API样式编写的旧代码片段调用可覆盖的方法
旧API和新API之间的巨大差异是:
1.environment的值(游标,用户ID和上下文)显式传递给方法
2.记录数据(ID)显式传递给方法,可能根本不传递
3.方法往往适用于ID列表而不是记录集
默认情况下,假定方法使用新的API样式,并且不能从旧的API样式调用。
tips:
从新API到旧API的调用被桥接 使用新的API样式时,使用旧API定义的方法的调用会自动转换,不需要做任何特殊的事情:
>>> # method in the old API style
>>> def old_method(self, cr, uid, ids, context=None):
... print ids
>>> # method in the new API style
>>> def new_method(self):
... # system automatically infers how to call the old-style
... # method from the new-style method
... self.old_method()
>>> env[model].browse([1, 2, 3, 4]).new_method()
[1, 2, 3, 4]
两个装饰器可以向旧API公开新式方法:
1.model()
该方法暴露为不使用id,其记录集通常为空。它的“旧API”签名是:cr, uid, *arguments, context:
@api.model
def some_method(self, a_value):
pass
# can be called as
old_style_model.some_method(cr, uid, a_value, context=context)
2.multi()
这个方法暴露为一个id列表(可能是空的),它的“旧API”签名是:cr, uid, ids, *arguments, context:
@api.multi
def some_method(self, a_value):
pass
# can be called as
old_style_model.some_method(cr, uid, [id1, id2], a_value, context=context)
因为新式API倾向于返回记录集,而旧式API倾向于返回id列表,所以还有一个装饰器管理这个:
3.returns()
假定函数返回一个记录集,第一个参数应该是记录集模型或self的名称(对于当前模型)。如果在新API样式中调用该方法,但在从旧API样式调用时将记录集转换为id列表,则无效:
>>> @api.multi
... @api.returns('self')
... def some_method(self):
... return self
>>> new_style_model = env['a.model'].browse(1, 2, 3)
>>> new_style_model.some_method()
a.model(1, 2, 3)
>>> old_style_model = pool['a.model']
>>> old_style_model.some_method(cr, uid, [1, 2, 3], context=context)
[1, 2, 3]