Toggle navigation

ORM API

记录集

New in version 8.0: 本文档的新API应该作为odoo8.0以后主要的API。它还提供了有关移植或桥接7.0和 更早版本的“旧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()

迭代一个记录集将产生新的 一个单独记录 的集合("singletons"),就像一个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), ...

字段访问

记录集提供一个“激活记录”界面: 模块字段可以通过这个记录直接被读和写,但是仅限单一记录集(signle-record)。 设置一个字段的值将触发一次数据库更新:

>>> record.name
Example Name
>>> record.company_id.name
Company Name
>>> record.name = "Bob"

尝试在多个记录中读取或写入字段会激发一个错误。

访问一个关联字段(Many2one, One2many, Many2many) 总是 返回一个记录集, 如果该字段没有值将返回空的记录集。

记录缓存和预取

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

预取也适用于 二次记录 :当关系字段被读取时,它们的值(这是记录)为将来的预取订阅。其中访问二次 记录中的某个字段只需从相同的模型预取所有次要记录。这使得下面的示例只生成两次查询, 一个用于合作伙伴,另一个用于国家:

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 <= set2set1 < set2 返回是否 set1set2 的子集
  • set1 >= set2set1 > set2 返回是否 set1set2 的超级
  • set1 | set2 返回两个集合的合集,一个新的记录集包括任一个集合中的所有记录
  • set1 & set2 返回两个集合的并集,一个新的记录集仅包括两个集合中都有的记录
  • set1 - set2 返回一个记录在 set1 而不 set2 中的新的记录集

其他记录集操作方法

记录集是可迭代的因此通常的Python 工具是可用于转化的(map(), sorted(), ifilter(), ...) 然而这些返回的要么是 list 要么是 iterator ,去除了在结果之上调用的方法的能力,或者去除了使用集合的操作。

记录集因此提供这些操作返回记录集本身:

filtered()

返回一个只包含满足提供判定函数的记录集。判定也可以是由真或假字段筛选的字符串:

# 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 通过ORM存储各种环境中的数据:数据库游标(数据库查询) 、
当前用户 (用来权限检查)、当前环境(存储任意的元数据)。环境可以被存储在缓存中。

所有的记录集都有一个不可变的环境,它可以通过 env 访问,通过 (user), 游标 (cr) or 上下文 (context)给当前用户访问:

>>> records.env
<Environment object ...>
>>> 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)

转换环境

来自一个记录集的环境可以被定制。使用转换环境返回一个新版本的记录集。

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. 可以携带一个位置参数,它将取代目前的环境的上下文
  2. 可以通过关键字携带任意数量的参数,这些参数将被增加到当前环境上下文中或步骤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() 提供一个 search domain ,返回一个匹配的记录集。 也可以返回匹配的记录集的子集,通过 offsetlimit 参数,同时通过 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'

.. tip:: 只检查是否有任何匹配的记录,或计数的数目,使用 :meth:`~odoo.models.Model.search_count`
create()

提供一系列数量字段的值,返回包含该记录的记录集:

>>> self.create({'name': "New Name"})
res.partner(78)
write()

提供一系列字段值,将他们写入到记录集中的所有记录中。不返回任何东西:

self.write({'name': "Newer Name"})
browse()

提供数据库id或者ids的集合,返回一个记录集,当记录的id从odoo之外被获取是有用的 (例如 往返通过外部的系统)或:ref:[UNKNOWN NODE title_reference]:

>>> 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()

环境的方法返回匹配到的 external id 的记录:

>>> env.ref('base.group_public')
res.groups(2)
ensure_one()

检查该记录集是一个signleton(仅包含一个单一记录),否则提示一个错误:

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")

针对不同类型字段和参数,看 参考该字段

默认值作为参数被定义在字段上,要么是一个值:

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 参数来在计算字段上搜索。该值是一个方法的名字返回一个 Domain:

    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 (代理)字段,该字段在当前记录提供一个子字段的值. 他们通过 related 参数被指定并且像常规的计算字段可以被存储:

nickname = fields.Char(related='user_id.partner_id.name', store=True)

onchange: 运行中更新界面

当一个用户在表单视图改变一个字段值(但是还没保存),它可以基于这个值被用于自动更新其他字段 例如,当税收改变或者添加一个新的发票行将更新最终总数。

  • 计算字段会自动检查并重新计算,他们不需要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
    

    在这个方法被发送至客户端程序并且对用户可视时,这个改变被展现出来

  • 计算字段和新的onchanges API通过客户端被自动调用无需添加他们在视图中
  • 通过添加on_change="0” 在view中来抑制一个指定字段触发onchange方法:

    <field name="name" on_change="0"/>

    当该字段被用户编辑时,将不会触发任何界面更新,即使这有个函数字段或明确的onchange依赖于该字段

底层SQL

cr 环境属性是针对当前事务的游标和允许直接执行SQL,要么是难以使用

ORM来进行表达, 要么是性能的原因:

self.env.cr.execute("some_sql", param1, param2, param3)

由于模块使用相同的游标并且 Environment 保存不同的缓存,当在原生的SQL中 变更 数据库时这些缓存必须失效,或者进一步利用模块来变成不相干。当在SQL中使用 CREATE, UPDATE 或者 DELETE 时必须清除缓存, 在 SELECT 中则不需要(这只是简单的读取数据库)。

清除缓存可以使用 Environment 对象中的 invalidate_all() 方法。

新旧API的兼容性

Odoo目前正从旧的API中过渡,手动通旧API建立桥梁是必须的:

  • RPC 层(XML-RPC和JSON-RPC)是用旧的API来表示的,单纯使用新的API表达式方法是不可用RPC的
  • 从旧系列的代码重写被调用的方法仍然需要使用旧API的风格

新旧API之间最大的不同:

  • Environment (游标,用户id和上下文)的值直接通过方法传递
  • 记录数据(ids)通过明确的方法传递,而且根本没有通过
  • 方法趋向使用列表而不是记录集

默认情况下,方法假定使用新API风格而不是调用旧的API。

两个装饰器可以使新风格的方法运用到旧的API中:

model()

该方法没有使用ids被展现出来,它的记录集是空的,对应旧的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)
multi()

该方法携带一系列ids被展现出来,对应旧的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)

由于新风格的APIs趋向返回记录集而旧的APIs趋向返回一系列id的列表,因此这也有管理这个的装饰器:

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]

模块参考

class odoo.models.Model(pool, cr)[source]

Main super-class for regular database-persisted Odoo models.

Odoo models are created by inheriting from this class:

class user(Model):
    ...

The system will later instantiate the class once per database (on which the class' module is installed).

结构属性

_name

业务对象名称,以点表示法(在模块命名空间中)

_rec_name

用作名称的替代字段,由osv的name_get()使用(默认:'name'

_inherit
  • 如果 _name 设置,要继承的父模型的名称。 如果从单个父继承,可以是一个 str
  • 如果 _name 未设置, 在本单个模型的名称上进行扩展

See 继承和扩展.

_order

在搜索时未指定顺序的排序字段(默认值 : 'id'

Type
str
_auto

是否应该创建数据库表(默认值:True

如果设置为 False ,则覆盖 init() 来创建数据库表

_table

在以下情况下创建的模型的表名 _auto ,默认情况下自动生成。

_inherits

用字典将父业务对象的_name映射到要使用的相应外键字段的名称:

_inherits = {
    'a.model': 'a_field_id',
    'b.model': 'b_field_id'
}

实现基于组合的继承: 新模型公开了 _inherits-ed模型的所有字段,但不存储它们: 值本身保持存储在链接记录上。

_constraints

(constraint_function,message,fields) 定义Python约束。 字段列表是指示性的

Deprecated since version 8.0: use constrains()

_sql_constraints

(name,sql_definition,message) 的列表定义生成支持表时要执行的SQL约束的三元组

_parent_store

依靠 parent_leftparent_right,设置一个 [UNKNOWN NODE title_reference] 来启用对记录的快速分层查询的当前模型(默认: False

Type
布尔值

CRUD

create(vals) → record[source]

Creates a new record for the model.

The new record is initialized using the values from vals and if necessary those from default_get().

Parameters
vals (dict) --

values for the model's fields, as a dictionary:

{'field_name': field_value, ...}

see write() for details

Returns
new record created
Raises
  • AccessError --
    • if user has no create rights on the requested object
    • if user tries to bypass access rules for create on the requested object
  • ValidateError -- if user tries to enter invalid value for a field that is not in selection
  • UserError -- if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
browse([ids]) → records[source]

Returns a recordset for the ids provided as parameter in the current environment.

Can take no ids, a single id or a sequence of ids.

Deletes the records of the current set

Raises
  • AccessError --
    • if user has no unlink rights on the requested object
    • if user tries to bypass access rules for unlink on the requested object
  • UserError -- if the record is default property for other records
write(vals)[source]

Updates all records in the current set with the provided values.

Parameters
vals (dict) --

fields to update and the value to set on them e.g:

{'foo': 1, 'bar': "Qux"}

will set the field foo to 1 and the field bar to "Qux" if those are valid (otherwise it will trigger an error).

Raises
  • AccessError --
    • if user has no write rights on the requested object
    • if user tries to bypass access rules for write on the requested object
  • ValidateError -- if user tries to enter invalid value for a field that is not in selection
  • UserError -- if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
  • For numeric fields (Integer, Float) the value should be of the corresponding type
  • For Boolean, the value should be a bool
  • For Selection, the value should match the selection values (generally str, sometimes int)
  • For Many2one, the value should be the database identifier of the record to set
  • Other non-relational fields use a string for value

  • One2many and Many2many use a special "commands" format to manipulate the set of records stored in/associated with the field.

    This format is a list of triplets executed sequentially, where each triplet is a command to execute on the set of records. Not all commands apply in all situations. Possible commands are:

    (0, _, values)
    adds a new record created from the provided value dict.
    (1, id, values)
    updates an existing record of id id with the values in values. Can not be used in create().
    (2, id, _)
    removes the record of id id from the set, then deletes it (from the database). Can not be used in create().
    (3, id, _)
    removes the record of id id from the set, but does not delete it. Can not be used on One2many. Can not be used in create().
    (4, id, _)
    adds an existing record of id id to the set. Can not be used on One2many.
    (5, _, _)
    removes all records from the set, equivalent to using the command 3 on every record explicitly. Can not be used on One2many. Can not be used in create().
    (6, _, ids)
    replaces all existing records in the set by the ids list, equivalent to using the command 5 followed by a command 4 for each id in ids.
read([fields])[source]

Reads the requested fields for the records in self, low-level/RPC method. In Python code, prefer browse().

Parameters
fields -- list of field names to return (default is all fields)
Returns
a list of dictionaries mapping field names to their values, with one dictionary per record
Raises AccessError
if user has no read rights on some of the given records
read_group(domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True)[source]

Get the list of records in list view grouped by the given groupby fields

Parameters
  • domain -- list specifying search criteria [['field_name', 'operator', 'value'], ...]
  • fields (list) -- list of fields present in the list view specified on the object
  • groupby (list) -- list of groupby descriptions by which the records will be grouped. A groupby description is either a field (then it will be grouped by that field) or a string 'field:groupby_function'. Right now, the only functions supported are 'day', 'week', 'month', 'quarter' or 'year', and they only make sense for date/datetime fields.
  • offset (int) -- optional number of records to skip
  • limit (int) -- optional max number of records to return
  • orderby (list) -- optional order by specification, for overriding the natural sort ordering of the groups, see also search() (supported only for many2one fields currently)
  • lazy (bool) -- if true, the results are only grouped by the first groupby and the remaining groupbys are put in the __context key. If false, all the groupbys are done in one call.
Returns

list of dictionaries(one dictionary for each record) containing:

  • the values of fields grouped by the fields in groupby argument
  • __domain: list of tuples specifying the search criteria
  • __context: dictionary with argument like groupby
Return type
[{'field_name_1': value, ...]
Raises AccessError
  • if user has no read rights on the requested object
  • if user tries to bypass access rules for read on the requested object

Searching

search(args[, offset=0][, limit=None][, order=None][, count=False])[source]

Searches for records based on the args search domain.

Parameters
  • args -- A search domain. Use an empty list to match all records.
  • offset (int) -- number of results to ignore (default: none)
  • limit (int) -- maximum number of records to return (default: all)
  • order (str) -- sort string
  • count (bool) -- if True, only counts and returns the number of matching records (default: False)
Returns
at most limit records matching the search criteria
Raises AccessError
  • if user tries to bypass access rules for read on the requested object.
search_count(args) → int[source]

Returns the number of records in the current model matching the provided domain.

Search for records that have a display name matching the given name pattern when compared with the given operator, while also matching the optional search domain (args).

This is used for example to provide suggestions based on a partial value for a relational field. Sometimes be seen as the inverse function of name_get(), but it is not guaranteed to be.

This method is equivalent to calling search() with a search domain based on display_name and then name_get() on the result of the search.

Parameters
  • name (str) -- the name pattern to match
  • args (list) -- optional search domain (see search() for syntax), specifying further restrictions
  • operator (str) -- domain operator for matching name, such as 'like' or '='.
  • limit (int) -- optional max number of records to return
Return type
list
Returns
list of pairs (id, text_repr) for all matching records.

Recordset operations

ids

List of actual record ids in this recordset (ignores placeholder ids for records to create)

ensure_one()[source]

Verifies that the current recorset holds a single record. Raises an exception otherwise.

exists() → records[source]

Returns the subset of records in self that exist, and marks deleted records as such in cache. It can be used as a test on records:

if record.exists():
    ...

By convention, new records are returned as existing.

filtered(func)[source]

Select the records in self such that func(rec) is true, and return them as a recordset.

Parameters
func -- a function or a dot-separated sequence of field names
sorted(key=None, reverse=False)[source]

Return the recordset self ordered by key.

Parameters
  • key -- either a function of one argument that returns a comparison key for each record, or a field name, or None, in which case records are ordered according the default model's order
  • reverse -- if True, return the result in reverse order
mapped(func)[source]

Apply func on all records in self, and return the result as a list or a recordset (if func return recordsets). In the latter case, the order of the returned recordset is arbitrary.

Parameters
func -- a function or a dot-separated sequence of field names (string); any falsy value simply returns the recordset self

Environment swapping

sudo([user=SUPERUSER])[source]

Returns a new version of this recordset attached to the provided user.

By default this returns a SUPERUSER recordset, where access control and record rules are bypassed.

with_context([context][, **overrides]) → records[source]

Returns a new version of this recordset attached to an extended context.

The extended context is either the provided context in which overrides are merged or the current context in which overrides are merged e.g.:

# 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}
with_env(env)[source]

Returns a new version of this recordset attached to the provided environment

Fields and views querying

fields_get([fields][, attributes])[source]

Return the definition of each field.

The returned value is a dictionary (indiced by field name) of dictionaries. The _inherits'd fields are included. The string, help, and selection (if present) attributes are translated.

Parameters
  • allfields -- list of fields to document, all if empty or not provided
  • attributes -- list of description attributes to return for each field, all if empty or not provided
fields_view_get([view_id | view_type='form'])[source]

Get the detailed composition of the requested view like fields, model, view architecture

Parameters
  • view_id -- id of the view or None
  • view_type -- type of the view to return if view_id is None ('form', 'tree', ...)
  • toolbar -- true to include contextual actions
  • submenu -- deprecated
Returns
dictionary describing the composition of the requested view (including inherited views and extensions)
Raises
  • AttributeError --
    • if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
    • if some tag other than 'position' is found in parent view
  • Invalid ArchitectureError -- if there is view type other than form, tree, calendar, search etc defined on the structure

???

default_get(fields) → default_values[source]

Return default values for the fields in fields_list. Default values are determined by the context, user defaults, and the model itself.

Parameters
fields_list -- a list of field names
Returns
a dictionary mapping each field name to its corresponding default value, if it has one.
copy(default=None)[source]

Duplicate record self updating it with default values

Parameters
default (dict) -- dictionary of field values to override in the original values of the copied record, e.g: {'field_name': overridden_value, ...}
Returns
new record
name_get() → [(id, name), ...][source]

Returns a textual representation for the records in self. By default this is the value of the display_name field.

Returns
list of pairs (id, text_repr) for each records
Return type
list(tuple)
name_create(name) → record[source]

Create a new record by calling create() with only one value provided: the display name of the new record.

The new record will be initialized with any default values applicable to this model, or provided through the context. The usual behavior of create() applies.

Parameters
name -- display name of the record to create
Return type
tuple
Returns
the name_get() pair value of the created record

Automatic fields

id

标识符 field

_log_access

是否应该生成日志访问字段(create_datewrite_uid ,...)(默认值: True

create_date

创建记录的日期

Type
Datetime
create_uid

创建记录的用户的关系字段

:type:res.users

write_date

上次修改记录的日期

Type
Datetime
write_uid

修改记录的最后一个用户的关系字段

:type:res.users

保留字段名称

一些字段名称保留用于超出自动字段的预定义行为。 当需要相关行为时,应在模型上定义它们:

name

默认值为 _rec_name ,用于在需要代表性“命名”的上下文中显示记录。

Type
active

切换记录的全局可见性,如果``active``被设置为``False``,记录在大多数搜索和列表中是不可见的

sequence

可更改的排序标准允许在列表视图中拖放模型的重新排序

state

对象的生命周期阶段,由 states 属性使用 fields

parent_id

用于在树结构中对记录进行排序,并在域中启用 child_of 运算符

parent_left

用于 _parent_store ,允许更快的树结构访问

parent_right

see parent_left

装饰器方法

This module provides the elements for managing two different API styles, namely the "traditional" and "record" styles.

In the "traditional" style, parameters like the database cursor, user id, context dictionary and record ids (usually denoted as cr, uid, context, ids) are passed explicitly to all methods. In the "record" style, those parameters are hidden into model instances, which gives it a more object-oriented feel.

For instance, the statements:

model = self.pool.get(MODEL)
ids = model.search(cr, uid, DOMAIN, context=context)
for rec in model.browse(cr, uid, ids, context=context):
    print rec.name
model.write(cr, uid, ids, VALUES, context=context)

may also be written as:

env = Environment(cr, uid, context) # cr, uid, context wrapped in env
model = env[MODEL]                  # retrieve an instance of MODEL
recs = model.search(DOMAIN)         # search returns a recordset
for rec in recs:                    # iterate over the records
    print rec.name
recs.write(VALUES)                  # update all records in recs

Methods written in the "traditional" style are automatically decorated, following some heuristics based on parameter names.

odoo.api.multi(method)[source]

Decorate a record-style method where self is a recordset. The method typically defines an operation on records. Such a method:

@api.multi
def method(self, args):
    ...

may be called in both record and traditional styles, like:

# recs = model.browse(cr, uid, ids, context)
recs.method(args)

model.method(cr, uid, ids, args, context=context)
odoo.api.model(method)[source]

Decorate a record-style method where self is a recordset, but its contents is not relevant, only the model is. Such a method:

@api.model
def method(self, args):
    ...

may be called in both record and traditional styles, like:

# recs = model.browse(cr, uid, ids, context)
recs.method(args)

model.method(cr, uid, args, context=context)

Notice that no ids are passed to the method in the traditional style.

odoo.api.depends(*args)[source]

Return a decorator that specifies the field dependencies of a "compute" method (for new-style function fields). Each argument must be a string that consists in a dot-separated sequence of field names:

pname = fields.Char(compute='_compute_pname')

@api.one
@api.depends('partner_id.name', 'partner_id.is_company')
def _compute_pname(self):
    if self.partner_id.is_company:
        self.pname = (self.partner_id.name or "").upper()
    else:
        self.pname = self.partner_id.name

One may also pass a single function as argument. In that case, the dependencies are given by calling the function with the field's model.

odoo.api.constrains(*args)[source]

Decorates a constraint checker. Each argument must be a field name used in the check:

@api.one
@api.constrains('name', 'description')
def _check_description(self):
    if self.name == self.description:
        raise ValidationError("Fields name and description must be different")

Invoked on the records on which one of the named fields has been modified.

Should raise ValidationError if the validation failed.

odoo.api.onchange(*args)[source]

Return a decorator to decorate an onchange method for given fields. Each argument must be a field name:

@api.onchange('partner_id')
def _onchange_partner(self):
    self.message = "Dear %s" % (self.partner_id.name or "")

In the form views where the field appears, the method will be called when one of the given fields is modified. The method is invoked on a pseudo-record that contains the values present in the form. Field assignments on that record are automatically sent back to the client.

The method may return a dictionary for changing field domains and pop up a warning message, like in the old API:

return {
    'domain': {'other_id': [('partner_id', '=', partner_id)]},
    'warning': {'title': "Warning", 'message': "What is this?"},
}
odoo.api.returns(model, downgrade=None, upgrade=None)[source]

Return a decorator for methods that return instances of model.

Parameters
  • model -- a model name, or 'self' for the current model
  • downgrade -- a function downgrade(self, value, *args, **kwargs) to convert the record-style value to a traditional-style output
  • upgrade -- a function upgrade(self, value, *args, **kwargs) to convert the traditional-style value to a record-style output

The arguments self, *args and **kwargs are the ones passed to the method in the record-style.

The decorator adapts the method output to the api style: id, ids or False for the traditional style, and recordset for the record style:

@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)

Note that the decorated method must satisfy that convention.

Those decorators are automatically inherited: a method that overrides a decorated existing method will be decorated with the same @returns(model).

odoo.api.one(method)[source]

Decorate a record-style method where self is expected to be a singleton instance. The decorated method automatically loops on records, and makes a list with the results. In case the method is decorated with returns(), it concatenates the resulting instances. Such a method:

@api.one
def method(self, args):
    return self.name

may be called in both record and traditional styles, like:

# recs = model.browse(cr, uid, ids, context)
names = recs.method(args)

names = model.method(cr, uid, ids, args, context=context)

Deprecated since version 9.0: one() often makes the code less clear and behaves in ways developers and readers may not expect.

It is strongly recommended to use multi() and either iterate on the self recordset or ensure that the recordset is a single record with ensure_one().

odoo.api.v7(method_v7)[source]

Decorate a method that supports the old-style api only. A new-style api may be provided by redefining a method with the same name and decorated with v8():

@api.v7
def foo(self, cr, uid, ids, context=None):
    ...

@api.v8
def foo(self):
    ...

Special care must be taken if one method calls the other one, because the method may be overridden! In that case, one should call the method from the current class (say MyClass), for instance:

@api.v7
def foo(self, cr, uid, ids, context=None):
    # Beware: records.foo() may call an overriding of foo()
    records = self.browse(cr, uid, ids, context)
    return MyClass.foo(records)

Note that the wrapper method uses the docstring of the first method.

odoo.api.v8(method_v8)[source]

Decorate a method that supports the new-style api only. An old-style api may be provided by redefining a method with the same name and decorated with v7():

@api.v8
def foo(self):
    ...

@api.v7
def foo(self, cr, uid, ids, context=None):
    ...

Note that the wrapper method uses the docstring of the first method.

字段

基础字段

class odoo.fields.Field(string=<object object at 0x68aa320>, **kwargs)[source]

The field descriptor contains the field definition, and manages accesses and assignments of the corresponding field on records. The following attributes may be provided when instanciating a field:

Parameters
  • string -- the label of the field seen by users (string); if not set, the ORM takes the field name in the class (capitalized).
  • help -- the tooltip of the field seen by users (string)
  • readonly -- whether the field is readonly (boolean, by default False)
  • required -- whether the value of the field is required (boolean, by default False)
  • index -- whether the field is indexed in database (boolean, by default False)
  • default -- the default value for the field; this is either a static value, or a function taking a recordset and returning a value; use default=None to discard default values for the field
  • states -- a dictionary mapping state values to lists of UI attribute-value pairs; possible attributes are: 'readonly', 'required', 'invisible'. Note: Any state-based condition requires the state field value to be available on the client-side UI. This is typically done by including it in the relevant views, possibly made invisible if not relevant for the end-user.
  • groups -- comma-separated list of group xml ids (string); this restricts the field access to the users of the given groups only
  • copy (bool) -- whether the field value should be copied when the record is duplicated (default: True for normal fields, False for one2many and computed fields, including property fields and related fields)
  • oldname (string) -- the previous name of this field, so that ORM can rename it automatically at migration

Computed fields

One can define a field whose value is computed instead of simply being read from the database. The attributes that are specific to computed fields are given below. To define such a field, simply provide a value for the attribute compute.

Parameters
  • compute -- name of a method that computes the field
  • inverse -- name of a method that inverses the field (optional)
  • search -- name of a method that implement search on the field (optional)
  • store -- whether the field is stored in database (boolean, by default False on computed fields)
  • compute_sudo -- whether the field should be recomputed as superuser to bypass access rights (boolean, by default False)

The methods given for compute, inverse and search are model methods. Their signature is shown in the following example:

upper = fields.Char(compute='_compute_upper',
                    inverse='_inverse_upper',
                    search='_search_upper')

@api.depends('name')
def _compute_upper(self):
    for rec in self:
        rec.upper = rec.name.upper() if rec.name else False

def _inverse_upper(self):
    for rec in self:
        rec.name = rec.upper.lower() if rec.upper else False

def _search_upper(self, operator, value):
    if operator == 'like':
        operator = 'ilike'
    return [('name', operator, value)]

The compute method has to assign the field on all records of the invoked recordset. The decorator odoo.api.depends() must be applied on the compute method to specify the field dependencies; those dependencies are used to determine when to recompute the field; recomputation is automatic and guarantees cache/database consistency. Note that the same method can be used for several fields, you simply have to assign all the given fields in the method; the method will be invoked once for all those fields.

By default, a computed field is not stored to the database, and is computed on-the-fly. Adding the attribute store=True will store the field's values in the database. The advantage of a stored field is that searching on that field is done by the database itself. The disadvantage is that it requires database updates when the field must be recomputed.

The inverse method, as its name says, does the inverse of the compute method: the invoked records have a value for the field, and you must apply the necessary changes on the field dependencies such that the computation gives the expected value. Note that a computed field without an inverse method is readonly by default.

The search method is invoked when processing domains before doing an actual search on the model. It must return a domain equivalent to the condition: field operator value.

The value of a related field is given by following a sequence of relational fields and reading a field on the reached model. The complete sequence of fields to traverse is specified by the attribute

Parameters
related -- sequence of field names

Some field attributes are automatically copied from the source field if they are not redefined: string, help, readonly, required (only if all fields in the sequence are required), groups, digits, size, translate, sanitize, selection, comodel_name, domain, context. All semantic-free attributes are copied from the source field.

By default, the values of related fields are not stored to the database. Add the attribute store=True to make it stored, just like computed fields. Related fields are automatically recomputed when their dependencies are modified.

Company-dependent fields

Formerly known as 'property' fields, the value of those fields depends on the company. In other words, users that belong to different companies may see different values for the field on a given record.

Parameters
company_dependent -- whether the field is company-dependent (boolean)

Sparse fields

Sparse fields have a very small probability of being not null. Therefore many such fields can be serialized compactly into a common location, the latter being a so-called "serialized" field.

Parameters
sparse -- the name of the field where the value of this field must be stored.

Incremental definition

A field is defined as class attribute on a model class. If the model is extended (see Model), one can also extend the field definition by redefining a field with the same name and same type on the subclass. In that case, the attributes of the field are taken from the parent class and overridden by the ones given in subclasses.

For instance, the second class below only adds a tooltip on the field 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")
class odoo.fields.Char(string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields._String

Basic string field, can be length-limited, usually displayed as a single-line string in clients.

Parameters
  • size (int) -- the maximum size of values stored for that field
  • translate -- enable the translation of the field's values; use translate=True to translate field values as a whole; translate may also be a callable such that translate(callback, value) translates value by using callback(term) to retrieve the translation of terms.
class odoo.fields.Boolean(string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields.Field

class odoo.fields.Integer(string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields.Field

class odoo.fields.Float(string=<object object at 0x68aa320>, digits=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields.Field

The precision digits are given by the attribute

Parameters
digits -- a pair (total, decimal), or a function taking a database cursor and returning a pair (total, decimal)
class odoo.fields.Text(string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields._String

Very similar to Char but used for longer contents, does not have a size and usually displayed as a multiline text box.

Parameters
translate -- enable the translation of the field's values; use translate=True to translate field values as a whole; translate may also be a callable such that translate(callback, value) translates value by using callback(term) to retrieve the translation of terms.
class odoo.fields.Selection(selection=<object object at 0x68aa320>, string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields.Field

Parameters
  • selection -- specifies the possible values for this field. It is given as either a list of pairs (value, string), or a model method, or a method name.
  • selection_add -- provides an extension of the selection in the case of an overridden field. It is a list of pairs (value, string).

The attribute selection is mandatory except in the case of related fields or field extensions.

class odoo.fields.Html(string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields._String

class odoo.fields.Date(string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields.Field

static context_today(record, timestamp=None)[source]

Return the current date as seen in the client's timezone in a format fit for date fields. This method may be used to compute default values.

Parameters
timestamp (datetime) -- optional datetime value to use instead of the current date and time (must be a datetime, regular dates can't be converted between timezones.)
Return type
str
static from_string(value)[source]

Convert an ORM value into a date value.

static to_string(value)[source]

Convert a date value into the format expected by the ORM.

static today(*args)[source]

Return the current day in the format expected by the ORM. This function may be used to compute default values.

class odoo.fields.Datetime(string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields.Field

static context_timestamp(record, timestamp)[source]

Returns the given timestamp converted to the client's timezone. This method is not meant for use as a default initializer, because datetime fields are automatically converted upon display on client side. For default values fields.datetime.now() should be used instead.

Parameters
timestamp (datetime) -- naive datetime value (expressed in UTC) to be converted to the client timezone
Return type
datetime
Returns
timestamp converted to timezone-aware datetime in context timezone
static from_string(value)[source]

Convert an ORM value into a datetime value.

static now(*args)[source]

Return the current day and time in the format expected by the ORM. This function may be used to compute default values.

static to_string(value)[source]

Convert a datetime value into the format expected by the ORM.

关联字段

class odoo.fields.Many2one(comodel_name=<object object at 0x68aa320>, string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields._Relational

The value of such a field is a recordset of size 0 (no record) or 1 (a single record).

Parameters
  • comodel_name -- name of the target model (string)
  • domain -- an optional domain to set on candidate values on the client side (domain or string)
  • context -- an optional context to use on the client side when handling that field (dictionary)
  • ondelete -- what to do when the referred record is deleted; possible values are: 'set null', 'restrict', 'cascade'
  • auto_join -- whether JOINs are generated upon search through that field (boolean, by default False)
  • delegate -- set it to True to make fields of the target model accessible from the current model (corresponds to _inherits)

The attribute comodel_name is mandatory except in the case of related fields or field extensions.

class odoo.fields.One2many(comodel_name=<object object at 0x68aa320>, inverse_name=<object object at 0x68aa320>, string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields._RelationalMulti

One2many field; the value of such a field is the recordset of all the records in comodel_name such that the field inverse_name is equal to the current record.

Parameters
  • comodel_name -- name of the target model (string)
  • inverse_name -- name of the inverse Many2one field in comodel_name (string)
  • domain -- an optional domain to set on candidate values on the client side (domain or string)
  • context -- an optional context to use on the client side when handling that field (dictionary)
  • auto_join -- whether JOINs are generated upon search through that field (boolean, by default False)
  • limit -- optional limit to use upon read (integer)

The attributes comodel_name and inverse_name are mandatory except in the case of related fields or field extensions.

class odoo.fields.Many2many(comodel_name=<object object at 0x68aa320>, relation=<object object at 0x68aa320>, column1=<object object at 0x68aa320>, column2=<object object at 0x68aa320>, string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields._RelationalMulti

Many2many field; the value of such a field is the recordset.

Parameters
comodel_name -- name of the target model (string)

The attribute comodel_name is mandatory except in the case of related fields or field extensions.

Parameters
  • relation -- optional name of the table that stores the relation in the database (string)
  • column1 -- optional name of the column referring to "these" records in the table relation (string)
  • column2 -- optional name of the column referring to "those" records in the table relation (string)

The attributes relation, column1 and column2 are optional. If not given, names are automatically generated from model names, provided model_name and comodel_name are different!

Parameters
  • domain -- an optional domain to set on candidate values on the client side (domain or string)
  • context -- an optional context to use on the client side when handling that field (dictionary)
  • limit -- optional limit to use upon read (integer)
class odoo.fields.Reference(selection=<object object at 0x68aa320>, string=<object object at 0x68aa320>, **kwargs)[source]

Bases: odoo.fields.Selection

继承和扩展

Odoo提供三种不同以模块化方式扩展模型:

  • 从现有模型创建新模型,向副本添加新信息,但保留原始模模型
  • 扩展其他模块中定义的模型,替换以前的版本
  • 将一些模型的字段委托给它包含的记录

原型继承

当同时使用 _inherit_name 属性时, Odoo使用现有的模型作为基础来创建一个新的模型(通过提供 _inherit )。 这个新的模型从基础模型中获取所有的字段,方法和元信息(默认值和al)

并且使用它们:

将会yield:

第二个模型继承了第一个模型的 check 方法和 name 字段,但是覆盖了 call 方法,因为当使用标准 Python inheritance

扩展继承

当使用 _inherit 但是没有提供 _name 时, 新模型替代已存在的模型,本质是在原有模型进行扩展。对于添加新字段或方法是非常有用的,或者去定制或重新配置它们 (例如 改变它们的默认排序):

将会yield:

委托继承

第三种继承机制提供了更多的灵活性(可以改变在运行时)但有较少的功能: 使用 _inherits 模型*委托*查找当前模型中未找到的任何字段 到“儿童”模型。委托通过以下方式执行 Reference 字段在父代上自动设置 模型:

将导致:

并且可以直接在委托字段上写入:

Domain

domain是一个标准列表,每个标准是一个三元组(要么是 list 或是一个 tuple )的 (field_name, operator, value) :

field_name (str)
当前模型的字段名,或者通过 Many2one 的关系遍历, 使用点符号。 'street'partner_id.country'
operator (str)

用于比较 field_namevalue 的操作符。有效的操作符有:

=
相等
!=
不等
>
大于
>=
不小于
<
小于
<=
不大于
=?
未设置或等于(如果 valueNoneFalse ,则返回真,否则表现为 = )
=like
匹配 field_namevalue 模式。 模式中的下划线 “_” 代表任何单个字符(匹配) 百分号“%”匹配任何零个或多个字符的字符串。
like
匹配 field_namevalue 模式。类似于 = like ,但在匹配之前用“%”包装 value
not like
不匹配 %value% 的模式
ilike
等同于不区分大小写的 like
not ilike
等同于不区分大小写的 not like
=ilike
等同于不区分大小写的 =like
in
等于 value 中的任意一项, value 应该是一个列表
not in
不等于 value 中的任意一项
child_of

是一个 value 记录的子节点(子孙)

采用模型的语义(即关系字段命名为 _parent_name )。

value
变量类型,必须是与命名字段可比的(通过 operator

可以使用 prefix 形式的逻辑运算符组合Domain条件

'&'
逻辑运算符 AND ,组合条件的默认操作。 Arity 2(使用接下来的两个标准或组合)。
'|'
逻辑运算符 OR, arity 2.
'!'

逻辑运算符 NOT, arity 1.

从旧API移植到新API

  • 在新API中要避免使用ids的裸列表,使用记录集替代
  • 仍然写在旧API中的方法应该自动桥接ORM,不需要切换到旧的API,只需调用它们就像是一个新的API方法。 有关更多详细信息,请参见 自动桥接旧API方法
  • search() 返回一个记录集。
  • 通过常规的字段类型 related=compute= 参数来替换 fields.relatedfields.function
  • depends()compute = 方法上 必须是存在的 , 它必须列出 所有 计算方法使用的字段和子字段。太多的依赖(在不需要的情况下重新计算字段)比不够(将忘记重新计算字段,然后值将不正确)好
  • 在计算字段上移除 所有 onchange 方法。当它的依赖字段改变时计算字段会自动重新计算,并且用于由客户端自动生成 onchange
  • 装饰器 multi() 用于桥接 当从旧的API上下文 调用时,对于内部或新的api(例如 计算)是无用的
  • 移除 _default, 在对应字段上用 default= 替代
  • 如果一个字段的 string = 是字段名的首字母大写的版本:

    name = fields.Char(string="Name")
    

    那是没有什么作用的,应该被移除

  • multi = 参数在新的API字段上不做任何事情,使用相同的 compute = 方法在所有相关字段上得到相同结果
  • 通过名称(一个字符串)提供 compute =inverse =search = 方法,这使得它们可以重写(不需要中间“跳转”函数)
  • 双重检查所有字段和方法有不同的名称,在碰撞情况下没有警告(因为Python在Odoo看到任何东西之前处理它)
  • 正常的新api导入是 from odoo import fields,models 。 如果兼容性装饰器是必要的,使用 from odoo import api,fields,models
  • 避免使用 one() 装饰器, 它有可能执行你不期望的事
  • 删除显示定义的 create_uid, create_date, write_uidwrite_date 字段: 它们现在被创建为常规的“合法”字段,并且可以 像任何其他字段一样读写
  • 当不可能进行直接转换(语义不能被桥接)或“旧API”版本是不可取的并且可以针对新的API进行改进时, 可以使用完全不同的“旧API”和“新API”实现相同的方法名使用 v7()v8() 。 该方法应该首先使用old-API风格定义,并装饰 v7() ,然后应该使用完全相同的名称重新定义, 但新的API风格和装饰 v8() 。 来自旧API上下文的调用将被分派到首要实现, 并且来自新API上下文的调用将被分派到次要实现。 一个实现可以通过切换上下文来调用(并且频繁地)调用另一个实现。

  • _columns_all_columns 应该被 _fields 所替代, 它提供通过新风格 odoo.fields.Field 对实例进行访问(而不是旧风格 odoo.osv.fields._column

    使用新的API样式创建的非存储计算字段是 可用于 _columns, 只能通过以下方式检查 _fields

  • 在方法中重新分配 self 是非必需的并且有可能破坏翻译自省
  • Environment 对象依赖于一些threadlocal状态,在使用它们之前必须设置它们。有必要使用 odoo.api.Environment.manage() 上下文管理器尝试在尚未设置的新环境中使用新API,

    例如新线程或Python交互式环境:

    >>> from odoo import api, modules
    >>> r = modules.registry.RegistryManager.get('test')
    >>> cr = r.cursor()
    >>> env = api.Environment(cr, 1, {})
    Traceback (most recent call last):
      ...
    AttributeError: environments
    >>> with api.Environment.manage():
    ...     env = api.Environment(cr, 1, {})
    ...     print env['res.partner'].browse(1)
    ...
    res.partner(1,)
    

自动桥接旧API方法

当模型被初始化时,如果它们看起来像在旧API样式中声明的模型,则所有方法都被自动扫描和桥接。 这种桥接使得它们可以从新的API样式方法中透明地调用。

如果被调用的第二个位置参数是 crcursor ,则被匹配为旧API风格的方法。该系统也会辨别 第三个位置参数被称为 uiduser 和 第四个位置参数被称为 idids 。它也会辨别 任意被命名为 context 参数的存在。

当从一个新API上下文调用这样的方法时,系统会自动从当前环境 Environment 调用(crusercontext) 或者当前的记录集 (for id and ids)。

在极少数需要的情况下,桥接可以通过装饰旧式方法来定制:

  • 完全禁用它,通过装饰一个方法 noguess() 这将不会有桥接并且方法将以准确的方式从新和旧的API风格调用
  • 显式地定义桥接,这主要是对于不正确匹配的方法(因为参数以意想不到的方式命名):

    cr()
    将自动地将当前游标添加到显式提供的参数位置
    cr_uid()
    将自动地将当前游标和用户id添加到显式提供的参数位置
    cr_uid_ids()
    将自动地将当前游标、用户id和记录集的ids添加到显式提供的参数位置
    cr_uid_id()
    将循环当前记录集,并为每个记录调用该方法一次,将当前游标,用户id和记录id添加到显式提供的参数位置。
  • 双重实现使用 v7()v8() 将被忽略,因为他们提供自己的“桥接”