Add issue12473.diff

This commit is contained in:
Albert Cervera i Areny 2023-08-21 16:33:19 +02:00
parent 6a3463425a
commit 393a1d35d2
2 changed files with 546 additions and 0 deletions

544
issue12473.diff Normal file
View File

@ -0,0 +1,544 @@
diff --git a/proteus/proteus/__init__.py b/proteus/proteus/__init__.py
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_cHJvdGV1cy9wcm90ZXVzL19faW5pdF9fLnB5..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_cHJvdGV1cy9wcm90ZXVzL19faW5pdF9fLnB5 100644
--- a/proteus/proteus/__init__.py
+++ b/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/sao/src/model.js b/sao/src/model.js
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_c2FvL3NyYy9tb2RlbC5qcw==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_c2FvL3NyYy9tb2RlbC5qcw== 100644
--- a/sao/src/model.js
+++ b/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/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/gui/window/view_form/model/record.py
+++ b/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/trytond/CHANGELOG b/trytond/CHANGELOG
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC9DSEFOR0VMT0c=..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC9DSEFOR0VMT0c= 100644
--- a/trytond/CHANGELOG
+++ b/trytond/CHANGELOG
@@ -1,3 +1,5 @@
+* Support add/update/remove/delete for on_change_with of xxx2Many
+* Add decorator on RPC
* Add a canonicalize function for domains
* Enforce record rules when reading only non SQL fields (#12428)
* Support PYSON comparison of timedelta
diff --git a/trytond/doc/ref/rpc.rst b/trytond/doc/ref/rpc.rst
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC9kb2MvcmVmL3JwYy5yc3Q=..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC9kb2MvcmVmL3JwYy5yc3Q= 100644
--- a/trytond/doc/ref/rpc.rst
+++ b/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/trytond/trytond/model/fields/field.py b/trytond/trytond/model/fields/field.py
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL21vZGVsL2ZpZWxkcy9maWVsZC5weQ==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL21vZGVsL2ZpZWxkcy9maWVsZC5weQ== 100644
--- a/trytond/trytond/model/fields/field.py
+++ b/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/trytond/trytond/model/fields/function.py b/trytond/trytond/model/fields/function.py
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL21vZGVsL2ZpZWxkcy9mdW5jdGlvbi5weQ==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL21vZGVsL2ZpZWxkcy9mdW5jdGlvbi5weQ== 100644
--- a/trytond/trytond/model/fields/function.py
+++ b/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/trytond/trytond/model/modelview.py b/trytond/trytond/model/modelview.py
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL21vZGVsL21vZGVsdmlldy5weQ==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL21vZGVsL21vZGVsdmlldy5weQ== 100644
--- a/trytond/trytond/model/modelview.py
+++ b/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/trytond/trytond/protocols/dispatcher.py b/trytond/trytond/protocols/dispatcher.py
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL3Byb3RvY29scy9kaXNwYXRjaGVyLnB5..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL3Byb3RvY29scy9kaXNwYXRjaGVyLnB5 100644
--- a/trytond/trytond/protocols/dispatcher.py
+++ b/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/trytond/trytond/rpc.py b/trytond/trytond/rpc.py
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL3JwYy5weQ==..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL3JwYy5weQ== 100644
--- a/trytond/trytond/rpc.py
+++ b/trytond/trytond/rpc.py
@@ -12,9 +12,10 @@
readonly: The transaction mode
instantiate: The position or the slice of the arguments to be instanciated
+ decorator: A function to decorate the procedure with
result: The function to transform the result
check_access: If access right must be checked
fresh_session: If a fresh session is required
unique: Check instances are unique
'''
@@ -15,9 +16,10 @@
result: The function to transform the result
check_access: If access right must be checked
fresh_session: If a fresh session is required
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')
@@ -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/trytond/trytond/tests/test_field_function.py b/trytond/trytond/tests/test_field_function.py
index bfad1dc7382cb9fd2c17bef77da699ed36869d3e_dHJ5dG9uZC90cnl0b25kL3Rlc3RzL3Rlc3RfZmllbGRfZnVuY3Rpb24ucHk=..bf7072adc76db4e62ec3fbde7efde17e7bbd630d_dHJ5dG9uZC90cnl0b25kL3Rlc3RzL3Rlc3RfZmllbGRfZnVuY3Rpb24ucHk= 100644
--- a/trytond/trytond/tests/test_field_function.py
+++ b/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'])

2
series
View File

@ -23,3 +23,5 @@ statement_of_account.diff # [account] Cumulate balance of previous fiscal years
issue11731.diff # [currency] currency test don't pass when Currency Rates Source Not Ready (forex)
counterpart_party_payment_clearing.diff # [account_payment_clearing] Add the possiblity to have a party in the counterpart move when reconcile on a payment.
issue12473.diff # [trytond] Support add/update/remove/delete on on_change_with for one2many