diff --git a/tryton/proteus/proteus/__init__.py b/proteus/proteus/__init__.py index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_cHJvdGV1cy9wcm90ZXVzL19faW5pdF9fLnB5..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_cHJvdGV1cy9wcm90ZXVzL19faW5pdF9fLnB5 100644 --- a/tryton/proteus/proteus/__init__.py +++ b/tryton/proteus/proteus/__init__.py @@ -1152,9 +1152,8 @@ values.update(self._on_change_args(on_change)) if values: context = self._context - changes = getattr(self._proxy, 'on_change')(values, names, context) - for change in changes: - self._set_on_change(change) + change = getattr(self._proxy, 'on_change')(values, names, context) + self._set_on_change(change) values = {} fieldnames = set(names) diff --git a/tryton/sao/src/model.js b/sao/src/model.js index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_c2FvL3NyYy9tb2RlbC5qcw==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_c2FvL3NyYy9tb2RlbC5qcw== 100644 --- a/tryton/sao/src/model.js +++ b/tryton/sao/src/model.js @@ -1071,5 +1071,5 @@ [values], this.get_context(), false)); } } else { - changes = this.model.execute( + changes = [this.model.execute( 'on_change', @@ -1075,5 +1075,5 @@ 'on_change', - [values, fieldnames], this.get_context(), false); + [values, fieldnames], this.get_context(), false)]; } } catch (e) { return; @@ -1129,9 +1129,9 @@ delete this._values[fieldname + '.']; } } - var result; + var changed; fieldnames = Object.keys(fieldnames); if (fieldnames.length) { try { if ((fieldnames.length == 1) || (values.id === undefined)) { @@ -1133,7 +1133,7 @@ fieldnames = Object.keys(fieldnames); if (fieldnames.length) { try { if ((fieldnames.length == 1) || (values.id === undefined)) { - result = {}; + changed = {}; for (const fieldname of fieldnames) { @@ -1139,6 +1139,8 @@ for (const fieldname of fieldnames) { - result[fieldname] = this.model.execute( - 'on_change_with_' + fieldname, - [values], this.get_context(), false); + changed = jQuery.extend( + changed, + this.model.execute( + 'on_change_with_' + fieldname, + [values], this.get_context(), false)); } } else { @@ -1143,9 +1145,9 @@ } } else { - result = this.model.execute( + changed = this.model.execute( 'on_change_with', [values, fieldnames], this.get_context(), false); } } catch (e) { return; } @@ -1146,10 +1148,10 @@ 'on_change_with', [values, fieldnames], this.get_context(), false); } } catch (e) { return; } - this.set_on_change(result); + this.set_on_change(changed); } if (!jQuery.isEmptyObject(later)) { values = {}; @@ -1164,5 +1166,5 @@ try { if ((fieldnames.length == 1) || (values.id === undefined)) { - result = {}; + changed = {}; for (const fieldname of fieldnames) { @@ -1168,6 +1170,8 @@ for (const fieldname of fieldnames) { - result[fieldname] = this.model.execute( - 'on_change_with_' + fieldname, - [values], this.get_context(), false); + changed = jQuery.extend( + changed, + this.model.execute( + 'on_change_with_' + fieldname, + [values], this.get_context(), false)); } } else { @@ -1172,9 +1176,9 @@ } } else { - result = this.model.execute( + changed = this.model.execute( 'on_change_with', [values, fieldnames], this.get_context(), false); } } catch (e) { return; } @@ -1175,10 +1179,10 @@ 'on_change_with', [values, fieldnames], this.get_context(), false); } } catch (e) { return; } - this.set_on_change(result); + this.set_on_change(changed); } }, set_on_change: function(values) { diff --git a/tryton/tryton/tryton/gui/window/view_form/model/record.py b/tryton/tryton/gui/window/view_form/model/record.py index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uL3RyeXRvbi9ndWkvd2luZG93L3ZpZXdfZm9ybS9tb2RlbC9yZWNvcmQucHk=..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uL3RyeXRvbi9ndWkvd2luZG93L3ZpZXdfZm9ybS9tb2RlbC9yZWNvcmQucHk= 100644 --- a/tryton/tryton/tryton/gui/window/view_form/model/record.py +++ b/tryton/tryton/tryton/gui/window/view_form/model/record.py @@ -577,9 +577,9 @@ 'on_change_' + fieldname, values, context=self.get_context())) else: - changes = RPCExecute( - 'model', self.model_name, 'on_change', - values, fieldnames, context=self.get_context()) + changes = [RPCExecute( + 'model', self.model_name, 'on_change', + values, fieldnames, context=self.get_context())] except RPCException: pass else: @@ -621,5 +621,5 @@ if fieldnames: try: if len(fieldnames) == 1 or 'id' not in values: - result = {} + changed = {} for fieldname in fieldnames: @@ -625,6 +625,6 @@ for fieldname in fieldnames: - result[fieldname] = RPCExecute( - 'model', self.model_name, - 'on_change_with_' + fieldname, - values, context=self.get_context()) + changed.update(RPCExecute( + 'model', self.model_name, + 'on_change_with_' + fieldname, + values, context=self.get_context())) else: @@ -630,6 +630,6 @@ else: - result = RPCExecute( + changed = RPCExecute( 'model', self.model_name, 'on_change_with', values, list(fieldnames), context=self.get_context()) except RPCException: return @@ -632,8 +632,8 @@ 'model', self.model_name, 'on_change_with', values, list(fieldnames), context=self.get_context()) except RPCException: return - self.set_on_change(result) + self.set_on_change(changed) if later: values = {} for fieldname in later: @@ -642,5 +642,5 @@ values.update(self._get_on_change_args(on_change_with)) try: if len(later) == 1 or 'id' not in values: - result = {} + changed = {} for fieldname in fieldnames: @@ -646,6 +646,6 @@ for fieldname in fieldnames: - result[fieldname] = RPCExecute( - 'model', self.model_name, - 'on_change_with_' + fieldname, - values, context=self.get_context()) + changed.update(RPCExecute( + 'model', self.model_name, + 'on_change_with_' + fieldname, + values, context=self.get_context())) else: @@ -651,6 +651,6 @@ else: - result = RPCExecute( + changed = RPCExecute( 'model', self.model_name, 'on_change_with', values, list(later), context=self.get_context()) except RPCException: return @@ -653,8 +653,8 @@ 'model', self.model_name, 'on_change_with', values, list(later), context=self.get_context()) except RPCException: return - self.set_on_change(result) + self.set_on_change(changed) def autocomplete_with(self, field_name): for fieldname, fieldinfo in self.group.fields.items(): diff --git a/tryton/trytond/doc/ref/rpc.rst b/trytond/doc/ref/rpc.rst index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC9kb2MvcmVmL3JwYy5yc3Q=..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC9kb2MvcmVmL3JwYy5yc3Q= 100644 --- a/tryton/trytond/doc/ref/rpc.rst +++ b/tryton/trytond/doc/ref/rpc.rst @@ -4,7 +4,7 @@ RPC === -.. class:: RPC([readonly[, instantiate[, result[, check_access[, unique[, fresh_session[, cache]]]]]]]) +.. class:: RPC([readonly[, instantiate[, [decorator, result[, check_access[, unique[, fresh_session[, cache]]]]]]]]) Define the behavior of Remote Procedure Call. @@ -18,6 +18,10 @@ The position or the slice of the argument to be instanciated +.. attribute:: RPC.decorator + + The function to decorate the called procedure with + .. attribute:: RPC.result The function to transform the result diff --git a/tryton/trytond/trytond/model/fields/field.py b/trytond/trytond/model/fields/field.py index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL21vZGVsL2ZpZWxkcy9maWVsZC5weQ==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL21vZGVsL2ZpZWxkcy9maWVsZC5weQ== 100644 --- a/tryton/trytond/trytond/model/fields/field.py +++ b/tryton/trytond/trytond/model/fields/field.py @@ -1,7 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import warnings -from functools import partial, wraps +from functools import wraps import sql from sql import ( @@ -195,18 +195,15 @@ return record._changed_values -def on_change_with_result(field, value): - from ..modelstorage import ModelStorage - if field._type in {'many2one', 'one2one', 'reference'}: - if isinstance(value, ModelStorage): - if field._type == 'reference': - value = str(value) - else: - value = value.id - elif field._type in {'one2many', 'many2many'}: - if isinstance(value, (list, tuple)): - value = [int(r) for r in value] - return value +def on_change_with_result(fieldname): + def decorator(func): + @wraps(func) + def wrapper(self, *args, **kwargs): + value = func(self, *args, **kwargs) + setattr(self, fieldname, value) + return self._changed_values + return wrapper + return decorator def domain_method(func): @@ -476,9 +473,9 @@ return [self.sql_column(table)] def set_rpc(self, model): - for attribute, result in ( - ('on_change', on_change_result), - ('on_change_with', partial(on_change_with_result, self)), + for attribute, decorator, result in ( + ('on_change', None, on_change_result), + ('on_change_with', on_change_with_result(self.name), None), ): if not getattr(self, attribute): continue @@ -486,7 +483,8 @@ assert hasattr(model, func_name), \ 'Missing %s on model %s' % (func_name, model.__name__) model.__rpc__.setdefault( - func_name, RPC(instantiate=0, result=result)) + func_name, + RPC(instantiate=0, decorator=decorator, result=result)) def definition(self, model, language): pool = Pool() diff --git a/tryton/trytond/trytond/model/fields/function.py b/trytond/trytond/model/fields/function.py index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL21vZGVsL2ZpZWxkcy9mdW5jdGlvbi5weQ==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL21vZGVsL2ZpZWxkcy9mdW5jdGlvbi5weQ== 100644 --- a/tryton/trytond/trytond/model/fields/function.py +++ b/tryton/trytond/trytond/model/fields/function.py @@ -9,7 +9,7 @@ from trytond.tools import is_instance_method from trytond.transaction import Transaction, without_check_access -from .field import Field, domain_method, on_change_with_result +from .field import Field, domain_method def getter_context(func): @@ -124,5 +124,16 @@ def call(name): if not instance_method: - return on_change_with_result(self, method(records, name)) + values = method(records, name) + if isinstance(name, str): + return convert_dict(values) + else: + return {k: convert_dict(v, k) for k, v in values.items()} + else: + return {r.id: convert(method(r, name)) for r in records} + + def convert(value, name=None): + from ..model import Model as BaseModel + if name: + field = Model._fields[name]._field else: @@ -128,7 +139,22 @@ else: - return { - r.id: on_change_with_result(self, method(r, name)) - for r in records} + field = self._field + if field._type in {'many2one', 'one2one', 'reference'}: + if isinstance(value, BaseModel): + if field._type == 'reference': + value = str(value) + else: + value = int(value) + elif field._type in {'one2many', 'many2many'}: + if value: + value = [int(r) for r in value] + return value + + def convert_dict(values, name=None): + # Keep the same class + values = values.copy() + values.update((k, convert(v, name)) for k, v in values.items()) + return values + if isinstance(name, list): names = name if multiple: diff --git a/tryton/trytond/trytond/model/modelview.py b/trytond/trytond/model/modelview.py index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL21vZGVsL21vZGVsdmlldy5weQ==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL21vZGVsL21vZGVsdmlldy5weQ== 100644 --- a/tryton/trytond/trytond/model/modelview.py +++ b/tryton/trytond/trytond/model/modelview.py @@ -68,8 +68,9 @@ super(ModelView, cls).__setup__() cls.__rpc__['fields_view_get'] = RPC(cache=dict(days=1)) cls.__rpc__['view_toolbar_get'] = RPC(cache=dict(days=1)) - cls.__rpc__['on_change'] = RPC(instantiate=0) - cls.__rpc__['on_change_with'] = RPC(instantiate=0) + cls.__rpc__['on_change'] = RPC(instantiate=0, result=on_change_result) + cls.__rpc__['on_change_with'] = RPC( + instantiate=0, result=on_change_result) cls.__rpc__['on_change_notify'] = RPC(instantiate=0) cls._buttons = {} @@ -758,8 +759,16 @@ return func return decorator + @on_change + def on_change_with(self, fieldnames): + for fieldname in fieldnames: + method_name = 'on_change_with_%s' % fieldname + value = getattr(self, method_name)() + setattr(self, fieldname, value) + + @on_change def on_change(self, fieldnames): for fieldname in sorted(fieldnames): method = getattr(self, 'on_change_%s' % fieldname, None) if method: method() @@ -761,34 +770,8 @@ def on_change(self, fieldnames): for fieldname in sorted(fieldnames): method = getattr(self, 'on_change_%s' % fieldname, None) if method: method() - # XXX remove backward compatibility - return [self._changed_values] - - def on_change_with(self, fieldnames): - from .modelstorage import ModelStorage - changes = {} - for fieldname in fieldnames: - field = self._fields[fieldname] - method_name = 'on_change_with_%s' % fieldname - value = getattr(self, method_name)() - setattr(self, fieldname, value) - if field._type in {'many2one', 'one2one', 'reference'}: - if isinstance(value, ModelStorage): - if value.id and value.id >= 0: - changes[f'%{fieldname}.'] = { - 'rec_name': value.rec_name, - } - if field._type == 'reference': - value = str(value) - else: - value = value.id - elif field._type in {'one2many', 'many2many'}: - if isinstance(value, (list, tuple)): - value = [int(r) for r in value] - changes[fieldname] = value - return changes def on_change_notify(self): """Return a list of type and message couples. @@ -802,8 +785,7 @@ By default, the value of a field is its internal representation except: - for Many2One and One2One field: the id. - for Reference field: the string model,id - - for Many2Many: the list of ids - - for One2Many: a dictionary composed of three keys: + - for One2Many and Many2Many: a dictionary composed of three keys: - add: a list of tuple, the first element is the index where the new line is added, the second element is `_default_values` @@ -822,7 +804,7 @@ # Always test key presence in case value is None if (fname in init_values and value == init_values[fname] - and field._type != 'one2many'): + and field._type not in {'one2many', 'many2many'}): continue if field._type in ('many2one', 'one2one', 'reference'): if value: diff --git a/tryton/trytond/trytond/protocols/dispatcher.py b/trytond/trytond/protocols/dispatcher.py index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL3Byb3RvY29scy9kaXNwYXRjaGVyLnB5..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL3Byb3RvY29scy9kaXNwYXRjaGVyLnB5 100644 --- a/tryton/trytond/trytond/protocols/dispatcher.py +++ b/tryton/trytond/trytond/protocols/dispatcher.py @@ -190,7 +190,7 @@ c_args, c_kwargs, transaction.context, transaction.timestamp \ = rpc.convert(obj, *args, **kwargs) transaction.context['_request'] = request.context - meth = getattr(obj, method) + meth = rpc.decorate(getattr(obj, method)) if (rpc.instantiate is None or not is_instance_method(obj, method)): result = rpc.result(meth(*c_args, **c_kwargs)) diff --git a/tryton/trytond/trytond/rpc.py b/trytond/trytond/rpc.py index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL3JwYy5weQ==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL3JwYy5weQ== 100644 --- a/tryton/trytond/trytond/rpc.py +++ b/tryton/trytond/trytond/rpc.py @@ -18,8 +18,9 @@ unique: Check instances are unique ''' - __slots__ = ('readonly', 'instantiate', 'result', 'check_access', - 'fresh_session', 'unique', 'cache') + __slots__ = ( + 'readonly', 'instantiate', 'decorator', 'result', + 'check_access', 'fresh_session', 'unique', 'cache') def __init__(self, readonly=True, instantiate=None, result=None, check_access=True, fresh_session=False, unique=True, cache=None): @@ -23,5 +25,6 @@ - def __init__(self, readonly=True, instantiate=None, result=None, + def __init__( + self, readonly=True, instantiate=None, decorator=None, result=None, check_access=True, fresh_session=False, unique=True, cache=None): self.readonly = readonly self.instantiate = instantiate @@ -25,6 +28,7 @@ check_access=True, fresh_session=False, unique=True, cache=None): self.readonly = readonly self.instantiate = instantiate + self.decorator = decorator if result is None: def result(r): return r @@ -83,6 +87,11 @@ context['_check_access'] = True return args, kwargs, context, timestamp + def decorate(self, func): + if self.decorator: + func = self.decorator(func) + return func + class RPCCache: __slots__ = ('duration',) diff --git a/tryton/trytond/trytond/tests/test_field_function.py b/trytond/trytond/tests/test_field_function.py index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL3Rlc3RzL3Rlc3RfZmllbGRfZnVuY3Rpb24ucHk=..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL3Rlc3RzL3Rlc3RfZmllbGRfZnVuY3Rpb24ucHk= 100644 --- a/tryton/trytond/trytond/tests/test_field_function.py +++ b/tryton/trytond/trytond/tests/test_field_function.py @@ -103,7 +103,7 @@ record = Model() record.save() - with patch.object(Model, 'get_function1') as getter: + with patch.object(Model, 'get_function1', autospec=True) as getter: getter.return_value = 'test' Model.read([record.id], ['function1', 'function2'])