Skip to content

Commit

Permalink
restore and fix the context for search methods
Browse files Browse the repository at this point in the history
  • Loading branch information
florentx committed Nov 21, 2018
1 parent 37a81e0 commit 5d0d591
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 94 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -9,6 +9,8 @@ Changelog

* New method ``Client.clone_database`` based on ``Client.db.duplicate_database``.

* Use the ``context`` for the ``search`` methods.


1.6.3 (2015-12-30)
~~~~~~~~~~~~~~~~~~
Expand Down
96 changes: 50 additions & 46 deletions erppeek.py
Expand Up @@ -38,6 +38,7 @@
DEFAULT_DB = 'odoo'
DEFAULT_USER = 'admin'
MAXCOL = [79, 179, 9999] # Line length in verbose mode
_DEFAULT = object()

USAGE = """\
Usage (some commands):
Expand Down Expand Up @@ -286,7 +287,7 @@ def start_odoo_services(options=None, appname=None):
try:
odoo._manager_class = odoo.modules.registry.RegistryManager
odoo._get_pool = odoo._manager_class.get
except AttributeError: # Odoo >= v10
except AttributeError: # Odoo >= 10
odoo._manager_class = odoo.modules.registry.Registry
odoo._get_pool = odoo._manager_class

Expand All @@ -313,7 +314,7 @@ def issearchdomain(arg):
(isinstance(arg[0], basestring) and arg[0].isdigit())))


def searchargs(params, kwargs=None, context=None):
def searchargs(params, kwargs=None, context=None, odd=False):
"""Compute the 'search' parameters."""
if not params:
return ([],)
Expand All @@ -332,14 +333,20 @@ def searchargs(params, kwargs=None, context=None):
# Interpret the value as a string
pass
domain[idx] = (field, operator, value)
params = (domain,) + params[1:]
if (kwargs or context) and len(params) == 1:
params = (domain,
kwargs.pop('offset', 0),
kwargs.pop('limit', None),
kwargs.pop('order', None),
context)
else:
params = (domain,) + params[1:]
args = (kwargs.pop('offset', 0),
kwargs.pop('limit', None),
kwargs.pop('order', None),
kwargs.pop('count', False),
context)
if odd:
# The order of the arguments was different with Odoo 9 and older
args = args[:3] + args[4:2:-1]
for idx in range(4, -1, -1):
if args[idx]:
params += args[:idx + 1]
break
return params


Expand Down Expand Up @@ -458,6 +465,7 @@ def get_proxy(name):
self._object = get_proxy('object')
self._report = get_proxy('report')
self._wizard = get_proxy('wizard') if float_version < 7.0 else None
self._searchargs = functools.partial(searchargs, odd=(float_version < 10.0))
self.reset()
self.context = None
if db:
Expand Down Expand Up @@ -701,7 +709,7 @@ def execute(self, obj, method, *params, **kwargs):
assert params
if issearchdomain(params[0]):
# Combine search+read
search_params = searchargs(params[:1], kwargs, context)
search_params = self._searchargs(params[:1], kwargs, context)
ordered = len(search_params) > 3 and search_params[3]
ids = self._execute(obj, 'search', *search_params)
elif isinstance(params[0], list):
Expand All @@ -722,10 +730,10 @@ def execute(self, obj, method, *params, **kwargs):
params = (ids, kwargs.pop('fields', None))
elif method == 'search':
# Accept keyword arguments for the search method
params = searchargs(params, kwargs, context)
params = self._searchargs(params, kwargs, context)
context = None
elif method == 'search_count':
params = searchargs(params)
params = self._searchargs(params)
elif method == 'perm_read':
# broken with a single id (verified with 5.0 and 6.1)
if params and isinstance(params[0], int_types):
Expand Down Expand Up @@ -755,7 +763,7 @@ def exec_workflow(self, obj, signal, obj_id):
assert isinstance(obj, basestring) and isinstance(signal, basestring)
return self._exec_workflow(obj, signal, obj_id)

def wizard(self, name, datas=None, action='init', context=None):
def wizard(self, name, datas=None, action='init', context=_DEFAULT):
"""Wrapper around ``wizard.create`` and ``wizard.execute``
RPC methods.
Expand All @@ -776,7 +784,7 @@ def wizard(self, name, datas=None, action='init', context=None):
if action == 'init' and name != wiz_id:
return wiz_id
datas = {}
if context is None:
if context is _DEFAULT:
context = self.context
return self._wizard_execute(wiz_id, datas, action, context)

Expand Down Expand Up @@ -1094,24 +1102,21 @@ def browse(self, domain, *params, **kwargs):
assert not params and not kwargs
return Record(self, domain, context=context)
if issearchdomain(domain):
params = searchargs((domain,) + params, kwargs, context)
domain = self._execute('search', *params)
# Ignore extra keyword arguments
for item in kwargs.items():
print('Ignoring: %s = %r' % item)
kwargs['context'] = context
domain = self._execute('search', domain, *params, **kwargs)
else:
assert not params and not kwargs
return RecordList(self, domain, context=context)

def get(self, domain, context=None):
def get(self, domain, context=_DEFAULT):
"""Return a single :class:`Record`.
The argument `domain` accepts a single integer ``id`` or a
search domain, or an ``xml_id``. The return value is a
:class:`Record` or None. If multiple records are found,
a ``ValueError`` is raised.
"""
if context is None:
if context is _DEFAULT:
context = self.client.context
if isinstance(domain, int_types): # a single id
return Record(self, domain, context=context)
Expand All @@ -1123,13 +1128,12 @@ def get(self, domain, context=None):
ids = [res['res_id'] for res in data]
else: # a search domain
assert issearchdomain(domain)
params = searchargs((domain,), {}, context)
ids = self._execute('search', *params)
ids = self._execute('search', domain, context=context)
if len(ids) > 1:
raise ValueError('domain matches too many records (%d)' % len(ids))
return Record(self, ids[0], context=context) if ids else None

def create(self, values, context=None):
def create(self, values, context=_DEFAULT):
"""Create a :class:`Record`.
The argument `values` is a dictionary of values which are used to
Expand All @@ -1140,13 +1144,13 @@ def create(self, values, context=None):
The newly created :class:`Record` is returned.
"""
if context is None:
if context is _DEFAULT:
context = self.client.context
values = self._unbrowse_values(values)
new_id = self._execute('create', values, context=context)
return Record(self, new_id, context=context)

def _browse_values(self, values, context=None):
def _browse_values(self, values, context=_DEFAULT):
"""Wrap the values of a Record.
The argument `values` is a dictionary of values read from a Record.
Expand Down Expand Up @@ -1232,13 +1236,13 @@ class RecordList(object):
to assign a single value to all the selected records.
"""

def __init__(self, res_model, ids, context=None):
def __init__(self, res_model, ids, context=_DEFAULT):
idnames = list(ids)
for (index, id_) in enumerate(ids):
if isinstance(id_, (list, tuple)):
ids[index] = id_ = id_[0]
assert isinstance(id_, int_types), repr(id_)
if context is None:
if context is _DEFAULT:
context = res_model.client.context
# Bypass the __setattr__ method
self.__dict__.update({
Expand Down Expand Up @@ -1270,9 +1274,9 @@ def __add__(self, other):
ids = self._idnames + other._idnames
return RecordList(self._model, ids, self._context)

def read(self, fields=None, context=None):
def read(self, fields=None, context=_DEFAULT):
"""Wrapper for :meth:`Record.read` method."""
if context is None:
if context is _DEFAULT:
context = self._context

client = self._model.client
Expand Down Expand Up @@ -1305,21 +1309,21 @@ def read(self, fields=None, context=None):
return records
return values

def write(self, values, context=None):
def write(self, values, context=_DEFAULT):
"""Wrapper for :meth:`Record.write` method."""
if not self.id:
return True
if context is None:
if context is _DEFAULT:
context = self._context
values = self._model._unbrowse_values(values)
rv = self._execute('write', self.id, values, context=context)
return rv

def unlink(self, context=None):
def unlink(self, context=_DEFAULT):
"""Wrapper for :meth:`Record.unlink` method."""
if not self.id:
return True
if context is None:
if context is _DEFAULT:
context = self._context
rv = self._execute('unlink', self.id, context=context)
return rv
Expand Down Expand Up @@ -1382,12 +1386,12 @@ class Record(object):
The attributes are evaluated lazily, and they are cached in the record.
The Record's cache is invalidated if any attribute is changed.
"""
def __init__(self, res_model, res_id, context=None):
def __init__(self, res_model, res_id, context=_DEFAULT):
if isinstance(res_id, (list, tuple)):
(res_id, res_name) = res_id
self.__dict__['_name'] = res_name
assert isinstance(res_id, int_types), repr(res_id)
if context is None:
if context is _DEFAULT:
context = res_model.client.context
# Bypass the __setattr__ method
self.__dict__.update({
Expand Down Expand Up @@ -1440,13 +1444,13 @@ def _update(self, values):
self._cached_keys.update(new_values)
return new_values

def read(self, fields=None, context=None):
def read(self, fields=None, context=_DEFAULT):
"""Read the `fields` of the :class:`Record`.
The argument `fields` accepts different kinds of values.
See :meth:`Client.read` for details.
"""
if context is None:
if context is _DEFAULT:
context = self._context
rv = self._model.read(self.id, fields, context=context)
if isinstance(rv, dict):
Expand All @@ -1455,45 +1459,45 @@ def read(self, fields=None, context=None):
return self._update({fields: rv})[fields]
return rv

def perm_read(self, context=None):
def perm_read(self, context=_DEFAULT):
"""Read the metadata of the :class:`Record`.
Return a dictionary of values.
See :meth:`Client.perm_read` for details.
"""
if context is None:
if context is _DEFAULT:
context = self._context
rv = self._execute('perm_read', [self.id], context=context)
return rv[0] if rv else None

def write(self, values, context=None):
def write(self, values, context=_DEFAULT):
"""Write the `values` in the :class:`Record`.
`values` is a dictionary of values.
See :meth:`Model.create` for details.
"""
if context is None:
if context is _DEFAULT:
context = self._context
values = self._model._unbrowse_values(values)
rv = self._execute('write', [self.id], values, context=context)
self.refresh()
return rv

def unlink(self, context=None):
def unlink(self, context=_DEFAULT):
"""Delete the current :class:`Record` from the database."""
if context is None:
if context is _DEFAULT:
context = self._context
rv = self._execute('unlink', [self.id], context=context)
self.refresh()
return rv

def copy(self, default=None, context=None):
def copy(self, default=None, context=_DEFAULT):
"""Copy a record and return the new :class:`Record`.
The optional argument `default` is a mapping which overrides some
values of the new record.
"""
if context is None:
if context is _DEFAULT:
context = self._context
if default:
default = self._model._unbrowse_values(default)
Expand Down
12 changes: 12 additions & 0 deletions tests/_common.py
Expand Up @@ -31,6 +31,14 @@ def OBJ(*args):
return ('object.execute', sentinel.AUTH) + args


def OBJ_v9(*args):
if len(args) == 8 and args[1] == 'search':
args = args[:6] + args[7:5:-1]
if not args[-1]:
args = args[:-1]
return OBJ(*args)


class XmlRpcTestCase(unittest2.TestCase):
server_version = None
server = None
Expand Down Expand Up @@ -67,6 +75,10 @@ def get_svc(server, name, *args, **kwargs):
svcs.common.login.return_value = self.uid
return svcs

def get_OBJ(self):
self.assertTrue(self.server_version)
return OBJ if (float(self.server_version) >= 10.0) else OBJ_v9

def assertCalls(self, *expected_args):
expected_calls = []
for expected in expected_args:
Expand Down

0 comments on commit 5d0d591

Please sign in to comment.