第五章 API
Odoo的发展历史伴随着Python2到Python3的发展, 因此也不可避免了留下了历史的痕迹. 从最初的(cr,uid,ids,context) 到 api.one, api.mulit, api.model, 再到如今只剩5种装饰方法, API的历史演变体现了Python2到Python3的进程, 本章我们将了解Odoo中API的前世今生.
8.0之前的API
当年那是Odoo还叫OpenERP的时代,OpenObject对象的方法通常都需要带着几个固定的参数:cr,uid,ids,context等等,写起来很繁琐,比如下面的例子:
def btn_import(self,cr,uid,ids,context=None):
...
到了8.0,可能Odoo官方的开发人员也觉得这样写起来太繁琐了,于是乎,他们引入了新API,封装在api.py文件中,主要有一下几种类型:
- model
- multi
- one
- constrains
- depends
- onchange
- returns
而从13.0开始, 官方再次对API动刀, 这次仅保留了5种装饰方法:
- model
- constrains
- depends
- onchange
- returns
下面我们就这5种类型进行详细的介绍, 关于one和multi的介绍 我们放到附录中, 仅供需要维护旧版本Odoo的同学参考.
model
作用:api.model装饰的方法, 其self只代表模型, 不含有具体的记录值.
典型应用是对象的create方法:
@api.model
def create(self,vals):
return super(Book,self).create(vals)
参数vals可以是一个字典, 也可以是一个字典组成的列表 . Odoo将会根据vals的类型返回单一或者多个记录集.*
create方法不会获取页面中标注为readonly的字段的值, 如果想要保存只读字段的值,请将其force_save属性设置为True。
contrains
作用:给某些字段添加限制条件, 如果是多个字段, 用逗号将其分开. 触发条件: 单声明的字段值发生变化时触发
constrains只接受简单的字段名称, 像对象属性obj.x形式的参数将会被忽略. 另外, 使用constrains限制的字段必须出现在视图中(如果没有出现,可以使用重载create的方法强制实现).
在我们book_store模块中, 我们可以给book对象设置一个名称限制, 限制其名称长度必须在10个字符以内,否则提示错误.
@api.constrains("name")
def _check_name(self):
"""检查名称长度"""
if len(self.name) > 10:
raise ValidationError("图书名称必须限制在10个字符以内")
当输入错误时,便会提示:
constrains的参数也可以是一个函数(当参数是函数时,只能接受一个参数), 该函数返回要限制的字段列表.
def _get_constrains_fields(self):
return ['name', 'date']
@api.constrains(_get_constrains_fields)
def _check_name(self):
"""检查名称长度"""
if len(self.name) > 10:
raise ValidationError("图书名称必须限制在10个字符以内")
if self.date < date(2000,1,1):
raise ValidationError("只能添加2000年以后的图书")
另外一种constrains
odoo支持另外一种添加限制的方式,即通过sql约束的方式。方法是在odoo类对象中添加_sql_constraints属性,值是一个包含了元组的列表,元组的三个值分别是约束名,约束条件和警告信息,看一个例子:
_sql_constraints = [
('name_description_check',
'CHECK(name != description)',
"The title of the course should not be the description"),
('name_unique',
'UNIQUE(name)',
"The course title must be unique"),
]
这样做的好处是,在数据库层面就限制了数据的校验,而不是在代码层面的校验,显然效率会更高。缺点是在添加限制之前,数据库中不能存在违反约束的数据,否则约束会添加失败。
depends
depends主要用于compute方法,v8当中已经取消了function字段,对于任何fields都可以通过添加compute属性动态赋值。depends就是用来标该方法依赖于哪些字段的装饰。
@api.depends('date')
def _get_book_age(self):
self.age = (datetime.now().date() - self.date).days
depends装饰器的参数可以是多个以逗号分割的字段, 也可以是一个返回字段列表的函数. depends装饰器不可用于id字段.
对于compute方法来说,加不加depends装饰的区别在于,加了depends的方法会在依赖的字段发生改变时重新计算本字段的值,而不加depends的方法只在触发的第一次调用,也就是说不会持续更新。
returns
returns的用法主要是用来指定返回值的格式,它接受三个参数,第一个为返回值的model,第二个为向下兼容的method,第三个为向上兼容的method。
第一个参数如果是对象本身,则写'self',如果是其他对象,则写其他对象名如:@api.returns('ir.ui.view')。
例如:
@api.multi
@api.returns('mail.message', lambda value: value.id)
def message_post(self, **kwargs):
if self.env.context.get('mark_so_as_sent'):
self.filtered(lambda o: o.state == 'draft').with_context(tracking_disable=True).write({'state': 'sent'})
self.env.user.company_id.set_onboarding_step_done('sale_onboarding_sample_quotation_state')
return super(SaleOrder, self.with_context(mail_post_autofollow=True)).message_post(**kwargs)
returns主要用于确保新旧API返回值的一致,并不常用。
onchange
onchange的适用场景是当某个字段发生变化时被调用,用来处理需要动态联动的字段。
onchange的参数是发生变化的字段名,例如:
@api.onchange('amount', 'unit_price')
def _onchange_price(self):
self.price = self.amount * self.unit_price
return {
'warning': {
'title': "Something bad happened",
'message': "It was very bad indeed",
}
}
onchange 可以有返回值可以没有返回值。返回值由一个字典组成,可选的值有 value和warning,value用来返回需要设置的字段值,warning用来返回一些警告信息。
@api.onchange('field_name')
def onchange_field_name(self):
value = self.field_name if self.field_name else 'Nothing'
return {'value': {'field_name': value}}
CUID方法介绍
前面介绍了API的多种装饰器及其作用,下面介绍odoo ORM框架的标准CUID方法。所谓CURD即Create\Update\Read\Delete等操作的简称。
Browse
在odoo中如果我们想要读取某个对象的信息,最常用的方法是browse方法,browse方法接收一个参数ID,然后返回该对象对应的对象。
order = self.env['sale.order'].browse(1)
print(order.name)
获取到对象以后,使用点号+属性名的方式即可获取该对象所有属性或者调用对应的方法。
Create
如果我们想要创建一个对象,那么我们就需要使用create方法。create方法接收一个参数vals,其值是包含需要创建的对象的各个字段的值。
order = self.env['sale.order'].create({
"name":"SO0001",
"user_id":1
})
Write
write方法给我们提供了一个修改对象值的方法,接收一个参数vals,其值是包含需要修改字段的值。
order = self.env['sale.order'].browse(1)
order.write({
"name":"SO002"
})
如果只是修改其中的一个字段的值,那么可以直接使用赋值的方法:
order = self.env['sale.order'].browse(1)
order.name = "SO0002"
两种方法的作用是等效的,第二种方法跟面向对象一些,第一种方法的优势在于可以在一行代码中提交多个字段的修改。
Unlink
odoo ORM的删除并没使用delete作为删除方法,而是使用了unlink关键字。
order = self.env['sale.order'].browse(1)
order.unlink()
这里只对CUID的各个方法做一个简单的示例, 关于其内部更多的详情,参见第五部分的模型一章.