From 59b2e29a9ca035e6379ef7ff501bdcfbd6e43bda Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 16 May 2019 14:38:52 +0000 Subject: [PATCH] [FIX] models: multiple x2many fields in onchange When the model has several x2many fields which are non-disjoint, the web client sometimes invokes `onchange` with inconsistent data: a line appearing in two x2many fields with different values for a subfield. The method must deal with that inconsistency and return correct data to the client. --- odoo/models.py | 48 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/odoo/models.py b/odoo/models.py index 6fbc7520603..f1520df032f 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -5483,13 +5483,18 @@ Fields: def __init__(self, record, tree): # put record in dict to include it when comparing snapshots super(Snapshot, self).__init__({'': record, '': tree}) - for name, subnames in tree.items(): - field = record._fields[name] + for name in tree: + self.fetch(name) + + def fetch(self, name): + """ Set the value of field ``name`` from the record's value. """ + record = self[''] + tree = self[''] + if record._fields[name].type in ('one2many', 'many2many'): # x2many fields are serialized as a list of line snapshots - self[name] = ( - [Snapshot(line, subnames) for line in record[name]] - if field.type in ('one2many', 'many2many') else record[name] - ) + self[name] = [Snapshot(line, tree[name]) for line in record[name]] + else: + self[name] = record[name] def has_changed(self, name): """ Return whether a field on record has changed. """ @@ -5562,11 +5567,40 @@ Fields: for subname in subnames: new_lines.mapped(subname) + # Isolate changed x2many values, to handle inconsistent data sent from + # the client side: when a form view contains two one2many fields that + # overlap, the lines that appear in both fields may be sent with + # different data. Consider, for instance: + # + # foo_ids: [line with value=1, ...] + # bar_ids: [line with value=1, ...] + # + # If value=2 is set on 'line' in 'bar_ids', the client sends + # + # foo_ids: [line with value=1, ...] + # bar_ids: [line with value=2, ...] + # + # The idea is to put 'foo_ids' in cache first, so that the snapshot + # contains value=1 for line in 'foo_ids'. The snapshot is then updated + # with the value of `bar_ids`, which will contain value=2 on line. + values = dict(values) + changed_x2many = { + name: values.pop(name, []) + for name in names + if self._fields[name].type in ('one2many', 'many2many') + } + # create a new record with values, and attach ``self`` to it record = self.new(values, origin=self) # make a snapshot based on the initial values of record - snapshot0 = snapshot1 = Snapshot(record, nametree) + snapshot0 = Snapshot(record, nametree) + + # store changed values in cache, and update snapshot0 + for name, value in changed_x2many.items(): + field = self._fields[name] + record._cache[name] = field.convert_to_cache(value, record) + snapshot0.fetch(name) # determine which field(s) should be triggered an onchange todo = list(names or nametree)