Skip to content

Commit

Permalink
Merge branch 'global-context'
Browse files Browse the repository at this point in the history
  • Loading branch information
florentx committed Sep 19, 2014
2 parents d84fd91 + 39233aa commit c62fb40
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 29 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Changelog
1.x (unreleased)
~~~~~~~~~~~~~~~~

* New attribute ``Client.context`` to set the default context for
all RPC calls.

* Remove a duplicate ``Logged in as ...`` line in interactive mode.

* Remove the ``search+name_get`` undocumented feature which has
Expand Down
19 changes: 19 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ list or install Odoo add-ons.

.. automethod:: Client.login

.. attribute:: Client.context

Default context used for all the methods (default ``None``).
In :ref:`interactive mode <interactive-mode>`, this default context
contains the language of the shell environment (variable ``LANG``).
Do not update the context, either copy it or replace it::

# Set language to German
client.context = {'lang': 'de_DE', 'preferred_color': 'blue'}
# ... do something

# Switch to Italian
client.context = dict(client.context, lang='it_IT')
# ... do something

# and AVOID (because it changes the context of existing records)
client.context['lang'] = 'fr_FR'


.. note::

In :ref:`interactive mode <interactive-mode>`, a method
Expand Down
53 changes: 35 additions & 18 deletions erppeek.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ def get_proxy(name):
self._report = get_proxy('report')
self._wizard = get_proxy('wizard') if major_version < '7.0' else None
self.reset()
self.context = None
if db:
# Try to login
self.login(user, password=password, database=db)
Expand Down Expand Up @@ -570,6 +571,10 @@ def connect(self, env=None):
return
client = self
env = client._environment or client._db
try: # copy the context to the new client
client.context = dict(global_vars['client'].context)
except (KeyError, TypeError):
pass # client not yet in globals(), or context is None
global_vars['client'] = client
if hasattr(client._server, 'modules'):
global_vars['get_pool'] = get_pool
Expand Down Expand Up @@ -634,6 +639,8 @@ def execute(self, obj, method, *params, **kwargs):
assert isinstance(obj, basestring)
assert isinstance(method, basestring) and method != 'browse'
context = kwargs.pop('context', None)
if context is None:
context = self.context
ordered = single_id = False
if method == 'read':
assert params
Expand Down Expand Up @@ -714,6 +721,8 @@ 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:
context = self.context
return self._wizard_execute(wiz_id, datas, action, context)

def _upgrade(self, modules, button):
Expand Down Expand Up @@ -1059,6 +1068,8 @@ def create(self, values, context=None):
The newly created :class:`Record` is returned.
"""
if context is None:
context = self.client.context
values = self._unbrowse_values(values)
new_id = self._execute('create', values, context=context)
return Record(self, new_id, context=context)
Expand Down Expand Up @@ -1086,7 +1097,7 @@ def _browse_values(self, values, context=None):
elif value and field_type == 'reference':
(res_model, res_id) = value.split(',')
rel_model = self.client.model(res_model, False)
values[key] = Record(rel_model, int(res_id))
values[key] = Record(rel_model, int(res_id), context=context)
return values

def _unbrowse_values(self, values):
Expand Down Expand Up @@ -1158,6 +1169,8 @@ def __init__(self, res_model, ids, context=None):
if isinstance(id_, (list, tuple)):
ids[index] = id_ = id_[0]
assert isinstance(id_, int_types), repr(id_)
if context is None:
context = res_model.client.context
# Bypass the __setattr__ method
self.__dict__.update({
'id': ids,
Expand Down Expand Up @@ -1190,7 +1203,7 @@ def __add__(self, other):

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

client = self._model.client
Expand All @@ -1199,7 +1212,7 @@ def read(self, fields=None, context=None):
fields, order=True, context=context)
if is_list_of_dict(values):
browse_values = self._model._browse_values
return [v and browse_values(v) for v in values]
return [v and browse_values(v, context) for v in values]
else:
values = []

Expand All @@ -1211,14 +1224,14 @@ def read(self, fields=None, context=None):
return RecordList(rel_model, values, context=context)
if field['type'] in ('one2many', 'many2many'):
rel_model = client.model(field['relation'], False)
return [RecordList(rel_model, v) for v in values]
return [RecordList(rel_model, v, context) for v in values]
if field['type'] == 'reference':
records = []
for value in values:
if value:
(res_model, res_id) = value.split(',')
rel_model = client.model(res_model, False)
value = Record(rel_model, int(res_id))
value = Record(rel_model, int(res_id), context)
records.append(value)
return records
return values
Expand All @@ -1227,7 +1240,7 @@ def write(self, values, context=None):
"""Wrapper for :meth:`Record.write` method."""
if not self.id:
return True
if context is None and self._context:
if context is None:
context = self._context
values = self._model._unbrowse_values(values)
rv = self._execute('write', self.id, values, context=context)
Expand All @@ -1237,7 +1250,7 @@ def unlink(self, context=None):
"""Wrapper for :meth:`Record.unlink` method."""
if not self.id:
return True
if context is None and self._context:
if context is None:
context = self._context
rv = self._execute('unlink', self.id, context=context)
return rv
Expand Down Expand Up @@ -1271,8 +1284,8 @@ def __getattr__(self, attr):

def wrapper(self, *params, **kwargs):
"""Wrapper for client.execute(%r, %r, [...], *params, **kwargs)."""
if context:
kwargs.setdefault('context', context)
if context is not None and 'context' not in kwargs:
kwargs['context'] = context
return self._execute(attr, self.id, *params, **kwargs)
wrapper.__name__ = attr
wrapper.__doc__ %= (self._model_name, attr)
Expand Down Expand Up @@ -1304,6 +1317,8 @@ def __init__(self, res_model, res_id, context=None):
(res_id, res_name) = res_id
self.__dict__['_name'] = res_name
assert isinstance(res_id, int_types), repr(res_id)
if context is None:
context = res_model.client.context
# Bypass the __setattr__ method
self.__dict__.update({
'id': res_id,
Expand Down Expand Up @@ -1356,7 +1371,7 @@ def read(self, fields=None, context=None):
The argument `fields` accepts different kinds of values.
See :meth:`Client.read` for details.
"""
if context is None and self._context:
if context is None:
context = self._context
rv = self._model.read(self.id, fields, context=context)
if isinstance(rv, dict):
Expand All @@ -1371,6 +1386,8 @@ def perm_read(self, context=None):
Return a dictionary of values.
See :meth:`Client.perm_read` for details.
"""
if context is None:
context = self._context
rv = self._execute('perm_read', [self.id], context=context)
return rv[0] if rv else None

Expand All @@ -1380,7 +1397,7 @@ def write(self, values, context=None):
`values` is a dictionary of values.
See :meth:`Model.create` for details.
"""
if context is None and self._context:
if context is None:
context = self._context
values = self._model._unbrowse_values(values)
rv = self._execute('write', [self.id], values, context=context)
Expand All @@ -1389,7 +1406,7 @@ def write(self, values, context=None):

def unlink(self, context=None):
"""Delete the current :class:`Record` from the database."""
if context is None and self._context:
if context is None:
context = self._context
rv = self._execute('unlink', [self.id], context=context)
self.refresh()
Expand All @@ -1401,12 +1418,12 @@ def copy(self, default=None, context=None):
The optional argument `default` is a mapping which overrides some
values of the new record.
"""
if context is None and self._context:
if context is None:
context = self._context
if default:
default = self._model._unbrowse_values(default)
new_id = self._execute('copy', self.id, default, context=context)
return Record(self._model, new_id)
return Record(self._model, new_id, context=context)

def _send(self, signal):
"""Trigger workflow `signal` for this :class:`Record`."""
Expand Down Expand Up @@ -1453,8 +1470,8 @@ def __getattr__(self, attr):

def wrapper(self, *params, **kwargs):
"""Wrapper for client.execute(%r, %r, %d, *params, **kwargs)."""
if context:
kwargs.setdefault('context', context)
if context is not None and 'context' not in kwargs:
kwargs['context'] = context
res = self._execute(attr, [self.id], *params, **kwargs)
self.refresh()
if isinstance(res, list) and len(res) == 1:
Expand Down Expand Up @@ -1592,10 +1609,10 @@ def main():
args.server = ['-c', args.config] if args.config else DEFAULT_URL
client = Client(args.server, args.db, args.user, args.password,
verbose=args.verbose)
client.context = {'lang': (os.getenv('LANG') or 'en_US').split('.')[0]}

if args.model and domain and client.user:
context = {'lang': (os.environ.get('LANG') or 'en_US').split('.')[0]}
data = client.execute(args.model, 'read', domain, args.fields, context)
data = client.execute(args.model, 'read', domain, args.fields)
if not args.fields:
args.fields = ['id']
if data:
Expand Down
7 changes: 5 additions & 2 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def get_proxy(name, methods=None):
self.assertCalls(call('login', ('aaa',)), 'call().__str__')
self.assertOutput('')

def test_service_openerp_client(self, server_version='6.1'):
def test_service_openerp_client(self, server_version='8.0'):
server = 'http://127.0.0.1:8069'
self.service.side_effect = [server_version, ['newdb'], 1]
client = erppeek.Client(server, 'newdb', 'usr', 'pss')
Expand All @@ -67,7 +67,7 @@ def test_service_openerp_client(self, server_version='6.1'):
self.assertIsInstance(client.common, erppeek.Service)
self.assertIsInstance(client._object, erppeek.Service)
self.assertIsInstance(client._report, erppeek.Service)
if server_version == '7.0':
if server_version >= '7.0':
self.assertNotIsInstance(client._wizard, erppeek.Service)
else:
self.assertIsInstance(client._wizard, erppeek.Service)
Expand All @@ -88,6 +88,9 @@ def test_service_openerp_client(self, server_version='6.1'):
def test_service_openerp_50(self):
self.test_service_openerp_client(server_version='5.0')

def test_service_openerp_61(self):
self.test_service_openerp_client(server_version='6.1')

def test_service_openerp_70(self):
self.test_service_openerp_client(server_version='7.0')

Expand Down
12 changes: 8 additions & 4 deletions tests/test_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def test_no_database(self):
def test_invalid_user_password(self):
env_tuple = ('http://127.0.0.1:8069', 'database', 'usr', 'passwd')
mock.patch('sys.argv', new=['erppeek', '--env', 'demo']).start()
mock.patch('os.environ', new={'LANG': 'fr_FR.UTF-8'}).start()
mock.patch('erppeek.read_config', return_value=env_tuple).start()
mock.patch('getpass.getpass', return_value='x').start()
self.service.db.list.return_value = ['database']
Expand All @@ -119,14 +120,17 @@ def test_invalid_user_password(self):
erppeek.main()

usr17 = ('object.execute', 'database', 17, 'passwd')
cx = {'lang': 'fr_FR'}
expected_calls = self.startup_calls + (
('common.login', 'database', 'usr', 'passwd'),
usr17 + ('ir.model', 'search', [('model', 'like', 'res.company')]),
usr17 + ('ir.model', 'read', 42, ('model',)),
usr17 + ('ir.model', 'search',
[('model', 'like', 'res.company')], 0, None, None, cx),
usr17 + ('ir.model', 'read', 42, ('model',), cx),
usr17 + ('ir.model.access', 'check', 'res.users', 'write'),
('common.login', 'database', 'gaspard', 'x'),
usr17 + ('ir.model', 'search', [('model', 'like', 'res.company')]),
usr17 + ('ir.model', 'read', 42, ('model',)),
usr17 + ('ir.model', 'search',
[('model', 'like', 'res.company')], 0, None, None, cx),
usr17 + ('ir.model', 'read', 42, ('model',), cx),
)
self.assertCalls(*expected_calls)
outlines = self.stdout.popvalue().splitlines()
Expand Down
19 changes: 14 additions & 5 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,22 +356,31 @@ def test_get(self):
self.assertIsInstance(FooBar.get(['name = Morice']), erppeek.Record)
self.assertIsNone(FooBar.get(['name = Blinky', 'missing = False']))

# domain matches too many records (2)
self.assertRaises(ValueError, FooBar.get, ['name like Morice'])

# set default context
ctx = {'lang': 'en_GB', 'location': 'somewhere'}
self.client.context = dict(ctx)

# with context
value = FooBar.get(['name = Morice'], context={'lang': 'fr_FR'})
self.assertEqual(type(value), erppeek.Record)
self.assertIsInstance(value.name, str)

# domain matches too many records (2)
self.assertRaises(ValueError, FooBar.get, ['name like Morice'])
# with default context
value = FooBar.get(['name = Morice'])
self.assertEqual(type(value), erppeek.Record)

self.assertCalls(
OBJ('foo.bar', 'search', [('name', '=', 'Morice')]),
OBJ('foo.bar', 'search', [('name', '=', 'Blinky'), ('missing', '=', False)]),
OBJ('foo.bar', 'search', [('name', 'like', 'Morice')]),
OBJ('foo.bar', 'search', [('name', '=', 'Morice')], 0, None, None, {'lang': 'fr_FR'}),
OBJ('foo.bar', 'fields_get_keys'),
OBJ('foo.bar', 'fields_get_keys', ctx),
OBJ('foo.bar', 'read', [1003], ['name'], {'lang': 'fr_FR'}),
OBJ('foo.bar', 'fields_get'),
OBJ('foo.bar', 'search', [('name', 'like', 'Morice')]),
OBJ('foo.bar', 'fields_get', ctx),
OBJ('foo.bar', 'search', [('name', '=', 'Morice')], 0, None, None, ctx),
)
self.assertOutput('')

Expand Down

0 comments on commit c62fb40

Please sign in to comment.