# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from unittest.mock import patch from odoo.addons.base.tests.common import SavepointCaseWithUserDemo from odoo.tests import common, Form from odoo import Command def strip_prefix(prefix, names): size = len(prefix) return [name[size:] for name in names if name.startswith(prefix)] class TestOnChange(SavepointCaseWithUserDemo): def setUp(self): super(TestOnChange, self).setUp() self.Discussion = self.env['test_new_api.discussion'] self.Message = self.env['test_new_api.message'] self.EmailMessage = self.env['test_new_api.emailmessage'] def test_default_get(self): """ checking values returned by default_get() """ fields = ['name', 'categories', 'participants', 'messages'] values = self.Discussion.default_get(fields) self.assertEqual(values, {}) def test_get_field(self): """ checking that accessing an unknown attribute does nothing special """ with self.assertRaises(AttributeError): self.Discussion.not_really_a_method() def test_onchange(self): """ test the effect of onchange() """ discussion = self.env.ref('test_new_api.discussion_0') BODY = "What a beautiful day!" USER = self.env.user field_onchange = self.Message._onchange_spec() self.assertEqual(field_onchange.get('author'), '1') self.assertEqual(field_onchange.get('body'), '1') self.assertEqual(field_onchange.get('discussion'), '1') # changing 'discussion' should recompute 'name' values = { 'discussion': discussion.id, 'name': "[%s] %s" % ('', USER.name), 'body': False, 'author': USER.id, 'size': 0, } self.env.invalidate_all() result = self.Message.onchange(values, 'discussion', field_onchange) self.assertIn('name', result['value']) self.assertEqual(result['value']['name'], "[%s] %s" % (discussion.name, USER.name)) # changing 'body' should recompute 'size' values = { 'discussion': discussion.id, 'name': "[%s] %s" % (discussion.name, USER.name), 'body': BODY, 'author': USER.id, 'size': 0, } self.env.invalidate_all() result = self.Message.onchange(values, 'body', field_onchange) self.assertIn('size', result['value']) self.assertEqual(result['value']['size'], len(BODY)) # changing 'body' should not recompute 'name', even if 'discussion' and # 'name' are not consistent with each other values = { 'discussion': discussion.id, 'name': False, 'body': BODY, 'author': USER.id, 'size': 0, } self.env.invalidate_all() result = self.Message.onchange(values, 'body', field_onchange) self.assertNotIn('name', result['value']) def test_onchange_many2one(self): Category = self.env['test_new_api.category'] field_onchange = Category._onchange_spec() self.assertEqual(field_onchange.get('parent'), '1') root = Category.create(dict(name='root')) values = { 'name': 'test', 'parent': root.id, 'root_categ': False, } self.env.invalidate_all() result = Category.onchange(values, 'parent', field_onchange).get('value', {}) self.assertIn('root_categ', result) self.assertEqual(result['root_categ'], root.name_get()[0]) values.update(result) values['parent'] = False self.env.invalidate_all() result = Category.onchange(values, 'parent', field_onchange).get('value', {}) self.assertIn('root_categ', result) self.assertIs(result['root_categ'], False) def test_onchange_one2many(self): """ test the effect of onchange() on one2many fields """ USER = self.env.user # create an independent message message1 = self.Message.create({'body': "ABC"}) message2 = self.Message.create({'body': "ABC"}) self.assertEqual(message1.name, "[%s] %s" % ('', USER.name)) field_onchange = self.Discussion._onchange_spec() self.assertEqual(field_onchange.get('name'), '1') self.assertEqual(field_onchange.get('messages'), '1') self.assertItemsEqual( strip_prefix('messages.', field_onchange), ['author', 'body', 'name', 'size', 'important'], ) # modify discussion name values = { 'name': "Foo", 'categories': [], 'moderator': False, 'participants': [], 'messages': [ Command.link(message1.id), Command.link(message2.id), Command.update(message2.id, {'body': "XYZ"}), Command.create({ 'name': "[%s] %s" % ('', USER.name), 'body': "ABC", 'author': USER.id, 'size': 3, 'important': False, }), ], } self.env.invalidate_all() result = self.Discussion.onchange(values, 'name', field_onchange) self.assertIn('messages', result['value']) self.assertEqual(result['value']['messages'], [ Command.clear(), Command.update(message1.id, { 'name': "[%s] %s" % ("Foo", USER.name), 'body': "ABC", 'author': USER.name_get()[0], 'size': 3, 'important': False, }), Command.update(message2.id, { 'name': "[%s] %s" % ("Foo", USER.name), 'body': "XYZ", # this must be sent back 'author': USER.name_get()[0], 'size': 3, 'important': False, }), Command.create({ 'name': "[%s] %s" % ("Foo", USER.name), 'body': "ABC", 'author': USER.name_get()[0], 'size': 3, 'important': False, }), ]) # ensure onchange changing one2many without subfield works one_level_fields = {k: v for k, v in field_onchange.items() if k.count('.') < 1} values = dict(values, name='{generate_dummy_message}') result = self.Discussion.with_context(generate_dummy_message=True).onchange(values, 'name', one_level_fields) self.assertEqual(result['value']['messages'], [ Command.clear(), Command.link(message1.id), Command.link(message2.id), Command.create({}), Command.create({}), ]) def test_onchange_one2many_reference(self): """ test the effect of onchange() on one2many fields with line references """ BODY = "What a beautiful day!" USER = self.env.user REFERENCE = "virtualid42" field_onchange = self.Discussion._onchange_spec() self.assertEqual(field_onchange.get('name'), '1') self.assertEqual(field_onchange.get('messages'), '1') self.assertItemsEqual( strip_prefix('messages.', field_onchange), ['author', 'body', 'name', 'size', 'important'], ) # modify discussion name, and check that the reference of the new line # is returned values = { 'name': "Foo", 'categories': [], 'moderator': False, 'participants': [], 'messages': [ (0, REFERENCE, { 'name': "[%s] %s" % ('', USER.name), 'body': BODY, 'author': USER.id, 'size': len(BODY), 'important': False, }), ], } self.env.invalidate_all() result = self.Discussion.onchange(values, 'name', field_onchange) self.assertIn('messages', result['value']) self.assertItemsEqual(result['value']['messages'], [ (5, 0, 0), (0, REFERENCE, { 'name': "[%s] %s" % ("Foo", USER.name), 'body': BODY, 'author': USER.name_get()[0], 'size': len(BODY), 'important': False, }), ]) def test_onchange_one2many_multi(self): """ test the effect of multiple onchange methods on one2many fields """ partner1 = self.env['res.partner'].create({'name': 'A partner'}) multi = self.env['test_new_api.multi'].create({'partner': partner1.id}) line1 = multi.lines.create({'multi': multi.id}) field_onchange = multi._onchange_spec() self.assertEqual(field_onchange, { 'name': '1', 'partner': '1', 'lines': None, 'lines.name': None, 'lines.partner': None, 'lines.tags': None, 'lines.tags.name': None, }) values = multi._convert_to_write({key: multi[key] for key in ('name', 'partner', 'lines')}) self.assertEqual(values, { 'name': partner1.name, 'partner': partner1.id, 'lines': [Command.set([line1.id])], }) # modify 'partner' # -> set 'partner' on all lines # -> recompute 'name' # -> set 'name' on all lines partner2 = self.env['res.partner'].create({'name': 'A second partner'}) values = { 'name': partner1.name, 'partner': partner2.id, # this one just changed 'lines': [Command.set([line1.id]), Command.create({'name': False, 'partner': False, 'tags': [Command.clear()]})], } self.env.invalidate_all() result = multi.onchange(values, 'partner', field_onchange) self.assertEqual(result['value'], { 'name': partner2.name, 'lines': [ Command.clear(), Command.update(line1.id, { 'name': partner2.name, 'partner': (partner2.id, partner2.name), 'tags': [Command.clear()], }), Command.create({ 'name': partner2.name, 'partner': (partner2.id, partner2.name), 'tags': [Command.clear()], }), ], }) # do it again, but this time with a new tag on the second line values = { 'name': partner1.name, 'partner': partner2.id, # this one just changed 'lines': [Command.set([line1.id]), Command.create({'name': False, 'partner': False, 'tags': [Command.clear(), Command.create({'name': 'Tag'})]})], } self.env.invalidate_all() result = multi.onchange(values, 'partner', field_onchange) expected_value = { 'name': partner2.name, 'lines': [ Command.clear(), Command.update(line1.id, { 'name': partner2.name, 'partner': (partner2.id, partner2.name), 'tags': [Command.clear()], }), Command.create({ 'name': partner2.name, 'partner': (partner2.id, partner2.name), 'tags': [Command.clear(), Command.create({'name': 'Tag'})], }), ], } self.assertEqual(result['value'], expected_value) # ensure ID is not returned when asked and a many2many record is set to be created self.env.invalidate_all() result = multi.onchange(values, 'partner', dict(field_onchange, **{'lines.tags.id': None})) self.assertEqual(result['value'], expected_value) # ensure inverse of one2many field is not returned self.env.invalidate_all() result = multi.onchange(values, 'partner', dict(field_onchange, **{'lines.multi': None})) self.assertEqual(result['value'], expected_value) def test_onchange_specific(self): """ test the effect of field-specific onchange method """ discussion = self.env.ref('test_new_api.discussion_0') demo = self.user_demo field_onchange = self.Discussion._onchange_spec() self.assertEqual(field_onchange.get('moderator'), '1') self.assertItemsEqual( strip_prefix('participants.', field_onchange), ['display_name'], ) # first remove demo user from participants discussion.participants -= demo self.assertNotIn(demo, discussion.participants) # check that demo_user is added to participants when set as moderator values = { 'name': discussion.name, 'moderator': demo.id, 'categories': [Command.link(cat.id) for cat in discussion.categories], 'messages': [Command.link(msg.id) for msg in discussion.messages], 'participants': [Command.link(usr.id) for usr in discussion.participants], } self.env.invalidate_all() result = discussion.onchange(values, 'moderator', field_onchange) self.assertIn('participants', result['value']) self.assertItemsEqual( result['value']['participants'], [Command.clear()] + [Command.link(user.id) for user in discussion.participants + demo], ) def test_onchange_default(self): """ test the effect of a conditional user-default on a field """ Foo = self.env['test_new_api.foo'] field_onchange = Foo._onchange_spec() self.assertTrue(Foo._fields['value1'].change_default) self.assertEqual(field_onchange.get('value1'), '1') # create a user-defined default based on 'value1' self.env['ir.default'].set('test_new_api.foo', 'value2', 666, condition='value1=42') # setting 'value1' to 42 should trigger the change of 'value2' self.env.invalidate_all() values = {'name': 'X', 'value1': 42, 'value2': False} result = Foo.onchange(values, 'value1', field_onchange) self.assertEqual(result['value'], {'value2': 666}) # setting 'value1' to 24 should not trigger the change of 'value2' self.env.invalidate_all() values = {'name': 'X', 'value1': 24, 'value2': False} result = Foo.onchange(values, 'value1', field_onchange) self.assertEqual(result['value'], {}) def test_onchange_one2many_first(self): partner = self.env['res.partner'].create({ 'name': 'X', 'country_id': self.env.ref('base.be').id, }) with common.Form(self.env['test_new_api.multi']) as form: form.partner = partner self.assertEqual(form.partner, partner) self.assertEqual(form.name, partner.name) with form.lines.new() as line: # the first onchange() must have computed partner self.assertEqual(line.partner, partner) def test_onchange_one2many_value(self): """ test the value of the one2many field inside the onchange """ discussion = self.env.ref('test_new_api.discussion_0') demo = self.user_demo field_onchange = self.Discussion._onchange_spec() self.assertEqual(field_onchange.get('messages'), '1') self.assertEqual(len(discussion.messages), 3) messages = [Command.link(msg.id) for msg in discussion.messages] messages[0] = (1, messages[0][1], {'body': 'test onchange'}) lines = ["%s:%s" % (m.name, m.body) for m in discussion.messages] lines[0] = "%s:%s" % (discussion.messages[0].name, 'test onchange') values = { 'name': discussion.name, 'moderator': demo.id, 'categories': [Command.link(cat.id) for cat in discussion.categories], 'messages': messages, 'participants': [Command.link(usr.id) for usr in discussion.participants], 'message_concat': False, } result = discussion.onchange(values, 'messages', field_onchange) self.assertIn('message_concat', result['value']) self.assertEqual(result['value']['message_concat'], "\n".join(lines)) def test_onchange_one2many_with_domain_on_related_field(self): """ test the value of the one2many field when defined with a domain on a related field""" discussion = self.env.ref('test_new_api.discussion_0') demo = self.user_demo # mimic UI behaviour, so we get subfields # (we need at least subfield: 'important_emails.important') view_info = self.Discussion.get_view(self.env.ref('test_new_api.discussion_form').id, 'form') field_onchange = self.Discussion._onchange_spec(view_info=view_info) self.assertEqual(field_onchange.get('messages'), '1') BODY = "What a beautiful day!" USER = self.env.user # create standalone email email = self.EmailMessage.create({ 'discussion': discussion.id, 'name': "[%s] %s" % ('', USER.name), 'body': BODY, 'author': USER.id, 'important': False, 'email_to': demo.email, }) # check if server-side cache is working correctly self.env.invalidate_all() self.assertIn(email, discussion.emails) self.assertNotIn(email, discussion.important_emails) email.important = True self.assertIn(email, discussion.important_emails) # check that when trigger an onchange, we don't reset important emails # (force `invalidate` as but appear in onchange only when we get a cache # miss) self.env.invalidate_all() self.assertEqual(len(discussion.messages), 4) values = { 'name': "Foo Bar", 'moderator': demo.id, 'categories': [Command.link(cat.id) for cat in discussion.categories], 'messages': [Command.link(msg.id) for msg in discussion.messages], 'participants': [Command.link(usr.id) for usr in discussion.participants], 'important_messages': [Command.link(msg.id) for msg in discussion.important_messages], 'important_emails': [Command.link(eml.id) for eml in discussion.important_emails], } self.env.invalidate_all() result = discussion.onchange(values, 'name', field_onchange) self.assertEqual( result['value']['important_emails'], [Command.clear(), Command.update(email.id, { 'name': u'[Foo Bar] %s' % USER.name, 'body': BODY, 'author': USER.name_get()[0], 'size': len(BODY), 'important': True, 'email_to': demo.email, })], ) def test_onchange_related(self): value = { 'message': 1, 'message_name': False, 'message_currency': 2, } field_onchange = { 'message': '1', 'message_name': None, 'message_currency': None, } onchange_result = { 'message_name': 'Hey dude!', 'message_currency': self.env.user.name_get()[0], } self.env.invalidate_all() Message = self.env['test_new_api.related'] result = Message.onchange(value, 'message', field_onchange) self.assertEqual(result['value'], onchange_result) self.env.invalidate_all() Message = self.env(user=self.user_demo.id)['test_new_api.related'] result = Message.onchange(value, 'message', field_onchange) self.assertEqual(result['value'], onchange_result) def test_onchange_many2one_one2many(self): """ Setting a many2one field should not read the inverse one2many. """ discussion = self.env.ref('test_new_api.discussion_0') field_onchange = self.Message._onchange_spec() self.assertEqual(field_onchange.get('discussion'), '1') values = { 'discussion': discussion.id, 'name': "[%s] %s" % ('', self.env.user.name), 'body': False, 'author': self.env.uid, 'size': 0, } called = [False] orig_read = type(discussion).read def mock_read(self, fields=None, load='_classic_read'): if discussion in self and 'messages' in (fields or ()): called[0] = True return orig_read(self, fields, load) # changing 'discussion' on message should not read 'messages' on discussion with patch.object(type(discussion), 'read', mock_read, create=True): self.env.invalidate_all() self.Message.onchange(values, 'discussion', field_onchange) self.assertFalse(called[0], "discussion.messages has been read") def test_onchange_one2many_many2one_in_form(self): order = self.env['test_new_api.monetary_order'].create({ 'currency_id': self.env.ref('base.USD').id, }) # this call to onchange() is made when creating a new line in field # order.line_ids; check what happens when the line's form view contains # the inverse many2one field values = {'order_id': {'id': order.id, 'currency_id': order.currency_id.id}} field_onchange = dict.fromkeys(['order_id', 'subtotal'], '') result = self.env['test_new_api.monetary_order_line'].onchange(values, [], field_onchange) self.assertEqual(result['value']['order_id'], (order.id, order.display_name)) def test_onchange_inherited(self): """ Setting an inherited field should assign the field on the parent record. """ foo, bar = self.env['test_new_api.multi.tag'].create([{'name': 'Foo'}, {'name': 'Bar'}]) view = self.env['ir.ui.view'].create({ 'name': 'Payment form view', 'model': 'test_new_api.payment', 'arch': """
""", }) # both fields 'tag_id' and 'tag_name' are inherited through 'move_id'; # assigning 'tag_id' should modify 'move_id.tag_id' accordingly, which # should in turn recompute `move.tag_name` and `tag_name` form = Form(self.env['test_new_api.payment'], view) self.assertEqual(form.tag_name, False) form.tag_id = foo self.assertEqual(form.tag_name, 'Foo') self.assertEqual(form.tag_string, '') form.tag_repeat = 2 self.assertEqual(form.tag_name, 'Foo') self.assertEqual(form.tag_string, 'FooFoo') payment = form.save() self.assertEqual(payment.tag_id, foo) self.assertEqual(payment.tag_name, 'Foo') self.assertEqual(payment.tag_repeat, 2) self.assertEqual(payment.tag_string, 'FooFoo') with Form(payment, view) as form: form.tag_id = bar self.assertEqual(form.tag_name, 'Bar') self.assertEqual(form.tag_string, 'BarBar') form.tag_repeat = 3 self.assertEqual(form.tag_name, 'Bar') self.assertEqual(form.tag_string, 'BarBarBar') self.assertEqual(payment.tag_id, bar) self.assertEqual(payment.tag_name, 'Bar') self.assertEqual(payment.tag_repeat, 3) self.assertEqual(payment.tag_string, 'BarBarBar') def test_display_name(self): self.env['ir.ui.view'].create({ 'name': 'test_new_api.multi.tag form view', 'model': 'test_new_api.multi.tag', 'arch': """ """, }) form = common.Form(self.env['test_new_api.multi.tag']) self.assertEqual(form.name, False) self.assertEqual(form.display_name, "") record = form.save() self.assertEqual(record.name, False) self.assertEqual(record.display_name, "") self.assertEqual(record.name_get(), [(record.id, "")]) 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, "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, "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, "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, "foor") self.assertEqual(record.baz, "baz") def test_copy(self): Model = self.env['test_new_api.compute.onchange'] # create tags tag_foo, tag_bar = self.env['test_new_api.multi.tag'].create([ {'name': 'foo1'}, {'name': 'bar1'}, ]) # compute 'bar' (readonly), 'baz', 'line_ids' and 'tag_ids' (editable) record = Model.create({'active': True, 'foo': "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) # manually update 'baz' and 'lines' to test copy attribute record.write({ 'baz': "baz1", 'line_ids': [Command.create({'foo': 'bar'})], 'tag_ids': [Command.link(tag_bar.id)], }) 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) # copy the record, and check results copied = record.copy() self.assertEqual(copied.foo, "foo1 (copy)") # copied and modified 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 def test_write(self): model = self.env['test_new_api.compute.onchange'] record = model.create({'active': True, 'foo': "foo"}) self.assertEqual(record.bar, "foor") self.assertEqual(record.baz, "fooz") # recompute 'bar' (readonly) and 'baz' (editable) record.write({'foo': "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, "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, "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, "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, "foor") self.assertEqual(record.baz, "fooz") # recompute 'bar' (readonly) and 'baz' (editable) record.foo = "foo1" self.assertEqual(record.bar, "foo1r") self.assertEqual(record.baz, "foo1z") # do not recompute 'baz' record.baz = "baz2" 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, "foo1r") self.assertEqual(record.baz, "baz2") # recompute 'baz', but do not change its value record.foo = "foo3" self.assertEqual(record.bar, "foo3r") self.assertEqual(record.baz, "baz2") # do not recompute 'baz' record.baz = "baz4" 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}) self.assertEqual(record.bar, "r") self.assertEqual(record.baz, "z") # recompute 'bar' (readonly) and 'baz' (editable) record.foo = "foo1" self.assertEqual(record.bar, "foo1r") self.assertEqual(record.baz, "foo1z") # do not recompute 'baz' record.baz = "baz2" 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, "foo1r") self.assertEqual(record.baz, "baz2") # recompute 'baz', but do not change its value record.foo = "foo3" self.assertEqual(record.bar, "foo3r") self.assertEqual(record.baz, "baz2") # do not recompute 'baz' record.baz = "baz4" 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, "foo1r") self.assertEqual(form.baz, "foo1z") form.baz = "baz2" self.assertEqual(form.bar, "foo1r") self.assertEqual(form.baz, "baz2") form.active = False self.assertEqual(form.bar, "foo1r") self.assertEqual(form.baz, "baz2") form.foo = "foo3" self.assertEqual(form.bar, "foo3r") self.assertEqual(form.baz, "baz2") form.active = True 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, "foo3r") self.assertEqual(record.baz, "foo3z") form = common.Form(record) self.assertEqual(form.bar, "foo3r") self.assertEqual(form.baz, "foo3z") form.foo = "foo4" self.assertEqual(form.bar, "foo4r") self.assertEqual(form.baz, "foo4z") form.baz = "baz5" self.assertEqual(form.bar, "foo4r") self.assertEqual(form.baz, "baz5") form.active = False self.assertEqual(form.bar, "foo4r") self.assertEqual(form.baz, "baz5") form.foo = "foo6" self.assertEqual(form.bar, "foo6r") self.assertEqual(form.baz, "baz5") def test_onchange_default(self): form = common.Form(self.env['test_new_api.compute.onchange'].with_context( default_active=True, default_foo="foo", default_baz="baz", )) # 'baz' is computed editable, so when given a default value it should # 'not be recomputed, even if a dependency also has a default value self.assertEqual(form.foo, "foo") self.assertEqual(form.bar, "foor") self.assertEqual(form.baz, "baz") def test_onchange_once(self): """ Modifies `foo` field which will trigger an onchange method and checks it was triggered only one time. """ form = Form(self.env['test_new_api.compute.onchange'].with_context(default_foo="oof")) record = form.save() self.assertEqual(record.foo, "oof") self.assertEqual(record.count, 1, "value onchange must be called only one time") def test_onchange_one2many(self): record = self.env['test_new_api.model_parent_m2o'].create({ 'name': 'Family', 'child_ids': [ Command.create({'name': 'W', 'cost': 10}), Command.create({'name': 'X', 'cost': 10}), Command.create({'name': 'Y'}), Command.create({'name': 'Z'}), ], }) self.env.flush_all() self.assertEqual(record.child_ids.mapped('name'), list('WXYZ')) self.assertEqual(record.cost, 22) # modifying a line should not recompute the cost on other lines with common.Form(record) as form: with form.child_ids.edit(1) as line: line.name = 'XXX' self.assertEqual(form.cost, 15) with form.child_ids.edit(1) as line: line.cost = 20 self.assertEqual(form.cost, 32) with form.child_ids.edit(2) as line: line.cost = 30 self.assertEqual(form.cost, 61) def test_onchange_editable_compute_one2many(self): # create a record with a computed editable field ('edit') on lines record = self.env['test_new_api.compute_editable'].create({'line_ids': [(0, 0, {'value': 7})]}) self.env.flush_all() line = record.line_ids self.assertRecordValues(line, [{'value': 7, 'edit': 7, 'count': 0}]) # retrieve the onchange spec for calling 'onchange' spec = Form(record)._view['onchange'] # The onchange on 'line_ids' should increment 'count' and keep the value # of 'edit' (this field should not be recomputed), whatever the order of # the fields in the dictionary. This ensures that the value set by the # user on a computed editable field on a line is not lost. line_ids = [ Command.update(line.id, {'value': 8, 'edit': 9, 'count': 0}), Command.create({'value': 8, 'edit': 9, 'count': 0}), ] result = record.onchange({'line_ids': line_ids}, 'line_ids', spec) expected = {'value': { 'line_ids': [ Command.clear(), Command.update(line.id, {'value': 8, 'edit': 9, 'count': 8}), Command.create({'value': 8, 'edit': 9, 'count': 8}), ], }} self.assertEqual(result, expected) # change dict order in lines, and try again line_ids = [ (op, id_, dict(reversed(list(vals.items())))) for op, id_, vals in line_ids ] result = record.onchange({'line_ids': line_ids}, 'line_ids', spec) self.assertEqual(result, expected) def test_computed_editable_one2many_domain(self): """ Test a computed, editable one2many field with a domain. """ record = self.env['test_new_api.one2many'].create({'name': 'foo'}) self.assertRecordValues(record.line_ids, [ {'name': 'foo', 'count': 1}, ]) # trigger recomputation by changing name record.name = 'bar' self.assertRecordValues(record.line_ids, [ {'name': 'foo', 'count': 1}, {'name': 'bar', 'count': 1}, ]) # manually adding a line should not trigger recomputation record.line_ids.create({'name': 'baz', 'container_id': record.id}) self.assertRecordValues(record.line_ids, [ {'name': 'foo', 'count': 1}, {'name': 'bar', 'count': 1}, {'name': 'baz', 'count': 1}, ]) # changing the field in the domain should not trigger recomputation... record.line_ids[-1].count = 2 self.assertRecordValues(record.line_ids, [ {'name': 'foo', 'count': 1}, {'name': 'bar', 'count': 1}, {'name': 'baz', 'count': 2}, ]) # ...and may show cache inconsistencies record.line_ids[-1].count = 0 self.assertRecordValues(record.line_ids, [ {'name': 'foo', 'count': 1}, {'name': 'bar', 'count': 1}, {'name': 'baz', 'count': 0}, ]) self.env.flush_all() self.env.invalidate_all() self.assertRecordValues(record.line_ids, [ {'name': 'foo', 'count': 1}, {'name': 'bar', 'count': 1}, ])