欢迎阅读Odoo 8.0新API指南

概览

记录/记录集(Record/Recordset) 与 模型(Model)

OpenERP/Odoo 8.0版引入了一个新的ORM API。

它的目标是提供更连贯和简洁的语法形式,并保持前后的兼容性。

新的API保留了之前的基础设计,例如 模型(Model)和 记录(Record),但是增加了新的概念,例如 环境(Environment)和 记录集(Recordset)。

之前API的一些内容在这个版本里也没有变化,例如 域(domain)的语法。

模型(Model)

一个模型代表了一个业务对象。

它本质上是一个类,包含各种类有关的功能定义和存储在数据库中的字段。 所有定义在模型中的方法都可以被模型本身直接调用。

现在编程范式有所改变,你不应该直接访问模型,而是应该使用 记录集(RecordSet) 参见 记录集(Recordset)

要实例化一个模型,你必须继承 openerp.model.Model:

from openerp import models, fields, api, _


class MyModel(models.Model):

    _name = 'a.model'  # 模型名称会被用作数据库表名

    firstname = fields.Char(string="Firstname")

继承(Inheritance)

继承机制没有任何变化。你可以使用:n:

class MyModelExtended(Model):
     _inherit = 'a.model'                       # 直接继承
     _inherit = ['a.model', 'a.other.model']    # 直接继承
     _inherits = {'a.model': 'field_name'}      # 多重继承

关于继承更多的细节内容,请参考

字段继承请参考 字段继承

记录集(Recordset)

所有模型的实例同时也是对应记录集的实例。 一个记录集表达了对应模型的经过排序的记录的集合。

你可以在记录集里调用方法:n:

class AModel(Model):
# ...
    def a_fun(self):
        self.do_something() # 这里self是一个记录集,介于 类(class)与 集合(set)之间的混合体
        record_set = self
        record_set.do_something()

    def do_something(self):
        for record in self:
           print record

在这个例子里,方法定义在模型一级,但是当运行这段代码时, self 变量实际上是包含很多记录的一个记录集的实例。

所以传入 do_something 的 self 是一个包含一系列记录的记录集。

如果你用 @api.one 装饰一个方法的话,它会自动遍历当前记录集的记录,然后这时的 self 就是当前这条记录。

如我们在 记录(Record) 中所述,你现在访问的就是一个伪 Active-Record模式。

注解

如果记录集只含有一条记录,你把这个装饰器用在该记录集上会导致中断!! (If you use it on a RecordSet it will break if recordset does not contains only one item.!!)

支持的操作

记录集也支持集合操作,你可以使用 联合、交、补 等运算:n:

record in recset1       # 属于
record not in recset1   # 不属于
recset1 + recset2       # 扩展(extend)
recset1 | recset2       # 联合(union)
recset1 & recset2       # 交集(intersect)
recset1 - recset2       # 差补/相对补集(difference)
recset.copy()           # 记录集的浅复制(被复制对象的所有变量都含有与原来的对象相同的值,而其所有的对其他对象的引用都仍然指向原来的对象)

只有 + 操作符保留集合元素的顺序

记录集也可以被排序:n:

sorted(recordset, key=lambda x: x.column)example

有用的辅助方法

新的API为记录集提供了很多有用的辅助方法。

你可以轻易的过滤已有的记录集:n:

recset.filtered(lambda record: record.company_id == user.company_id)
# 或使用字符串
recset.filtered("product_id.can_be_sold")

你可以对一个记录集排序:n:

# 按名称对记录排序
recset.sorted(key=lambda r: r.name)

你也可以使用 operator 模块

from operator import attrgetter
recset.sorted(key=attrgetter('partner_id', 'name'))

有一个映射记录集的辅助方法:n:

recset.mapped(lambda record: record.price_unit - record.cost_price)

# 返回名称列表
recset.mapped('name')

# 返回合作者记录集
recset.mapped('invoice_id.partner_id')

ids 属性

ids 属性是记录集的一个特殊属性,当记录集包含一个或更多记录时它会返回对应ids。

记录(Record)

一个记录反映了从数据库中取得的“模型记录实例”。它使用缓存和查询生成了数据库记录条目的抽象:

>>> record = self
>>> record.name
toto
>>> record.partner_id.name
partner name

记录的显示名称(display name)

在新API里一个叫显示名称的概念被引入。它使用 name_get 底层方法。

所以如果你希望覆盖显示名称,你需要覆盖 display_name 字段。 示例

如果你希望覆盖显示名称和计算出的相关名称,你需要覆盖 name_get示例

Active Record 模式

在新API引入的新特性之一是对active record模式的基础支撑。你现在可以使用设置属性(setting properties)来写入数据库:n:

record = self
record.name = 'new name'

上面的例子会更新缓存中的值并调用写方法来触发向数据库的写入动作。

Active Record模式 注意事项

使用Active Record模式写值必须要小心,因为每一个值的指定都会触发数据库的写操作:n:

@api.one
def dangerous_write(self):
  self.x = 1
  self.y = 2
  self.z = 4

在这个例子里每一个赋值都会触发写操作。 因为这个方法使用了 @api.one 装饰,对记录集里的每个记录的写操作都会被调用3次,那么如果你的记录集有10条记录,一共会有 10*3 = 30 次写操作。

这在高负载任务里会导致性能问题。你应该这样写:n:

def better_write(self):
   for rec in self:
      rec.write({'x': 1, 'y': 2, 'z': 4})

# 或者

def better_write2(self):
   # 给所有记录赋相同值
   self.write({'x': 1, 'y': 2, 'z': 4})

空查询(Browse_null)链

空关系现在返回一个空的记录集。

在新API里,如果你关联到一个拥有很多空关系的关系(relation),每个关系都会被关联,最后会返回一个空的记录集。

环境(Environment)

新API引入了环境的定义。它的主要目的是提供对于 记录指针(cursor)、用户id(user_id)、模型(model)、上下文(context)、记录集(Recordset)和缓存(cache)的封装。

_images/Diagram1.png

有了这个附加功能,你就不用传递那么多的方法参数了:n:

# 以前
def afun(self, cr, uid, ids, context=None):
    pass

# 现在
def afun(self):
    pass

为了访问到环境,你需要:n:

def afun(self):
     self.env
     # 或者
     model.env

环境应该是不可变的,不能在方法里进行修改,因为它还存储着记录集的缓存等等信息。

修改环境

如果你需要修改当前的上下文,你需要使用 with_context() 方法:n:

self.env['res.partner'].with_context(tz=x).create(vals)

要小心不要使用如下方法修改当前记录集:n:

self = self.env['res.partner'].with_context(tz=x).browse(self.ids)

它会在重新查询后修改当前记录集里的记录,从而导致缓存和记录集之间的不连贯。

改变用户

环境提供了一个切换用户的辅助方法:n:

self.sudo(user.id)
self.sudo()   # 缺省会使用 SUPERUSER_ID
# 或者
self.env['res.partner'].sudo().create(vals)

访问当前用户

self.env.user

使用XML id 获取记录

self.env.ref('base.main_company')

清理环境缓存

在前面我们介绍了环境维护着多种缓存,这些缓存用于模型、字段等类。

有时候你可能必须要使用记录指针(cursor)来直接插入/写数据,这种情况下你需要使这些缓存无效:n:

self.env.invalidate_all()

一般动作(Common Actions)

搜索

搜索并没有太大变化。可惜的是宣称的域(domain)的变动没有在8.0版本里实现。

下面是一些主要的变化。

search_read

search_read 方法加入进来了。它会执行一个 search 并返回一个字典(dict)列表(list)。

这里我们获取所有合作伙伴名称:n:

>>> self.search_read([], ['name'])
[{'id': 3, 'name': u'Administrator'},
 {'id': 7, 'name': u'Agrolait'},
 {'id': 43, 'name': u'Michel Fletcher'},
 ...]

search_count

search_count 方法返回符合搜索域(domain)定义的记录数量:n:

>>> self.search_count([('is_company', '=', True)])
26L

检索

检索是从数据获取记录的标准方法。现在检索会返回一个记录集:n:

>>> self.browse([1, 2, 3])
res.partner(1, 2, 3)

更多关于记录的信息请参考 记录(Record)

写入

使用 Active Record 模式

现在可以用 Active Record 模式来写入:n:

@api.one
def any_write(self):
  self.x = 1
  self.name = 'a'

更多关于Active Record 模式来写入的小窍门,请参考 记录(Record)

传统的写入方式仍然可用。

从记录写入

从记录写入:

@api.one
...
self.write({'key': value })
# 或者
record.write({'key': value})

从记录集写入

从记录集写入:n:

@api.multi
...
self.write({'key': value })
# 它将写入到所有记录里
self.line_ids.write({'key': value })

它将写入到所有关联的线索(line)的记录里。

多对多(Many2many) 一对多(One2many) 写入行为

一对多(One2many) 和 多对多(Many2many)字段有一些特殊行为需要考虑到。 At that time (this may change at release) using create on a multiple relation fields will not introspect to look for the relation.

self.line_ids.create({'name': 'Tho'})   # 这个调用将会失败,因为没有指定订单(order)
self.line_ids.create({'name': 'Tho', 'order_id': self.id})  # 这个调用将会正常执行
self.line_ids.write({'name': 'Tho'})    # 这个调用将会写到所有相关的线索(line)记录里

当在一个装饰了 @api.onchange 的方法里添加新的关联记录时,你可以使用 openerp.models.BaseModel.new() 构造方法。这个方法会创建一个未提交至数据库的记录,包含一个 openerp.models.NewId 类型的id。

self.child_ids += self.new({'key': value})

这种记录在表单保存时会写入数据库。

拷贝

注解

标题得改,目前还是有很多问题!!!

从记录拷贝

从记录拷贝:n:

>>> @api.one
>>> ...
>>>     self.copy()
broken

从记录集拷贝

从记录集拷贝:n:

>>> @api.multi
>>> ...
>>>     self.copy()
broken

创建

创建方法没有变化,除了它现在也是返回一个记录集:n:

self.create({'name': 'New name'})

演习(Dry run)

你可以通过 do_in_draft 这个环境上下文管理器的辅助方法来只在缓存中执行动作。

使用记录指针

记录、记录集和环境共用同一个记录指针。

所以你可以用如下方法来访问记录指针:n:

def my_fun(self):
    cursor = self._cr
    # 或者
    self.env.cr

然后你就可以像以前的API里一样使用记录指针了。

使用线程

使用线程时你必须创建自己的记录指针,并且在每个线程里初始化一个新的环境。 数据库操作提交在提交记录指针时完成:n:

with Environment.manage():  # 类方法
    env = Environment(cr, uid, context)

新 ids

当创建一个包含计算字段的记录或模型时,记录集的记录只在内存里。此时记录的 id 将是一个 openerp.models.NewId 类型的虚拟id。

所以如果你在你的代码里(例如一段SQL查询)用到了记录 id 的话,你应该先检查它是否存在:

if isinstance(current_record.id, models.NewId):
    # 你的代码

字段

现在字段是类的属性:

from openerp import models, fields

class AModel(models.Model):

    _name = 'a_name'

    name = fields.Char(
        string="Name",                   # 字段的可选标签
        compute="_compute_name_custom",  # 在计算字段里转换的字段
        store=True,                      # 如果计算,就保存结果
        select=True,                     # 强制对字段索引
        readonly=True,                   # 在视图中字段将是只读的
        inverse="_write_name"            # 在修改时触发
        required=True,                   # 必需的字段
        translate=True,                  # 启用翻译
        help='blabla',                   # 工具提示帮助文本
        company_dependent=True,          # 把列转换到 ir.property
        search='_search_function'        # 自定义搜索功能,主要和计算一起使用
    )

   # string 关键字不是必需的
   # 缺省情况下使用大写属性名称

   name = fields.Char()  # 合法定义

字段继承

新API的一个新特性就是可以修改字段的一个属性:

name = fields.Char(string='New Value')

字段类型

布尔型(Boolean)

布尔型的字段:

abool = fields.Boolean()

字符型(Char)

存储字符串并包含变量长度:

achar = fields.Char()

特殊选项:

  • size: 数据会根据指定长度尺寸裁剪
  • translate: 字段可以被翻译

文本型(Text)

用于存储长文本:

atext = fields.Text()

特殊选项:

  • translate: 字段可以被翻译

HTML

用于存储 HTML 代码,提供一个html小部件:

anhtml = fields.Html()

特殊选项:

  • translate: 字段可以被翻译

整型(Integer)

存储整数值。不支持 NULL 值,如果未设定值则返回 0:

anint = fields.Integer()

浮点型(Float)

存储浮点值。不支持 NULL 值,如果未设定值则返回 0.0。如果设定了 digits 选项,那么将使用数值(numeric)类型:

afloat = fields.Float()
afloat = fields.Float(digits=(32, 32))
afloat = fields.Float(digits=lambda cr: (32, 32))

特殊选项:

  • digits: 强制使用数据库的数值(numeric)类型。参数可以是一个元组(tuple) (int len, float len) 或者一个使用记录指针(cursor)作为参数的、返回值为一个元组(tuple)的可调用方法

日期型(Date)

存储日期。 这种字段提供一些辅助方法:

  • context_today 返回基于时区(tz)的当日日期字符串
  • today 返回当前系统日期字符串
  • from_string 返回从字符串转换来的 datetime.date() 值
  • to_string 返回从datetime.date来的日期字符串
>>> from openerp import fields

>>> adate = fields.Date()
>>> fields.Date.today()
'2014-06-15'
>>> fields.Date.context_today(self)
'2014-06-15'
>>> fields.Date.context_today(self, timestamp=datetime.datetime.now())
'2014-06-15'
>>> fields.Date.from_string(fields.Date.today())
datetime.datetime(2014, 6, 15, 19, 32, 17)
>>> fields.Date.to_string(datetime.datetime.today())
'2014-06-15'

日期和时间型(DateTime)

存储日期和时间。 这种字段提供一些辅助方法:

  • context_timestamp 返回基于时区(tz)的当日日期时间戳字符串
  • now 返回当前系统日期和时间字符串
  • from_string 返回从字符串转换来的 datetime.datetime() 值
  • to_string 返回从datetime.date来的日期和时间字符串
>>> fields.Datetime.context_timestamp(self, timestamp=datetime.datetime.now())
datetime.datetime(2014, 6, 15, 21, 26, 1, 248354, tzinfo=<DstTzInfo 'Europe/Brussels' CEST+2:00:00 DST>)
>>> fields.Datetime.now()
'2014-06-15 19:26:13'
>>> fields.Datetime.from_string(fields.Datetime.now())
datetime.datetime(2014, 6, 15, 19, 32, 17)
>>> fields.Datetime.to_string(datetime.datetime.now())
'2014-06-15 19:26:13'

二进制型(Binary)

存储以base64编码的文件到字节列:

abin = fields.Binary()

可选型(Selection)

存储文本,但给出一个可选项小部件。 它向数据库引入了非可选约束。(It induces no selection constraint in database.) 可选型必须设置为元组(tuples)列表或者一个返回元组(tuples)列表的可调用方法:

aselection = fields.Selection([('a', 'A')])
aselection = fields.Selection(selection=[('a', 'A')])
aselection = fields.Selection(selection='a_function_name')

特殊选项:

  • selection: 元组(tuples)列表或者一个使用记录集为输入参数的返回元组(tuples)列表的可调用方法名称
  • size: 当使用的索引是整型而非字符串时,必须设定本选项且须设置size=1

在扩展一个模型时,如果你希望向可选型字段添加可能的值,你应该用到 selection_add 关键字参数:

class SomeModel(models.Model):
    _inherits = 'some.model'
    type = fields.Selection(selection_add=[('b', 'B'), ('c', 'C')])

引用型(Reference)

存储到一个模型和一行记录的任意引用:

aref = fields.Reference([('model_name', 'String')])
aref = fields.Reference(selection=[('model_name', 'String')])
aref = fields.Reference(selection='a_function_name')

特殊选项:

  • selection: 元组(tuples)列表或者一个使用记录集为输入参数的返回元组(tuples)列表的可调用方法名称

多对一型(Many2one)

存储模型之间的多对一关联:

arel_id = fields.Many2one('res.users')
arel_id = fields.Many2one(comodel_name='res.users')
an_other_rel_id = fields.Many2one(comodel_name='res.partner', delegate=True)

特殊选项:

  • comodel_name: 对应的模型名称
  • delegate: 为了从当前模型访问目标模型的字段,需要将此选项设为 True (对应于 _inherits

一对多型(One2many)

存储到对应模型的多个记录的关联:

arel_ids = fields.One2many('res.users', 'rel_id')
arel_ids = fields.One2many(comodel_name='res.users', inverse_name='rel_id')

特殊选项:

  • comodel_name: 对应的模型名称
  • inverse_name: 对应的模型的关联字段名称

多对多型(Many2many)

存储到对应模型的多对多个记录的关联:

arel_ids = fields.Many2many('res.users')
arel_ids = fields.Many2many(comodel_name='res.users',
                            relation='table_name',
                            column1='col_name',
                            column2='other_col_name')

特殊选项:

  • comodel_name: 对应的模型名称
  • relation: 关联的表名称
  • columns1: 关联表左字段名称
  • columns2: 关联表右字段名称

名称冲突

注解

字段和方法名称有可能出现冲突。

当你使用字典(dict)类型来调用一个记录时,将强制查询这个名字的字段。

字段缺省值

default 现在是字段的一个关键字,你可以使用值或者方法来为该属性赋值:

name = fields.Char(default='A name')
# 或者
name = fields.Char(default=a_fun)

#...
def a_fun(self):
   return self.do_something()

当使用方法时,你必须在字段定义前定义该方法。

计算字段(Computed Fields)

不再有直接的 fields.function 创建方式。

作为替代,你可以添加一个 compute 关键字。该关键字属性的值就是一个方法名字符串或者一个返回方法名称的方法。 它允许你在类一开始的部分就定义字段:

class AModel(models.Model):
    _name = 'a_name'

    computed_total = fields.Float(compute='compute_total')

    def compute_total(self):
        ...
        self.computed_total = x

这个方法可以是空的。 它应该修改记录属性以便写入到缓存里:

self.name = new_value

要注意这个赋值会触发数据库的写操作。 如果你需要对大量数据进行修改或者必须考虑性能,你应该使用经典方式来写数据库。

为了提供搜索功能到未持久化的计算字段,你必须为该字段添加 search 关键字。该关键字属性的值就是一个方法名字符串或者或者之前定义的一个返回方法名称的方法,这个方法的第2和第3个参数均为域元组(domain tuple),返回一个域(domain)本身(The function takes the second and third member of a domain tuple and returns a domain itself):

def search_total(self, operator, operand):
    ...
    return domain  # e.g. [('id', 'in', ids)]

翻转(Inverse)

翻转 inverse 关键字允许在字段写入或“创建”时触发装饰方法。

多字段(Multi Fields)

一个方法计算多个值:

@api.multi
@api.depends('field.relation', 'an_otherfield.relation')
def _amount(self):
    for x in self:
        x.total = an_algo
        x.untaxed = an_algo

属性字段(Property Field)

在有一些用例里,字段值必须修改到当前公司的依赖。

要启用这种动作,你现在可以使用 company_dependent 选项。

一个值得注意的进展是,在新API里属性字段(property fields)现在是可搜索的。

半成品可拷贝选项(WIP copyable option)

在字段上简单的设定 copy 选项,可以防止重新定义拷贝(There is a dev running that will prevent to redefine copy by simply setting a copy option on fields):

copy=False  # !! WIP to prevent redefine copy

方法与装饰器

新的装饰器只用于新API。 因为网页客户端(webclient)和HTTP控制器与新API不兼容,所以对新API装饰器是强制使用的。

api 命名空间下的装饰器会检查方法的参数名称以确定是否符合旧参数。

已认定的参数名称有:

cr, cursor, uid, user, user_id, id, ids, context

@api.returns

这个装饰器保证返回值的一致性。 它基于原始返回值返回指定模型的一个记录集:

@api.returns('res.partner')
def afun(self):
    ...
    return x  # 一个记录集

如果一个旧API方法调用新API方法,它会自动转换为一个id列表。

所有装饰器都继承自这个装饰器,以升级或降级返回值。

@api.one

这个装饰器自动遍历记录集的记录,self被重新定义为当前记录:

@api.one
def afun(self):
    self.name = 'toto'

注解

注意:返回值被放进一个列表里,这种做法并不是总被网页客户端支持,例如在按钮动作方法里。在那种情况下,你应该用 @api.multi 来装饰你的方法,并且可能需要在方法定义里调用 self.ensure_one()

@api.multi

self为当前记录集,无迭代。 以下是缺省做法:

@api.multi
def afun(self):
    len(self)

@api.model

这个装饰器会把旧API对它装饰的方法的调用转换到新API参数。 它让我们可以优雅的迁移旧代码:

@api.model
def afun(self):
    pass

@api.constrains

这个装饰器确保当进行创建、写入、删除等操作时,被装饰的方法会被调用。 如果一个约束条件被满足,这个方法应该抛出 openerp.exceptions.Warning 并给出相应警告消息。

@api.depends

当这个装饰器指定的任何字段被ORM或表单修改,它所装饰的方法会被触发调用:

@api.depends('name', 'an_other_field')
def afun(self):
    pass

注解

当你重新定义依赖时,你必须重新定义所有的 @api.depends,否则它会丢失监视的字段。

视图管理

新API的一个重大提升是依赖会通过一种简单的方式自动插入表单,你不用再担心修改视图的事情。

@api.onchange

当这个装饰器指定的字段在表单里被修改时,它所装饰的方法会被触发调用:

@api.onchange('fieldx')
def do_stuff(self):
   if self.fieldx == x:
      self.fieldy = 'toto'

例子里面的 self 对应的记录现在被用户在表单里修改了。 在 on_change 上下文里所有的工作都是在缓存里完成。 所以你可以在你的方法里修改记录集而不用担心修改了数据库记录。 这是跟 @api.depends 的主要区别。

在方法返回时,缓存和记录集之间的差异会被返回给表单(At function return, differences between the cache and the RecordSet will be returned to the form.)。

视图管理

新API的一个重大提升是 变动(onchange)会通过一种简单的方式自动插入表单,你不用再担心修改视图的事情。

警告和域(Warning and Domain)

要改变域或发送一个警告,返回正常的字典(dictionary)即可。 要小心这种情况下不要使用 @api.one ,因为它会破坏字典(把它放入一个列表,这个不被网页客户端支持)。

@api.noguess

这个装饰器阻止新API装饰器去改变一个方法的输出。

自省(Introspection)

在OpenERP一个常见模式是使用 _columns 对模型的字段自省。 从8.0版本开始 _columns 已被弃用,而使用 _fields 替代,它包含了使用新、旧API初始化的整理过的字段列表。

约定与代码升级

约定

下划线(Snake_casing)还是 驼峰(CamelCasing)

目前没有定论。 但是现在看起来 OpenERP SA 在9.0版开始使用驼峰命名方式。

导入(Imports)

通过于 Raphaël Collet 的讨论,在8.0 RC1之后将会使用下面的约定

模型(Model)

from openerp import models

字段(Fields)

from openerp import fields

翻译(Translation)

from openerp import _

开发接口(API)

from openerp import api

例外(Exceptions)

from openerp import exceptions

一个标准的模块导入:n:

from openerp import models, fields, api, _

类(Classes)

类应该以如下方式初始化:n:

class Toto(models.Model):
   pass


class Titi(models.TransientModel):
   pass

新的例外类

except_orm 例外已经弃用。 我们应该使用 openerp.exceptions.Warning 及其子类实例

注解

不要与Python内建的警告混合。

重定向警告(RedirectWarning)

警告用户有可能被重定向而不是简单的显示一个警示消息

应该作为参数接收:

  • param int action_id:
     执行重定向的动作的id
  • param string button_text:
     触发重定向的按钮上的文字

禁止访问(AccessDenied)

登录、密码错误。无消息,无追溯。

访问错误(AccessError)

访问权限错误。

缺失错误(MissingError)

记录缺失。

推迟例外(DeferredException)

持有对异步报告的追溯的例外对象。

有一些远程调用(RPC)(创建数据库、生成报告)发生时的初始请求伴随着多个或轮询请求。这个类用来存储在该线程处理初始请求时并然后要发送给一个轮询请求时,发生的可能例外。

注解

追溯让人迷惑,这真的是一个 sys.exc_info() triplet.

兼容性(Compatibility)

当捕捉ORM例外时,我们应该同时捕捉这两种例外:n:

try:
    pass
except (Warning, except_orm) as exc:
    pass

字段(Fields)

字段应该使用新API的字段声明方式。 使用string关键字来作说明比使用一个长长的属性名称要更好:n:

class AClass(models.Model):

    name = fields.Char(string="This is a really long long name")  # ok
    really_long_long_long_name = fields.Char()

属性名称应该有意义,避免使用类似“nb”这样的名字。

缺省 或 计算

compute 选项不应该用于设置缺省值。 缺省值应该只用于属性初始化。

上面的意思是他们可能共用一个方法。

在方法内修改自身

我们永远不要在一个模型方法内修改自身。 这种做法会破坏与当前环境缓存的关联性。

在演习(dry run)中执行

如果你使用环境上下文管理器的 do_in_draft,它将只在缓存中执行而不会提交到数据库。

使用记录指针(Cursor)

使用记录指针时你应该使用当前环境的记录指针:n:

self.env.cr

except if you need to use threads:

with Environment.manage():  # class function
    env = Environment(cr, uid, context)

显示名称

_name_get 已弃用。

你应该定义 display_name 字段,可包含以下选项:

  • compute
  • inverse

约束

在性能允许的情况下,应该使用 @api.constrains 装饰器与 @api.one 装饰器。

Qweb视图 或 非Qweb视图

如果在模型视图里不需要高级的行为,应首先选择标准视图(非Qweb)。

Javascript 和 网站(Website)相关代码

可以在下面链接找到相关指南:

兼容性

在迁移期间,有一些保持基础代码在新旧API上兼容性的模式。

访问旧 API

一般的,用新API你会使用 self,这是一个新的记录集(RecordSet)类的实例。 但是旧API上下文和模型依然可以用:n:

self.pool
self._model

如何优雅的对待旧的基础代码

如果旧的API基础代码必须使用到你的代码,你应该使用以下装饰器:

  • @api.returns 以确保合适的返回值
  • @api.model 以确保新的签名支持旧API调用

单元测试

在common.TransactionCase内或其它类里的单元测试中访问新API:n:

class test_partner_firstname(common.TransactionCase):

    def setUp(self):
        super(test_partner_firstname, self).setUp()
        self.user_model = self.env["res.users"]
        self.partner_model = self.env["res.partner"]

YAML

在Python YAML标签里访问新API:n:

!python {model: account.invoice, id: account_invoice_customer0}: |
    self  # 现在是新API记录
    assert (self.move_id), "Move falsely created at pro-forma"