diff --git a/odoo/addons/test_testing_utilities/tests/test_form_impl.py b/odoo/addons/test_testing_utilities/tests/test_form_impl.py index af49d27cbe5..ab846bda87e 100644 --- a/odoo/addons/test_testing_utilities/tests/test_form_impl.py +++ b/odoo/addons/test_testing_utilities/tests/test_form_impl.py @@ -374,13 +374,13 @@ class TestO2M(TransactionCase): with f.subs.edit(index=0) as s: self.assertEqual(s.v, 5) - self.assertEqual(s.value, False) + self.assertEqual(s.value, 2) r = f.save() self.assertEqual( [get(s) for s in r.subs], - [("5", 0, 5)] + [("5", 2, 5)] ) def test_o2m_inner_default(self): diff --git a/odoo/models.py b/odoo/models.py index d82806b6862..f3a2b4d7530 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -5857,6 +5857,10 @@ Fields: names (in view order), or False :param field_onchange: dictionary mapping field names to their on_change attribute + + When ``field_name`` is falsy, the method first adds default values + to ``values``, applies onchange methods to them, and return all the + fields in ``field_onchange``. """ # this is for tests using `Form` self.flush() @@ -5869,6 +5873,8 @@ Fields: else: names = [] + first_call = not names + if not all(name in self._fields for name in names): return {} @@ -5931,14 +5937,16 @@ Fields: ) ) - def diff(self, other): + def diff(self, other, force=False): """ Return the values in ``self`` that differ from ``other``. Requires record cache invalidation for correct output! """ record = self[''] result = {} for name, subnames in self[''].items(): - if (name == 'id') or (other.get(name) == self[name]): + if name == 'id': + continue + if not force and other.get(name) == self[name]: continue field = record._fields[name] if field.type not in ('one2many', 'many2many'): @@ -5968,6 +5976,16 @@ Fields: nametree = PrefixTree(self.browse(), field_onchange) + if first_call: + names = list(nametree) + values.update(self.default_get([ + name + for name in names + if name not in values + ])) + for name in names: + values.setdefault(name, False) + # prefetch x2many lines without data (for the initial snapshot) for name, subnames in nametree.items(): if subnames and values.get(name): @@ -6065,7 +6083,7 @@ Fields: # determine values that have changed by comparing snapshots self.invalidate_cache() - result['value'] = snapshot1.diff(snapshot0) + result['value'] = snapshot1.diff(snapshot0, force=first_call) # format warnings warnings = result.pop('warnings') diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 066bb067148..106cde6f74e 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -1695,42 +1695,12 @@ class Form(object): def _init_from_defaults(self, model): vals = self._values - fields = self._view['fields'] - def cleanup(k, v): - if fields[k]['type'] == 'one2many': - return [ - # use None as "empty" value for UPDATE instead of {} - (1, c[1], None) if c[0] == 1 and not c[2] else c - for c in v - if c[0] != 6 # o2m default gets a (6) at the start, nonsensical - ] - elif fields[k]['type'] == 'datetime' and isinstance(v, datetime): - return odoo.fields.Datetime.to_string(v) - elif fields[k]['type'] == 'date' and isinstance(v, date): - return odoo.fields.Datetime.to_string(v) + vals.clear() + vals['id'] = False - return v - defaults = { - k: cleanup(k, v) - for k, v in model.default_get(list(fields)).items() - if k in fields - } - vals.update(defaults) - # m2m should all be rep'd as command list - for k, v in vals.items(): - if not v: - type_ = fields[k]['type'] - if type_ == 'many2many': - vals[k] = [(6, False, [])] - elif type_ == 'one2many': - vals[k] = [] - elif type_ in ('integer', 'float'): - vals[k] = 0 - - # on creation, every field is considered changed by the client - # apparently - # and fields should be sent in view order, not whatever fields_view_get['fields'].keys() is - self._perform_onchange(self._view['fields_ordered']) + # call onchange with an empty list of fields; this retrieves default + # values, applies onchanges and return the result + self._perform_onchange([]) def _init_from_values(self, values): self._values.update( @@ -1929,6 +1899,9 @@ class Form(object): """ values = {} for f in fields: + if f == 'id': + continue + get_modifier = functools.partial( self._get_modifier, f, modmap=view['modifiers'], @@ -1941,8 +1914,8 @@ class Form(object): if get_modifier('required'): raise AssertionError("{} is a required field ({})".format(f, view['modifiers'][f])) - # skip unmodified fields unless all_fields (also always ignore id) - if f == 'id' or not (all_fields or f in changed): + # skip unmodified fields unless all_fields + if not (all_fields or f in changed): continue if get_modifier('readonly'): @@ -1997,7 +1970,7 @@ class Form(object): # skip calling onchange() if there's no trigger on any of the changed # fields spec = self._view['onchange'] - if not any(spec[f] for f in fields): + if fields and not any(spec[f] for f in fields): return record = self._model.browse(self._values.get('id')) @@ -2012,7 +1985,7 @@ class Form(object): self._values.update( (k, self._cleanup_onchange( self._view['fields'][k], - v, self._values[k], + v, self._values.get(k), )) for k, v in values.items() if k in self._view['fields']