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:
+17
-13
@@ -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,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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'])
|
||||
|
||||
Reference in New Issue
Block a user