第一章 深入理解Odoo的API模型
api.model
api.model与普通的方法有何区别? 这个要从API的历史谈起, odoo还在使用python2的时候, 那个时候定义方法的参数很多, 写一个方法要带很多的参数, 像下面这样:
def funct(self,cr,uid,ids,context):
...
这样写代码真的很麻烦, 于是odoo在8.0时代推出了新的API接口, 使用装饰器的方法推出了下面几种装饰方法:
- model
- multi
- one
- constrains
- depends
- onchange
- returns
api.model也就是在这个时候引进的, 这个时候api.model和api.multi,api.one还是单独分开的, 他们的主要区别也就是api.model返回的记录集self是一个不带有ids的空记录集, 有些类似python中的类方法的意味. 而api.multi返回的是一多行多个记录的记录集, api.one返回的是一个的单一记录.
从13.0开始, 官方又把multi和one一并砍掉, 类中定义的方法默认就是multi式的记录集.因此我们在写不带有装饰的方法时,一般都要遍历记录集 以防止传入的记录集是多个值的时候引起异常.
我们来看api.model的实现原理:
def model(method):
""" 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):
...
"""
if method.__name__ == 'create':
return model_create_single(method)
method._api = 'model'
return method
从这里我们可以看出来, 对于create方法, 如果使用了api.model进行装饰, 那么其内部将使用model_create_single方法来装饰该函数, 否则将使用标准的api.model逻辑. 我们先来看标准的api.model的逻辑是怎样的
api.model的内部实现
我们知道, 所有对象的方法调用都是从call_kw开始的. 那么我们来看看call_wk方法内部是如何处理的.
def call_kw(model, name, args, kwargs):
""" Invoke the given method ``name`` on the recordset ``model``. """
method = getattr(type(model), name)
api = getattr(method, '_api', None)
if api == 'model':
result = _call_kw_model(method, model, args, kwargs)
elif api == 'model_create':
result = _call_kw_model_create(method, model, args, kwargs)
else:
result = _call_kw_multi(method, model, args, kwargs)
model.flush()
return result
这里的逻辑很清晰, 即
odoo在调用模型的方法时, 第一步会检查其是否包含_api属性,如果没有,那么按照多记录集的逻辑进行处理, 如果_api是model装饰的, 那么按照标准的api.model流程进行处理,而如果是model_create装饰的,那么按照创建方法的单例模式处理
由于api.model内部对于create方法做了特殊处理, 实际上api.model和api.model_create_single的效果是一样的.
由上可以知道,内部使用了_call_kw_model来处理api.model的逻辑:
def _call_kw_model(method, self, args, kwargs):
context, args, kwargs = split_context(method, args, kwargs)
recs = self.with_context(context or {})
_logger.debug("call %s.%s(%s)", recs, method.__name__, Params(args, kwargs))
result = method(recs, *args, **kwargs)
return downgrade(method, result, recs, args, kwargs)
其中关键的一句在于:
recs = self.with_context(context or {})
与常规记录集不同的地方在于此, 这里将ids丢弃了, 传入method内部的只是一个模型,而不包含ids. 这样的结果就是 任何调用api.model方法装饰的方法, 其传入的self对象的ids都将会丢失.
api.model对于create方法的处理
如果使用api.model装饰了create方法, 那么它内部将调用model_create_single
def model_create_single(method):
""" Decorate a method that takes a dictionary and creates a single record.
The method may be called with either a single dict or a list of dicts::
record = model.create(vals)
records = model.create([vals, ...])
"""
wrapper = decorate(method, _model_create_single)
wrapper._api = 'model_create'
return wrapper
def _model_create_single(create, self, arg):
# 'create' expects a dict and returns a record
if isinstance(arg, Mapping):
return create(self, arg)
if len(arg) > 1:
_create_logger.debug("%s.create() called with %d dicts", self, len(arg))
return self.browse().concat(*(create(self, vals) for vals in arg))
虽然model_create_single标识了应该使用单一模式, 但实际的代码中还是做多条记录做了兼容处理.同样的, 虽然model_create_multi也标识了用于创建多个记录, 但实际上其内部也对单一的记录做了兼容:
def model_create_multi(method):
""" Decorate a method that takes a list of dictionaries and creates multiple
records. The method may be called with either a single dict or a list of
dicts::
record = model.create(vals)
records = model.create([vals, ...])
"""
wrapper = decorate(method, _model_create_multi)
wrapper._api = 'model_create'
return wrapper
def _model_create_multi(create, self, arg):
# 'create' expects a list of dicts and returns a recordset
if isinstance(arg, Mapping):
return create(self, [arg])
return create(self, arg)
总结下来就是, 虽然odoo内部定义了model_create_single和model_create_multi两个装饰函数, 但实际上都对彼此做了兼容处理. 那么实际应用过程中, 我们在使用api.model标识create方法时 ,vals参数既可以是一个单例的字典 , 也可以是多个字典组成的列表 .
api.constrains
我们知道api.constrains用来给字段设置限制, 当字段发生变化时触发限制条件的验证. 那么api.constrains内部的触发机制是怎样的呢?
首先,我们需要知道, 在create和write方法中, 存在一个_validate_fields方法,该方法用来验证用户输入的数据是否合法. 而对_constrains条件的验证就是在这个方法内部中完成的:
def _validate_fields(self, field_names, excluded_names=()):
""" Invoke the constraint methods for which at least one field name is
in ``field_names`` and none is in ``excluded_names``.
"""
field_names = set(field_names)
excluded_names = set(excluded_names)
for check in self._constraint_methods:
if (not field_names.isdisjoint(check._constrains)
and excluded_names.isdisjoint(check._constrains)):
check(self)
即: 用户在调用create或write方法时会检查模型中的_constraint_methods属性中的方法,而如果页面中发生变化或创建时传入的字段正好在constrains的范围内, 那么就会触发此方法进行验证
而API中的contrains仅做了一件事,就是把字段或者函数挂在到模型实例中:
def constrains(*args):
if args and callable(args[0]):
args = args[0]
return attrsetter('_constrains', args)
api.depends
最佳实践
create方法使用api.model装饰, vals值既可以传单个的字段 , 也可以传入多个字典组成的列表一次性创建多条记录