Published: 2016-08-05

Odoo新式api心得

Table of Contents

1 一些odoo8.0中新式api的使用心得

@api.multi和@api.one的差别在于(参见odoo/openerp/api.py两个装饰器的使用):

当在recordset(s)调用@api.one装饰的方法method时,会迭代此 recordset(s), 对应于迭代后的每个record调用方法method, 这时候方法method的self就指代这个record。 最后方法method返回的结果组装成“列表”返回(当需要使用@api.one装饰的方法返回值时这点要特别注意)

当在recordset(s)调用@api.multi装饰的方法时, 会直接在recordsets(s)上调用method, 这时候方法的self就指代这个 recordset(s), 所以在具体场景下,有可能需要迭代这个self进行处理(因为此时self为一个recordsets)。

@api.model和@api.multi很像,除了在旧式方法调用下(传入cr, uid 等的情况), @api.multi会把传入的第4个参数(self,cr,uid,ids)当成ids, 搜索数据库得到recordsets,然后调用方法,所以self即recordsets。 而@api.model不会,第4个参数会被当成普通的位置参数传入方法。

考虑到@api.one容易错误使用,odoo9.0后不建议使用。可以使用@api.mult配合self.ensureone()来确保self指代单条记录,或者遍历self来进行相关操作(对于需要返回值的情况,需要根据情况相应设计)

odoo客户端并不总是支持@api.one装饰的方法,正如官方文档的提醒:

@api.one is not always supported by the web client, e.g. on button action methods. In that case, you should use @api.multi to decorate your method, and probably call self.ensureone() in the method definition."

相关代码:

def make_wrapper(decorator, method, old_api, new_api):
    """ Return a wrapper method for ``method``. """
    def wrapper(self, *args, **kwargs):
        # avoid hasattr(self, '_ids') because __getattr__() is overridden
        if '_ids' in self.__dict__:
            return new_api(self, *args, **kwargs)
        else:
            return old_api(self, *args, **kwargs)

    # propagate specific openerp attributes from method to wrapper
    for attr in WRAPPED_ATTRS:
        if hasattr(method, attr):
            setattr(wrapper, attr, getattr(method, attr))
    wrapper._api = decorator
    wrapper._orig = method

    return wrapper


def multi(method):
    """ 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)
    """
    split = get_context_split(method)
    downgrade = get_downgrade(method)

    def old_api(self, cr, uid, ids, *args, **kwargs):
        context, args, kwargs = split(args, kwargs)
        recs = self.browse(cr, uid, ids, context)
        result = method(recs, *args, **kwargs)
        return downgrade(recs, result, *args, **kwargs)

    return make_wrapper(multi, method, old_api, method)


def one(method):
    """ 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
        :func:`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:: 9.0

            :func:`~.one` often makes the code less clear and behaves in ways
            developers and readers may not expect.

            It is strongly recommended to use :func:`~.multi` and either
            iterate on the ``self`` recordset or ensure that the recordset
            is a single record with :meth:`~openerp.models.Model.ensure_one`.
    """
    split = get_context_split(method)
    downgrade = get_downgrade(method)
    aggregate = get_aggregate(method)

    def old_api(self, cr, uid, ids, *args, **kwargs):
        context, args, kwargs = split(args, kwargs)
        recs = self.browse(cr, uid, ids, context)
        result = new_api(recs, *args, **kwargs)
        return downgrade(recs, result, *args, **kwargs)

    def new_api(self, *args, **kwargs):
        result = [method(rec, *args, **kwargs) for rec in self]
        return aggregate(self, result)

    return make_wrapper(one, method, old_api, new_api)


def ensure_one(self):
        """ Verifies that the current recorset holds a single record. Raises
        an exception otherwise.
        """
        if len(self) == 1:
            return self
        raise except_orm("ValueError", "Expected singleton: %s" % self)

Author: Nisen

Email: imnisen@163.com