[REF] core: combine default_get() with first onchange()

When creating a new record, the client calls `default_get()`, completes
the returned values with `False`, and calls `onchange()` to apply the
onchange to the defaults.

Optimize the double round-trip by integrating `default_get()` inside
`onchange()` for the first call.  The method is called with an empty
list of fields, and usually no field values, except for records in a
one2many field.  In this case, `onchange()` does both steps above.

Task 2261084
This commit is contained in:
Raphael Collet
2020-08-20 13:39:04 +00:00
parent 540682b9be
commit 488e334fc8
3 changed files with 35 additions and 44 deletions
@@ -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):
+21 -3
View File
@@ -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['<record>']
result = {}
for name, subnames in self['<tree>'].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')
+12 -39
View File
@@ -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']