odoo基础(ORM API)

一、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 <set2返回set1是否为set2的子集(分别为strict)
③ set1> = set2和set1> set2返回set1是超集还是set2(分别严格)
④ 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
<Environment object ...>
>>> records.env.user
res.user(3)
>>> records.env.cr
<Cursor object ...)

从其他记录集创建记录集时,将继承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]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • 记录集 model的数据是通过数据集合的形式来使用的,定义在model里的函数执行时它们的self变量也是一个数据...
    XiaoHaiYang阅读 16,860评论 0 16
  • 〇、前言 本文共108张图,流量党请慎重! 历时1个半月,我把自己学习Python基础知识的框架详细梳理了一遍。 ...
    Raxxie阅读 18,907评论 17 410
  • 记录集(Recordsets) 本文涉及的API是基于Odoo 8.0及之后版本的,7.0版本之前的API称为“旧...
    luohuayong阅读 4,624评论 1 6
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 使用xcode可以进行和git进行简单的版本控制。首先我们需要安装git环境,这些网上有很多文章进行了总结,过程都...
    loghm阅读 1,397评论 0 0