From 2067a206ecba9a8dbf7f76a55926be964e580e7e Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Mon, 9 Feb 2015 17:35:17 +0100 Subject: [PATCH] [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. --- addons/web/static/src/js/view_form.js | 4 +++- openerp/models.py | 27 ++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 6aba8c5dc50..996034f7ebe 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -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()); diff --git a/openerp/models.py b/openerp/models.py index c929d3be120..ace7abed3a1 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -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