# -*- coding: utf-8 -*- try: from unittest.mock import patch except ImportError: from mock import patch from odoo.tests import common def strip_prefix(prefix, names): size = len(prefix) return [name[size:] for name in names if name.startswith(prefix)] class TestOnChange(common.TransactionCase): 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.cache.invalidate() 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.cache.invalidate() 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.cache.invalidate() 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.cache.invalidate() 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.cache.invalidate() 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': [ (4, message1.id), (4, message2.id), (1, message2.id, {'body': "XYZ"}), (0, 0, { 'name': "[%s] %s" % ('', USER.name), 'body': "ABC", 'author': USER.id, 'size': 3, 'important': False, }), ], } self.env.cache.invalidate() result = self.Discussion.onchange(values, 'name', field_onchange) self.assertIn('messages', result['value']) self.assertEqual(result['value']['messages'], [ (5,), (1, message1.id, { 'name': "[%s] %s" % ("Foo", USER.name), 'body': "ABC", 'author': USER.name_get()[0], 'size': 3, 'important': False, }), (1, message2.id, { 'name': "[%s] %s" % ("Foo", USER.name), 'body': "XYZ", # this must be sent back 'author': USER.name_get()[0], 'size': 3, 'important': False, }), (0, 0, { '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'], [ (5,), (4, message1.id), (4, message2.id), (0, 0, {}), (0, 0, {}), ]) 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.cache.invalidate() result = self.Discussion.onchange(values, 'name', field_onchange) self.assertIn('messages', result['value']) self.assertItemsEqual(result['value']['messages'], [ (5,), (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.ref('base.res_partner_1') 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': [(6, 0, [line1.id])], }) # modify 'partner' # -> set 'partner' on all lines # -> recompute 'name' # -> set 'name' on all lines partner2 = self.env.ref('base.res_partner_2') values = { 'name': partner1.name, 'partner': partner2.id, # this one just changed 'lines': [(6, 0, [line1.id]), (0, 0, {'name': False, 'partner': False, 'tags': [(5,)]})], } self.env.cache.invalidate() result = multi.onchange(values, 'partner', field_onchange) self.assertEqual(result['value'], { 'name': partner2.name, 'lines': [ (5,), (1, line1.id, { 'name': partner2.name, 'partner': (partner2.id, partner2.name), 'tags': [(5,)], }), (0, 0, { 'name': partner2.name, 'partner': (partner2.id, partner2.name), 'tags': [(5,)], }), ], }) # 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': [(6, 0, [line1.id]), (0, 0, {'name': False, 'partner': False, 'tags': [(5,), (0, 0, {'name': 'Tag'})]})], } self.env.cache.invalidate() result = multi.onchange(values, 'partner', field_onchange) expected_value = { 'name': partner2.name, 'lines': [ (5,), (1, line1.id, { 'name': partner2.name, 'partner': (partner2.id, partner2.name), 'tags': [(5,)], }), (0, 0, { 'name': partner2.name, 'partner': (partner2.id, partner2.name), 'tags': [(5,), (0, 0, {'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.cache.invalidate() 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.cache.invalidate() 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.env.ref('base.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': [(4, cat.id) for cat in discussion.categories], 'messages': [(4, msg.id) for msg in discussion.messages], 'participants': [(4, usr.id) for usr in discussion.participants], } self.env.cache.invalidate() result = discussion.onchange(values, 'moderator', field_onchange) self.assertIn('participants', result['value']) self.assertItemsEqual( result['value']['participants'], [(5,)] + [(4, 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.cache.invalidate() 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.cache.invalidate() values = {'name': 'X', 'value1': 24, 'value2': False} result = Foo.onchange(values, 'value1', field_onchange) self.assertEqual(result['value'], {}) 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.env.ref('base.user_demo') field_onchange = self.Discussion._onchange_spec() self.assertEqual(field_onchange.get('messages'), '1') self.assertEqual(len(discussion.messages), 3) messages = [(4, 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': [(4, cat.id) for cat in discussion.categories], 'messages': messages, 'participants': [(4, 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.env.ref('base.user_demo') # mimic UI behaviour, so we get subfields # (we need at least subfield: 'important_emails.important') view_info = self.Discussion.fields_view_get( view_id=self.env.ref('test_new_api.discussion_form').id, view_type='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.cache.invalidate() 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.cache.invalidate() self.assertEqual(len(discussion.messages), 4) values = { 'name': "Foo Bar", 'moderator': demo.id, 'categories': [(4, cat.id) for cat in discussion.categories], 'messages': [(4, msg.id) for msg in discussion.messages], 'participants': [(4, usr.id) for usr in discussion.participants], 'important_messages': [(4, msg.id) for msg in discussion.important_messages], 'important_emails': [(4, eml.id) for eml in discussion.important_emails], } self.env.cache.invalidate() result = discussion.onchange(values, 'name', field_onchange) self.assertEqual( result['value']['important_emails'], [(5,), (1, 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.cache.invalidate() Message = self.env['test_new_api.related'] result = Message.onchange(value, ['message', 'message_name', 'message_currency'], field_onchange) self.assertEqual(result['value'], onchange_result) self.env.cache.invalidate() Message = self.env(user=self.env.ref('base.user_demo').id)['test_new_api.related'] result = Message.onchange(value, ['message', 'message_name', 'message_currency'], 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.cache.invalidate() self.Message.onchange(values, 'discussion', field_onchange) self.assertFalse(called[0], "discussion.messages has been read")