[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 "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self._onchange_fields = [];
|
||||||
self._onchange_specs = {};
|
self._onchange_specs = {};
|
||||||
_.each(this.fields, function(field, name) {
|
_.each(this.fields, function(field, name) {
|
||||||
|
self._onchange_fields.push(name);
|
||||||
self._onchange_specs[name] = find(name, field.node);
|
self._onchange_specs[name] = find(name, field.node);
|
||||||
_.each(field.field.views, function(view) {
|
_.each(field.field.views, function(view) {
|
||||||
_.each(view.fields, function(_, subname) {
|
_.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;
|
var change_spec = widget ? onchange_specs[widget.name] : null;
|
||||||
if (!widget || (!_.isEmpty(change_spec) && change_spec !== "0")) {
|
if (!widget || (!_.isEmpty(change_spec) && change_spec !== "0")) {
|
||||||
var ids = [],
|
var ids = [],
|
||||||
trigger_field_name = widget ? widget.name : false,
|
trigger_field_name = widget ? widget.name : self._onchange_fields,
|
||||||
values = self._get_onchange_values(),
|
values = self._get_onchange_values(),
|
||||||
context = new instance.web.CompoundContext(self.dataset.get_context());
|
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
|
:param values: dictionary mapping field names to values, giving the
|
||||||
current state of modification
|
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
|
:param field_onchange: dictionary mapping field names to their
|
||||||
on_change attribute
|
on_change attribute
|
||||||
"""
|
"""
|
||||||
env = self.env
|
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 {}
|
return {}
|
||||||
|
|
||||||
# determine subfields for field.convert_to_write() below
|
# determine subfields for field.convert_to_write() below
|
||||||
|
@ -5759,23 +5766,24 @@ class BaseModel(object):
|
||||||
# attach `self` with a different context (for cache consistency)
|
# attach `self` with a different context (for cache consistency)
|
||||||
record._origin = self.with_context(__onchange=True)
|
record._origin = self.with_context(__onchange=True)
|
||||||
|
|
||||||
# determine which field should be triggered an onchange
|
# determine which field(s) should be triggered an onchange
|
||||||
todo = set([field_name]) if field_name else set(values)
|
todo = list(names) or list(values)
|
||||||
done = set()
|
done = set()
|
||||||
|
|
||||||
# dummy assignment: trigger invalidations on the record
|
# dummy assignment: trigger invalidations on the record
|
||||||
for name in todo:
|
for name in todo:
|
||||||
value = record[name]
|
value = record[name]
|
||||||
field = self._fields[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
|
# do not nullify all fields of parent record for new records
|
||||||
continue
|
continue
|
||||||
record[name] = value
|
record[name] = value
|
||||||
|
|
||||||
result = {'value': {}}
|
result = {'value': {}}
|
||||||
|
|
||||||
|
# process names in order (or the keys of values if no name given)
|
||||||
while todo:
|
while todo:
|
||||||
name = todo.pop()
|
name = todo.pop(0)
|
||||||
if name in done:
|
if name in done:
|
||||||
continue
|
continue
|
||||||
done.add(name)
|
done.add(name)
|
||||||
|
@ -5799,7 +5807,7 @@ class BaseModel(object):
|
||||||
result['value'][name] = field.convert_to_write(
|
result['value'][name] = field.convert_to_write(
|
||||||
newval, record._origin, subfields.get(name),
|
newval, record._origin, subfields.get(name),
|
||||||
)
|
)
|
||||||
todo.add(name)
|
todo.append(name)
|
||||||
else:
|
else:
|
||||||
# keep result: newval may have been dirty before
|
# keep result: newval may have been dirty before
|
||||||
pass
|
pass
|
||||||
|
@ -5809,14 +5817,15 @@ class BaseModel(object):
|
||||||
result['value'][name] = field.convert_to_write(
|
result['value'][name] = field.convert_to_write(
|
||||||
newval, record._origin, subfields.get(name),
|
newval, record._origin, subfields.get(name),
|
||||||
)
|
)
|
||||||
todo.add(name)
|
todo.append(name)
|
||||||
else:
|
else:
|
||||||
# clean up result to not return another value
|
# clean up result to not return another value
|
||||||
result['value'].pop(name, None)
|
result['value'].pop(name, None)
|
||||||
|
|
||||||
# At the moment, the client does not support updates on a *2many field
|
# At the moment, the client does not support updates on a *2many field
|
||||||
# while this one is modified by the user.
|
# 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)
|
result['value'].pop(field_name, None)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
Loading…
Reference in New Issue