Use _changed_values to return result of on_change_with

This allows to support add/update/remove/delete for xxx2many fields and
rec_name for xxx2one and reference fields.

Closes #12473
This commit is contained in:
Cédric Krier
2023-08-16 16:11:51 +02:00
parent 9aa4a8583b
commit bc5a28a50c
7 changed files with 88 additions and 75 deletions
+17 -13
View File
@@ -1129,27 +1129,29 @@
delete this._values[fieldname + '.'];
}
}
var result;
var changed;
fieldnames = Object.keys(fieldnames);
if (fieldnames.length) {
try {
if ((fieldnames.length == 1) ||
(values.id === undefined)) {
result = {};
changed = {};
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 {
result = this.model.execute(
changed = this.model.execute(
'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,21 +1166,23 @@
try {
if ((fieldnames.length == 1) ||
(values.id === undefined)) {
result = {};
changed = {};
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 {
result = this.model.execute(
changed = this.model.execute(
'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) {
@@ -621,19 +621,19 @@ class Record:
if fieldnames:
try:
if len(fieldnames) == 1 or 'id' not in values:
result = {}
changed = {}
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:
result = RPCExecute(
changed = RPCExecute(
'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,19 +642,19 @@ class Record:
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:
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:
result = RPCExecute(
changed = RPCExecute(
'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():
+1
View File
@@ -1,3 +1,4 @@
* Support add/update/remove/delete for on_change_with of xxx2Many
* Add decorator on RPC
* Remove translations with empty source
* Run doc tests with different globals
+15 -17
View File
@@ -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 @@ def on_change_result(record):
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):
@@ -471,9 +468,9 @@ class Field(object):
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
@@ -481,7 +478,8 @@ class Field(object):
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()
+31 -5
View File
@@ -9,7 +9,7 @@ from trytond.i18n import gettext
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,11 +124,37 @@ class Function(Field):
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: on_change_with_result(self, method(r, name))
for r in records}
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:
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:
+9 -25
View File
@@ -69,7 +69,8 @@ class ModelView(Model):
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, result=on_change_result)
cls.__rpc__['on_change_with'] = RPC(instantiate=0)
cls.__rpc__['on_change_with'] = RPC(
instantiate=0, result=on_change_result)
cls.__rpc__['on_change_notify'] = RPC(instantiate=0)
cls._buttons = {}
@@ -758,6 +759,13 @@ class ModelView(Model):
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):
@@ -765,30 +773,6 @@ class ModelView(Model):
if method:
method()
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.
Available types are info, warning and error.
+1 -1
View File
@@ -103,7 +103,7 @@ class FieldFunctionTestCase(unittest.TestCase):
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'])