[FIX] models: process onchange methods on new records in the order of the view
The onchange() on new records processes fields in non-predictable order. This is problematic when onchange methods are designed to be applied after each other. The expected order is presumed to be the one of the fields in the view. In order to implement this behavior, the JS client invokes method onchange() with the list of fields (in view order) instead of False. The server then uses that order for evaluating the onchange methods. This fixes #4897.
This commit is contained in:
parent
372f4a82d5
commit
2067a206ec
|
@ -452,8 +452,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
return "";
|
||||
};
|
||||
|
||||
self._onchange_fields = [];
|
||||
self._onchange_specs = {};
|
||||
_.each(this.fields, function(field, name) {
|
||||
self._onchange_fields.push(name);
|
||||
self._onchange_specs[name] = find(name, field.node);
|
||||
_.each(field.field.views, function(view) {
|
||||
_.each(view.fields, function(_, subname) {
|
||||
|
@ -490,7 +492,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
var change_spec = widget ? onchange_specs[widget.name] : null;
|
||||
if (!widget || (!_.isEmpty(change_spec) && change_spec !== "0")) {
|
||||
var ids = [],
|
||||
trigger_field_name = widget ? widget.name : false,
|
||||
trigger_field_name = widget ? widget.name : self._onchange_fields,
|
||||
values = self._get_onchange_values(),
|
||||
context = new instance.web.CompoundContext(self.dataset.get_context());
|
||||
|
||||
|
|
|
@ -5734,13 +5734,20 @@ class BaseModel(object):
|
|||
|
||||
:param values: dictionary mapping field names to values, giving the
|
||||
current state of modification
|
||||
:param field_name: name of the modified field_name
|
||||
:param field_name: name of the modified field, or list of field
|
||||
names (in view order), or False
|
||||
:param field_onchange: dictionary mapping field names to their
|
||||
on_change attribute
|
||||
"""
|
||||
env = self.env
|
||||
if isinstance(field_name, list):
|
||||
names = field_name
|
||||
elif field_name:
|
||||
names = [field_name]
|
||||
else:
|
||||
names = []
|
||||
|
||||
if field_name and field_name not in self._fields:
|
||||
if not all(name in self._fields for name in names):
|
||||
return {}
|
||||
|
||||
# determine subfields for field.convert_to_write() below
|
||||
|
@ -5759,23 +5766,24 @@ class BaseModel(object):
|
|||
# attach `self` with a different context (for cache consistency)
|
||||
record._origin = self.with_context(__onchange=True)
|
||||
|
||||
# determine which field should be triggered an onchange
|
||||
todo = set([field_name]) if field_name else set(values)
|
||||
# determine which field(s) should be triggered an onchange
|
||||
todo = list(names) or list(values)
|
||||
done = set()
|
||||
|
||||
# dummy assignment: trigger invalidations on the record
|
||||
for name in todo:
|
||||
value = record[name]
|
||||
field = self._fields[name]
|
||||
if not field_name and field.type == 'many2one' and field.delegate and not value:
|
||||
if field.type == 'many2one' and field.delegate and not value:
|
||||
# do not nullify all fields of parent record for new records
|
||||
continue
|
||||
record[name] = value
|
||||
|
||||
result = {'value': {}}
|
||||
|
||||
# process names in order (or the keys of values if no name given)
|
||||
while todo:
|
||||
name = todo.pop()
|
||||
name = todo.pop(0)
|
||||
if name in done:
|
||||
continue
|
||||
done.add(name)
|
||||
|
@ -5799,7 +5807,7 @@ class BaseModel(object):
|
|||
result['value'][name] = field.convert_to_write(
|
||||
newval, record._origin, subfields.get(name),
|
||||
)
|
||||
todo.add(name)
|
||||
todo.append(name)
|
||||
else:
|
||||
# keep result: newval may have been dirty before
|
||||
pass
|
||||
|
@ -5809,14 +5817,15 @@ class BaseModel(object):
|
|||
result['value'][name] = field.convert_to_write(
|
||||
newval, record._origin, subfields.get(name),
|
||||
)
|
||||
todo.add(name)
|
||||
todo.append(name)
|
||||
else:
|
||||
# clean up result to not return another value
|
||||
result['value'].pop(name, None)
|
||||
|
||||
# At the moment, the client does not support updates on a *2many field
|
||||
# while this one is modified by the user.
|
||||
if field_name and self._fields[field_name].type in ('one2many', 'many2many'):
|
||||
if field_name and not isinstance(field_name, list) and \
|
||||
self._fields[field_name].type in ('one2many', 'many2many'):
|
||||
result['value'].pop(field_name, None)
|
||||
|
||||
return result
|
||||
|
|
Loading…
Reference in New Issue