[FIX] core: first onchange should always compute fields

When a form view is opened, the first call to `onchange` should always
compute fields, even if their dependencies have no default value.  Make
sure it is the case for main records, and for records inside one2many
fields.  The latter case was actually not working as expected.

closes odoo/odoo#63646

X-original-commit: 45422d56bce413b8577f1784e10dd22ede93c751
Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
This commit is contained in:
Raphael Collet
2020-12-21 13:55:20 +00:00
parent 81f2bc7b0a
commit dcc2a7e39d
3 changed files with 85 additions and 59 deletions
@@ -556,13 +556,13 @@ class ComputeOnchange(models.Model):
@api.depends('foo')
def _compute_bar(self):
for record in self:
record.bar = record.foo
record.bar = (record.foo or "") + "r"
@api.depends('active', 'foo')
def _compute_baz(self):
for record in self:
if record.active:
record.baz = record.foo
record.baz = (record.foo or "") + "z"
@api.depends('foo')
def _compute_line_ids(self):
@@ -590,9 +590,14 @@ class ComputeOnchangeLine(models.Model):
_name = 'test_new_api.compute.onchange.line'
_description = "Line-like model for test_new_api.compute.onchange"
record_id = fields.Many2one('test_new_api.compute.onchange', ondelete='cascade')
foo = fields.Char()
record_id = fields.Many2one('test_new_api.compute.onchange',
required=True, ondelete='cascade')
bar = fields.Char(compute='_compute_bar')
@api.depends('foo')
def _compute_bar(self):
for line in self:
line.bar = (line.foo or "") + "r"
class ComputeDynamicDepends(models.Model):
+71 -53
View File
@@ -543,24 +543,29 @@ class TestComputeOnchange(common.TransactionCase):
def test_create(self):
model = self.env['test_new_api.compute.onchange']
# compute 'bar' (readonly) and 'baz' (editable)
record = model.create({'active': True})
self.assertEqual(record.bar, "r")
self.assertEqual(record.baz, "z")
# compute 'bar' and 'baz'
record = model.create({'active': True, 'foo': "foo"})
self.assertEqual(record.bar, "foo")
self.assertEqual(record.baz, "foo")
self.assertEqual(record.bar, "foor")
self.assertEqual(record.baz, "fooz")
# compute 'bar' but not 'baz'
record = model.create({'active': True, 'foo': "foo", 'bar': "bar", 'baz': "baz"})
self.assertEqual(record.bar, "foo")
self.assertEqual(record.bar, "foor")
self.assertEqual(record.baz, "baz")
# compute 'bar' and 'baz', but do not change its value
record = model.create({'active': False, 'foo': "foo"})
self.assertEqual(record.bar, "foo")
self.assertEqual(record.bar, "foor")
self.assertEqual(record.baz, False)
# compute 'bar' but not 'baz'
record = model.create({'active': False, 'foo': "foo", 'bar': "bar", 'baz': "baz"})
self.assertEqual(record.bar, "foo")
self.assertEqual(record.bar, "foor")
self.assertEqual(record.baz, "baz")
def test_copy(self):
@@ -572,10 +577,10 @@ class TestComputeOnchange(common.TransactionCase):
{'name': 'bar1'},
])
# compute 'bar', 'baz', 'line_ids' and 'tag_ids'
# compute 'bar' (readonly), 'baz', 'line_ids' and 'tag_ids' (editable)
record = Model.create({'active': True, 'foo': "foo1"})
self.assertEqual(record.bar, "foo1")
self.assertEqual(record.baz, "foo1")
self.assertEqual(record.bar, "foo1r")
self.assertEqual(record.baz, "foo1z")
self.assertEqual(record.line_ids.mapped('foo'), ['foo1'])
self.assertEqual(record.tag_ids, tag_foo)
@@ -585,7 +590,7 @@ class TestComputeOnchange(common.TransactionCase):
'line_ids': [Command.create({'foo': 'bar'})],
'tag_ids': [Command.link(tag_bar.id)],
})
self.assertEqual(record.bar, "foo1")
self.assertEqual(record.bar, "foo1r")
self.assertEqual(record.baz, "baz1")
self.assertEqual(record.line_ids.mapped('foo'), ['foo1', 'bar'])
self.assertEqual(record.tag_ids, tag_foo + tag_bar)
@@ -593,7 +598,7 @@ class TestComputeOnchange(common.TransactionCase):
# copy the record, and check results
copied = record.copy()
self.assertEqual(copied.foo, "foo1 (copy)") # copied and modified
self.assertEqual(copied.bar, "foo1 (copy)") # computed
self.assertEqual(copied.bar, "foo1 (copy)r") # computed
self.assertEqual(copied.baz, "baz1") # copied
self.assertEqual(record.line_ids.mapped('foo'), ['foo1', 'bar']) # copied
self.assertEqual(record.tag_ids, tag_foo + tag_bar) # copied
@@ -601,128 +606,141 @@ class TestComputeOnchange(common.TransactionCase):
def test_write(self):
model = self.env['test_new_api.compute.onchange']
record = model.create({'active': True, 'foo': "foo"})
self.assertEqual(record.bar, "foo")
self.assertEqual(record.baz, "foo")
self.assertEqual(record.bar, "foor")
self.assertEqual(record.baz, "fooz")
# recompute 'bar' and 'baz'
# recompute 'bar' (readonly) and 'baz' (editable)
record.write({'foo': "foo1"})
self.assertEqual(record.bar, "foo1")
self.assertEqual(record.baz, "foo1")
self.assertEqual(record.bar, "foo1r")
self.assertEqual(record.baz, "foo1z")
# recompute 'bar' but not 'baz'
record.write({'foo': "foo2", 'bar': "bar2", 'baz': "baz2"})
self.assertEqual(record.bar, "foo2")
self.assertEqual(record.bar, "foo2r")
self.assertEqual(record.baz, "baz2")
# recompute 'bar' and 'baz', but do not change its value
record.write({'active': False, 'foo': "foo3"})
self.assertEqual(record.bar, "foo3")
self.assertEqual(record.bar, "foo3r")
self.assertEqual(record.baz, "baz2")
# recompute 'bar' but not 'baz'
record.write({'active': False, 'foo': "foo4", 'bar': "bar4", 'baz': "baz4"})
self.assertEqual(record.bar, "foo4")
self.assertEqual(record.bar, "foo4r")
self.assertEqual(record.baz, "baz4")
def test_set(self):
model = self.env['test_new_api.compute.onchange']
record = model.create({'active': True, 'foo': "foo"})
self.assertEqual(record.bar, "foo")
self.assertEqual(record.baz, "foo")
self.assertEqual(record.bar, "foor")
self.assertEqual(record.baz, "fooz")
# recompute 'bar' and 'baz'
# recompute 'bar' (readonly) and 'baz' (editable)
record.foo = "foo1"
self.assertEqual(record.bar, "foo1")
self.assertEqual(record.baz, "foo1")
self.assertEqual(record.bar, "foo1r")
self.assertEqual(record.baz, "foo1z")
# do not recompute 'baz'
record.baz = "baz2"
self.assertEqual(record.bar, "foo1")
self.assertEqual(record.bar, "foo1r")
self.assertEqual(record.baz, "baz2")
# recompute 'baz', but do not change its value
record.active = False
self.assertEqual(record.bar, "foo1")
self.assertEqual(record.bar, "foo1r")
self.assertEqual(record.baz, "baz2")
# recompute 'baz', but do not change its value
record.foo = "foo3"
self.assertEqual(record.bar, "foo3")
self.assertEqual(record.bar, "foo3r")
self.assertEqual(record.baz, "baz2")
# do not recompute 'baz'
record.baz = "baz4"
self.assertEqual(record.bar, "foo3")
self.assertEqual(record.bar, "foo3r")
self.assertEqual(record.baz, "baz4")
def test_set_new(self):
model = self.env['test_new_api.compute.onchange']
record = model.new({'active': True, 'foo': "foo"})
self.assertEqual(record.bar, "foo")
self.assertEqual(record.baz, "foo")
record = model.new({'active': True})
self.assertEqual(record.bar, "r")
self.assertEqual(record.baz, "z")
# recompute 'bar' and 'baz'
# recompute 'bar' (readonly) and 'baz' (editable)
record.foo = "foo1"
self.assertEqual(record.bar, "foo1")
self.assertEqual(record.baz, "foo1")
self.assertEqual(record.bar, "foo1r")
self.assertEqual(record.baz, "foo1z")
# do not recompute 'baz'
record.baz = "baz2"
self.assertEqual(record.bar, "foo1")
self.assertEqual(record.bar, "foo1r")
self.assertEqual(record.baz, "baz2")
# recompute 'baz', but do not change its value
record.active = False
self.assertEqual(record.bar, "foo1")
self.assertEqual(record.bar, "foo1r")
self.assertEqual(record.baz, "baz2")
# recompute 'baz', but do not change its value
record.foo = "foo3"
self.assertEqual(record.bar, "foo3")
self.assertEqual(record.bar, "foo3r")
self.assertEqual(record.baz, "baz2")
# do not recompute 'baz'
record.baz = "baz4"
self.assertEqual(record.bar, "foo3")
self.assertEqual(record.bar, "foo3r")
self.assertEqual(record.baz, "baz4")
def test_onchange(self):
# check computations of 'bar' (readonly) and 'baz' (editable)
form = common.Form(self.env['test_new_api.compute.onchange'])
self.assertEqual(form.bar, "r")
self.assertEqual(form.baz, False)
form.active = True
self.assertEqual(form.bar, "r")
self.assertEqual(form.baz, "z")
form.foo = "foo1"
self.assertEqual(form.bar, "foo1")
self.assertEqual(form.baz, "foo1")
self.assertEqual(form.bar, "foo1r")
self.assertEqual(form.baz, "foo1z")
form.baz = "baz2"
self.assertEqual(form.bar, "foo1")
self.assertEqual(form.bar, "foo1r")
self.assertEqual(form.baz, "baz2")
form.active = False
self.assertEqual(form.bar, "foo1")
self.assertEqual(form.bar, "foo1r")
self.assertEqual(form.baz, "baz2")
form.foo = "foo3"
self.assertEqual(form.bar, "foo3")
self.assertEqual(form.bar, "foo3r")
self.assertEqual(form.baz, "baz2")
form.active = True
self.assertEqual(form.bar, "foo3")
self.assertEqual(form.baz, "foo3")
self.assertEqual(form.bar, "foo3r")
self.assertEqual(form.baz, "foo3z")
with form.line_ids.new() as line:
# check computation of 'bar' (readonly)
self.assertEqual(line.foo, False)
self.assertEqual(line.bar, "r")
line.foo = "foo"
self.assertEqual(line.foo, "foo")
self.assertEqual(line.bar, "foor")
record = form.save()
self.assertEqual(record.bar, "foo3")
self.assertEqual(record.baz, "foo3")
self.assertEqual(record.bar, "foo3r")
self.assertEqual(record.baz, "foo3z")
form = common.Form(record)
self.assertEqual(form.bar, "foo3")
self.assertEqual(form.baz, "foo3")
self.assertEqual(form.bar, "foo3r")
self.assertEqual(form.baz, "foo3z")
form.foo = "foo4"
self.assertEqual(form.bar, "foo4")
self.assertEqual(form.baz, "foo4")
self.assertEqual(form.bar, "foo4r")
self.assertEqual(form.baz, "foo4z")
form.baz = "baz5"
self.assertEqual(form.bar, "foo4")
self.assertEqual(form.bar, "foo4r")
self.assertEqual(form.baz, "baz5")
form.active = False
self.assertEqual(form.bar, "foo4")
self.assertEqual(form.bar, "foo4r")
self.assertEqual(form.baz, "baz5")
form.foo = "foo6"
self.assertEqual(form.bar, "foo6")
self.assertEqual(form.bar, "foo6r")
self.assertEqual(form.baz, "baz5")
def test_onchange_one2many(self):
+5 -2
View File
@@ -6162,8 +6162,11 @@ Fields:
for name in names:
snapshot0.fetch(name)
# determine which field(s) should be triggered an onchange
todo = list(names or nametree)
# Determine which field(s) should be triggered an onchange. On the first
# call, 'names' only contains fields with a default. If 'self' is a new
# line in a one2many field, 'names' also contains the one2many's inverse
# field, and that field may not be in nametree.
todo = list(names) + list(nametree) if first_call else list(names)
done = set()
# dummy assignment: trigger invalidations on the record