版本8.0中新东西: 这个在Odoo8.0中新加的API的页面文档应该是不断向前发展的主要开发API。同时它还提供了关于移植或桥接版本7和更早版本的“旧API”的信息,但没有明确地记录API。请查看旧文档。
模型和记录相关交互是通过记录集执行的,一个相同模型的有序记录集执行的。
警告
和记录集名字暗示的情况相反,包含副本的记录集当前是可以接收的。这在将来可能有所改变。
模型上定义的方法在一个记录集上执行,并且方法本身(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), ...
字段访问
记录集提供一个“活动记录”接口:模型的字段可以直接读取和写入的记录属性,但只有单个记录(记录集中单个记录)。字段值也可以想字典项一样访问,这比通过方法getattr()动态获取字段名称更加简练和安全。设置字段的值会触发数据库更新操作:
>>> record.name
Example Name
>>> record.company_id.name
Company Name
>>> record.name = "Bob"
>>> field = "name"
>>> record[field]
Bob
试图在多个记录上读取或写入字段会引发错误。
访问一个关联字段 (Many2one
, One2many
, Many2many
) 总是返回一个记录集,如果字段没有设置则返回空集合。
危险
每次给字段赋值触发数据库更新,当在统一时间设置多个字段或设置多个记录上的字段(为相同的值),使用write()
:
# 3 * len(records) database updates
for record in records:
record.a = 1
record.b = 2
record.c = 3
# len(records) database updates
for record in records:
record.write({'a': 1, 'b': 2, 'c': 3})
# 1 database update
records.write({'a': 1, 'b': 2, 'c': 3})
记录缓存和预取
Odoo支持记录字段的缓存,因此不是每个字段访问都进行数据库请求,这对性能来说是可怕的。下面请求数据库查询的列子仅发生在第一条语句中:
record.name # first access reads value from database
record.name # second access gets value from cache
为了避免统一时间读取一条记录的一个字段,Odoo通过一些探索法预取记录和字段来获得更好的性能。在更定的记录上必须一次读取一个字段,事实上ORM在一个更大的记录集上读取字段,并把返回的值放入缓存以方便之后使用。预取得到记录集通常是来自每个记录通过迭代获取的记录集。此外,所有存储的简单字段(boolean, integer, float, char, text, date, datetime, selection, 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
预取对二次记录也起作用:当关系字段被读取时,它们的值(它们是记录)被订阅,以便将来进行预取。访问哪些二次记录中的一个,它预取自同一模型的所有二次记录。这使得下面的示例只生成两个查询,一个用于partners ,另一个用于countries:
countries = set()
for partner in partners:
country = partner.country_id # first pass prefetches all partners
countries.add(country.name) # first pass prefetches all countries
集合操作
记录集是不可变的,但同样模型的集合可以组合使用不同的设置操作,返回新的记录集。集合操作不保留顺序:
record in set
返回record
(必须是有一个元素的记录集) 是否在set
中。record not in set
是相反的操作(即不在集合中)set1 = set2
和set1 > set2
返回set1
是否是set2的父集合 (分别的 严格的)set1 | set2
返回两个记录集的并集,一个包含了原来记录集所有记录的新记录集set1 & set2
返回两个记录集的交集,一个包含原来记录集共同拥有的记录的新记录集set1 - set2
返回一个仅在set1
中而不在set2中的新的记录集
记录集是可以迭代的,一次通常的Python工具(map()
, sorted()
, ifilter()
, ...)可提供转换只用,可是这些返回结果要么是一个list
,要么是一个除去在它们结果上调用方法能力的迭代器, 要么使用集合操作。
因此,记录集提供这些返回记录集本身的操作(如果可能的话):
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()
返回一个提供key函数进行排序的记录集。如果没有提供key函数,使用模型默认的排序方式:
# 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)存储ORM使用的各种上下文数据: 数据库游标(用于数据库查询)、当前用户(用于访问权限检查)和当前上下文(存储任意元数据)。环境还存储缓存。
所有记录集都有一个不可变的环境,可以使用env访问它,也可以给当前用户(user
)访问,光标 (cr
) 或者上下文(context
):
>>> records.env
>>> records.env.user
res.user(3)
>>> records.env.cr
>> 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)
改变环境
环境可以从一个记录集定制而来。这将通过使用改变的环境返回一个新版本的记录集。
sudo()
使用用户提供的设置创建一个新环境,如果没有提供使用系统管理员设置(绕过权限/规则的安全上下文),使用新的环境调用返回一个记录集副本:
# 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中的上下文设置中
# 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()
完全替换现有环境
通用的ORM方法search()
接受一个搜索域,返回一个匹配记录的记录集。能够返回一个匹配记录(offset
和limit
参数)的子集合并按顺序(order
参数)返回:
>>> # 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'
提示
仅仅检查是否有匹配的记录的域, 或者计数匹配记录的数量,使用search_count()
create()
接受多个记录值,并返回一个包含创建的记录的记录集:
>>> self.create({'name': "New Name"})
res.partner(78)
write()
接受多个记录值,修改记录集中所有的记录。不返回任何值:
self.write({'name': "Newer Name"})
browse()
接手一个数据库id或者id的列表并返回一个记录集,当记录id是从Odoo(例如往返通过外部系统)外部获取或者当调用旧的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()
返回提供匹配外部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"
创建模型
模型字段作为属性被定义在模型内:
from odoo import models, fields
class AModel(models.Model):
_name = 'a.model.name'
field1 = fields.Char()
警告
这意味着你不能定义相同名称的字段和方法,它们是冲突的
默认情况下,字段的标签(用户可见的名字)是字段名称的大写版本,它可以通过string参数重写:
field2 = fields.Integer(string="an other field")
对于各种字段类型和参数, 详见字段参考(在odoo10系列--ORM API 三中)
默认值被定义为字段上的参数,或者是值:
a_field = fields.Char(default="a value")
或者调用一个函数来计算默认值,它应该返回这个值:
def compute_default_value(self):
return self.get_value()
a_field = fields.Char(default=compute_default_value)
计算的字段
字段可以使用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') @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
相关字段
计算字段的一个特殊情况是相关的(代理)字段, 它提供当前记录中子字段的值。它们通过设置related参数定义并像可以存储的普通计算字段一样:
nickname = fields.Char(related='user_id.partner_id.name', store=True)
变化:即时更新的用户界面
当用户更改表单中的字段值(但尚未保存表单)时,根据该值自动更新其他字段是有用的,例如在更改税收或添加新的发票行时更新最终总金额。
- 计算字段是自动检查和重新计算,他们不需要
onchange
-
对于非计算字段,
onchange()
修饰符用例提供新字段值:@api.onchange('field1', 'field2') # if these fields are changed, call method def check_change(self): if self.field1 < self.field2: self.field3 = True
在方法中执行的更改随后被发送到客户端程序并对用户可见
- 计算字段和新API的变化被客户端自动调用而无需添加到视图中(改变的内容)
-
依靠在视图中添加on_change="0"可能一直来自特定字段的触发:
当字段被用户编辑,将不会触发任何界面更新,即使有函数字段或者依赖那个字段的明确变化
注
onchange
方法将这些记录上的虚拟记录分配工作不写入数据库,只用于知道要返回到客户端的值
环境中的cr属性是当前数据库事务的游标,允许直接执行SQL,或者使用ORM难以表达的查询(例如复杂连接)或出于性能原因:
self.env.cr.execute("some_sql", param1, param2, param3)
由于模型使用相同的游标,并且环境(Environment)拥有不同的缓存,当使用原始SQL语句修改数据库时,这些缓存必须失效,否则模型的进一步使用可能变得不连贯。当在SQL中使用CREATE
, UPDATE
或 DELETE
但不是SELECT(简单读取数据库)时有必要清除缓存
清除缓存可以使用的环境(Environment)对象的invalidate_all()方法进行处理
ps:有翻译不当之处,欢迎留言指正。
原文地址:https://www.odoo.com/documentation/10.0/reference/orm.html