[FIX] api: improve decorator `returns` to handle special cases, like method `search`

Add the possibility in the decorator to specify the `upgrade` and `downgrade`
functions that convert values between APIs.  Both function have the same API:

    upgrade(self, value, *args, **kwargs)
    downgrade(self, value, *args, **kwargs)

The arguments ``self``, ``*args`` and ``**kwargs`` are the ones passed to the
method, following its new-API signature.

Fixes #4944, #7830.
This commit is contained in:
Raphael Collet 2015-11-24 15:37:48 +01:00
parent 3e03cfe7ac
commit 8e1a5add38
3 changed files with 63 additions and 37 deletions

View File

@ -66,6 +66,15 @@ class TestAPI(common.TransactionCase):
self.assertIsRecordset(partners2, 'res.partner')
self.assertEqual(list(partners1), list(partners2))
@mute_logger('openerp.models')
def test_04_query_count(self):
""" Test the search method with count=True. """
count1 = self.registry('res.partner').search(self.cr, self.uid, [], count=True)
count2 = self.env['res.partner'].search([], count=True)
self.assertIsInstance(count1, (int, long))
self.assertIsInstance(count2, (int, long))
self.assertEqual(count1, count2)
@mute_logger('openerp.models')
def test_05_immutable(self):
""" Check that a recordset remains the same, even after updates. """

View File

@ -209,13 +209,19 @@ def depends(*args):
return lambda method: decorate(method, '_depends', args)
def returns(model, downgrade=None):
def returns(model, downgrade=None, upgrade=None):
""" Return a decorator for methods that return instances of ``model``.
:param model: a model name, or ``'self'`` for the current model
:param downgrade: a function ``downgrade(value)`` to convert the
record-style ``value`` to a traditional-style output
:param downgrade: a function ``downgrade(self, value, *args, **kwargs)``
to convert the record-style ``value`` to a traditional-style output
:param 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::
@ -237,7 +243,7 @@ def returns(model, downgrade=None):
a decorated existing method will be decorated with the same
``@returns(model)``.
"""
return lambda method: decorate(method, '_returns', (model, downgrade))
return lambda method: decorate(method, '_returns', (model, downgrade, upgrade))
def make_wrapper(decorator, method, old_api, new_api):
@ -260,30 +266,39 @@ def make_wrapper(decorator, method, old_api, new_api):
def get_downgrade(method):
""" Return a function `downgrade(value)` that adapts ``value`` from
record-style to traditional-style, following the convention of ``method``.
""" Return a function `downgrade(self, value, *args, **kwargs)` that adapts
``value`` from record-style to traditional-style, following the
convention of ``method``.
"""
spec = getattr(method, '_returns', None)
if spec:
model, downgrade = spec
return downgrade or (lambda value: value.ids)
_, downgrade, _ = spec
if downgrade and len(getargspec(downgrade).args) > 1:
return downgrade
elif downgrade:
return lambda self, *args, **kwargs: downgrade(args[0])
else:
return lambda self, *args, **kwargs: args[0].ids
else:
return lambda value: value
return lambda self, *args, **kwargs: args[0]
def get_upgrade(method):
""" Return a function `upgrade(self, value)` that adapts ``value`` from
traditional-style to record-style, following the convention of ``method``.
""" Return a function `upgrade(self, value, *args, **kwargs)` that adapts
``value`` from traditional-style to record-style, following the
convention of ``method``.
"""
spec = getattr(method, '_returns', None)
if spec:
model, downgrade = spec
if model == 'self':
return lambda self, value: self.browse(value)
model, _, upgrade = spec
if upgrade:
return upgrade
elif model == 'self':
return lambda self, *args, **kwargs: self.browse(args[0])
else:
return lambda self, value: self.env[model].browse(value)
return lambda self, *args, **kwargs: self.env[model].browse(args[0])
else:
return lambda self, value: value
return lambda self, *args, **kwargs: args[0]
def get_aggregate(method):
@ -293,7 +308,7 @@ def get_aggregate(method):
spec = getattr(method, '_returns', None)
if spec:
# value is a list of instances, concatenate them
model, downgrade = spec
model, _, _ = spec
if model == 'self':
return lambda self, value: sum(value, self.browse())
else:
@ -343,7 +358,7 @@ def model(method):
context, args, kwargs = split(args, kwargs)
recs = self.browse(cr, uid, [], context)
result = method(recs, *args, **kwargs)
return downgrade(result)
return downgrade(recs, result, *args, **kwargs)
return make_wrapper(model, method, old_api, method)
@ -370,7 +385,7 @@ def multi(method):
context, args, kwargs = split(args, kwargs)
recs = self.browse(cr, uid, ids, context)
result = method(recs, *args, **kwargs)
return downgrade(result)
return downgrade(recs, result, *args, **kwargs)
return make_wrapper(multi, method, old_api, method)
@ -410,7 +425,7 @@ def one(method):
context, args, kwargs = split(args, kwargs)
recs = self.browse(cr, uid, ids, context)
result = new_api(recs, *args, **kwargs)
return downgrade(result)
return downgrade(recs, result, *args, **kwargs)
def new_api(self, *args, **kwargs):
result = [method(rec, *args, **kwargs) for rec in self]
@ -433,7 +448,7 @@ def cr(method):
def new_api(self, *args, **kwargs):
cr, uid, context = self.env.args
result = method(self._model, cr, *args, **kwargs)
return upgrade(self, result)
return upgrade(self, result, *args, **kwargs)
return make_wrapper(cr, method, method, new_api)
@ -444,9 +459,9 @@ def cr_context(method):
def new_api(self, *args, **kwargs):
cr, uid, context = self.env.args
kwargs['context'] = context
result = method(self._model, cr, *args, **kwargs)
return upgrade(self, result)
old_kwargs = dict(kwargs, context=context)
result = method(self._model, cr, *args, **old_kwargs)
return upgrade(self, result, *args, **kwargs)
return make_wrapper(cr_context, method, method, new_api)
@ -458,7 +473,7 @@ def cr_uid(method):
def new_api(self, *args, **kwargs):
cr, uid, context = self.env.args
result = method(self._model, cr, uid, *args, **kwargs)
return upgrade(self, result)
return upgrade(self, result, *args, **kwargs)
return make_wrapper(cr_uid, method, method, new_api)
@ -477,9 +492,9 @@ def cr_uid_context(method):
def new_api(self, *args, **kwargs):
cr, uid, context = self.env.args
kwargs['context'] = context
result = method(self._model, cr, uid, *args, **kwargs)
return upgrade(self, result)
old_kwargs = dict(kwargs, context=context)
result = method(self._model, cr, uid, *args, **old_kwargs)
return upgrade(self, result, *args, **kwargs)
return make_wrapper(cr_uid_context, method, method, new_api)
@ -494,7 +509,7 @@ def cr_uid_id(method):
def new_api(self, *args, **kwargs):
cr, uid, context = self.env.args
result = [method(self._model, cr, uid, id, *args, **kwargs) for id in self.ids]
return upgrade(self, result)
return upgrade(self, result, *args, **kwargs)
return make_wrapper(cr_uid_id, method, method, new_api)
@ -518,9 +533,9 @@ def cr_uid_id_context(method):
def new_api(self, *args, **kwargs):
cr, uid, context = self.env.args
kwargs['context'] = context
result = [method(self._model, cr, uid, id, *args, **kwargs) for id in self.ids]
return upgrade(self, result)
old_kwargs = dict(kwargs, context=context)
result = [method(self._model, cr, uid, id, *args, **old_kwargs) for id in self.ids]
return upgrade(self, result, *args, **kwargs)
return make_wrapper(cr_uid_id_context, method, method, new_api)
@ -535,7 +550,7 @@ def cr_uid_ids(method):
def new_api(self, *args, **kwargs):
cr, uid, context = self.env.args
result = method(self._model, cr, uid, self.ids, *args, **kwargs)
return upgrade(self, result)
return upgrade(self, result, *args, **kwargs)
return make_wrapper(cr_uid_ids, method, method, new_api)
@ -561,9 +576,9 @@ def cr_uid_ids_context(method):
def new_api(self, *args, **kwargs):
cr, uid, context = self.env.args
kwargs['context'] = context
result = method(self._model, cr, uid, self.ids, *args, **kwargs)
return upgrade(self, result)
old_kwargs = dict(kwargs, context=context)
result = method(self._model, cr, uid, self.ids, *args, **old_kwargs)
return upgrade(self, result, *args, **kwargs)
return make_wrapper(cr_uid_ids_context, method, method, new_api)

View File

@ -1625,7 +1625,9 @@ class BaseModel(object):
return len(res)
return res
@api.returns('self')
@api.returns('self',
upgrade=lambda self, value, args, offset=0, limit=None, order=None, count=False: value if count else self.browse(value),
downgrade=lambda self, value, args, offset=0, limit=None, order=None, count=False: value if count else value.ids)
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
""" search(args[, offset=0][, limit=None][, order=None][, count=False])