# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from unittest.mock import patch from odoo import fields from odoo.addons.mail.tests.common import MailCommon from odoo.addons.mail.tools.discuss import Store from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE from odoo.tests import Form, tagged, users from odoo.tools import mute_logger @tagged('mail_track') class TestTracking(MailCommon): @classmethod def setUpClass(cls): super().setUpClass() record = cls.env['mail.test.ticket'].with_user( cls.user_employee ).with_context(cls._test_context).create({ 'name': 'Test', }) cls.record = record.with_context(mail_notrack=False) def test_message_track_author(self): """ Check that the author of the log note matches the user at the time of writing. """ with self.mock_mail_gateway(): self.record._track_set_author(self.partner_admin) self.record.write({ 'customer_id': self.partner_employee.id, }) self.flush_tracking() self.assertEqual(len(self.record.message_ids), 1) self.assertEqual(len(self.record.message_ids.tracking_value_ids), 1) self.assertEqual(self.record.message_ids.author_id, self.partner_admin) @users('employee') def test_message_track_default_message(self): """Check that the default tracking log message defined on the model is used and that setting a log message overrides it. See `_track_get_default_log_message`""" record = self.env['mail.test.track'].with_context(self._test_context).create({ 'name': 'Test', 'track_enable_default_log': True, }).with_context(mail_notrack=False) self.flush_tracking() with self.mock_mail_gateway(): record.user_id = self.user_admin self.flush_tracking() messages = record.message_ids self.assertEqual(len(messages), 1) self.assertEqual(messages.body, '
There was a change on Test for fields "user_id"
', 'Default message should be used') with self.mock_mail_gateway(): record._track_set_log_message('Hi') record.user_id = False self.flush_tracking() messages = record.message_ids - messages self.assertEqual(len(messages), 1) self.assertEqual(messages.body, 'Hi
', '_track_set_log_message should take priority over default message') @users('employee') def test_message_track_filter_for_display(self): """Check that tracked fields filtered for display are not present in the front-end and email formatting methods. See `_track_filter_for_display`""" field_dname = 'Responsible' field_name = 'user_id' field_type = 'many2one' original_user = self.user_admin new_user = self.user_employee records = self.env['mail.test.track'].create([{ 'name': 'TestTrack Hide User Field', 'user_id': original_user.id, 'track_fields_tofilter': 'user_id', }, { 'name': 'TestTrack Show All Fields', 'user_id': original_user.id, 'track_fields_tofilter': '', }]) self.flush_tracking() records.write({'user_id': new_user.id}) self.flush_tracking() for record in records: self.assertEqual(len(record.message_ids), 2, 'Should be a creation message and a tracking message') self.assertTracking( record.message_ids[0], [('user_id', 'many2one', original_user, new_user)] ) # first record: tracking value should be hidden message_0 = records[0].message_ids[0] formatted = Store(message_0, for_current_user=True).get_result()["mail.message"][0] self.assertEqual(formatted['trackingValues'], [], 'Hidden values should not be formatted') mail_render = records[0]._notify_by_email_prepare_rendering_context(message_0, {}) self.assertEqual(mail_render['tracking_values'], []) # second record: all values displayed message_1 = records[1].message_ids[0] formatted = Store(message_1, for_current_user=True).get_result()["mail.message"][0] self.assertEqual(len(formatted['trackingValues']), 1) self.assertDictEqual( formatted['trackingValues'][0], { 'changedField': field_dname, 'fieldName': field_name, 'fieldType': field_type, 'id': message_1.tracking_value_ids.id, 'newValue': { 'currencyId': False, 'value': new_user.display_name, }, 'oldValue': { 'currencyId': False, 'value': original_user.display_name, }, }) mail_render = records[1]._notify_by_email_prepare_rendering_context(message_1, {}) self.assertEqual(mail_render['tracking_values'], [(field_dname, original_user.display_name, new_user.display_name)]) def test_message_track_message_type(self): """Check that the right message type is applied for track templates.""" self.record.message_subscribe( partner_ids=[self.user_admin.partner_id.id], subtype_ids=[self.env.ref('mail.mt_comment').id] ) mail_templates = self.env['mail.template'].create([{ 'name': f'Template {n}', 'subject': f'Template {n}', 'model_id': self.env.ref('test_mail.model_mail_test_ticket').id, 'body_html': f'Template {n}
', } for n in range(2)]) def _track_subtype(self, init_values): return self.env.ref('mail.mt_note') self.patch(self.registry('mail.test.ticket'), '_track_subtype', _track_subtype) def _track_template(self, changes): if 'email_from' in changes: return {'email_from': (mail_templates[0], {})} elif 'container_id' in changes: return {'container_id': (mail_templates[1], {'message_type': 'notification'})} return {} self.patch(self.registry('mail.test.ticket'), '_track_template', _track_template) container = self.env['mail.test.container'].create({'name': 'Container'}) # default is auto_comment with self.mock_mail_gateway(): self.record.email_from = 'test@test.lan' self.flush_tracking() first_message = self.record.message_ids.filtered(lambda message: message.subject == 'Template 0') self.assertEqual(len(self.record.message_ids), 2, 'Should be one change message and one automated template') self.assertEqual(first_message.message_type, 'auto_comment') # auto_comment can be overriden by _track_template with self.mock_mail_gateway(mail_unlink_sent=False): self.record.container_id = container self.flush_tracking() second_message = self.record.message_ids.filtered(lambda message: message.subject == 'Template 1') self.assertEqual(len(self.record.message_ids), 4, 'Should have added one change message and one automated template') self.assertEqual(second_message.message_type, 'notification') def test_message_track_multiple(self): """ check that multiple updates generate a single tracking message """ container = self.env['mail.test.container'].with_context(mail_create_nosubscribe=True).create({'name': 'Container'}) self.record.name = 'Zboub' self.record.customer_id = self.user_admin.partner_id self.record.user_id = self.user_admin self.record.container_id = container self.flush_tracking() # should have a single message with all tracked fields self.assertEqual(len(self.record.message_ids), 1, 'should have 1 tracking message') self.assertEqual(self.record.message_ids.author_id, self.partner_employee) self.assertTracking(self.record.message_ids[0], [ ('customer_id', 'many2one', False, self.user_admin.partner_id), ('user_id', 'many2one', False, self.user_admin), ('container_id', 'many2one', False, container), ]) def test_message_track_no_subtype(self): """ Update some tracked fields not linked to some subtype -> message with onchange """ customer = self.env['res.partner'].create({'name': 'Customer', 'email': 'cust@example.com'}) with self.mock_mail_gateway(): self.record.write({ 'name': 'Test2', 'customer_id': customer.id, }) self.flush_tracking() # one new message containing tracking; without subtype linked to tracking, a note is generated self.assertEqual(len(self.record.message_ids), 1) self.assertEqual(self.record.message_ids.author_id, self.partner_employee) self.assertEqual(self.record.message_ids.subtype_id, self.env.ref('mail.mt_note')) # no specific recipients except those following notes, no email self.assertEqual(self.record.message_ids.partner_ids, self.env['res.partner']) self.assertEqual(self.record.message_ids.notified_partner_ids, self.env['res.partner']) self.assertNotSentEmail() # verify tracked value self.assertTracking( self.record.message_ids, [('customer_id', 'many2one', False, customer) # onchange tracked field ]) @users('employee') def test_message_track_no_tracking(self): """ Update a set of non tracked fields -> no message, no tracking, or use dedicated context key """ record = self.record.with_env(self.env) record.write({ 'name': 'Tracking or not', 'count': 32, }) self.flush_tracking() self.assertFalse(record.message_ids) # check context key allowing to skip tracking record.with_context(mail_notrack=True).write({'email_from': 'new.from@test.example.com'}) self.flush_tracking() self.assertFalse(record.message_ids) def test_message_track_subtype(self): """ Update some tracked fields linked to some subtype -> message with onchange """ self.record.message_subscribe( partner_ids=[self.user_admin.partner_id.id], subtype_ids=[self.env.ref('test_mail.st_mail_test_ticket_container_upd').id] ) container = self.env['mail.test.container'].with_context(mail_create_nosubscribe=True).create({'name': 'Container'}) self.record.write({ 'name': 'Test2', 'email_from': 'noone@example.com', 'container_id': container.id, }) self.flush_tracking() # one new message containing tracking; subtype linked to tracking self.assertEqual(len(self.record.message_ids), 1) self.assertEqual(self.record.message_ids.author_id, self.partner_employee) self.assertEqual(self.record.message_ids.subtype_id, self.env.ref('test_mail.st_mail_test_ticket_container_upd')) # no specific recipients except those following container self.assertEqual(self.record.message_ids.partner_ids, self.env['res.partner']) self.assertEqual(self.record.message_ids.notified_partner_ids, self.user_admin.partner_id) # verify tracked value self.assertTracking( self.record.message_ids, [('container_id', 'many2one', False, container) # onchange tracked field ]) @mute_logger('odoo.addons.mail.models.mail_mail') def test_message_track_template(self): """ Update some tracked fields linked to some template -> message with onchange """ self.record.write({'mail_template': self.env.ref('test_mail.mail_test_ticket_tracking_tpl').id}) self.assertEqual(self.record.message_ids, self.env['mail.message']) with self.mock_mail_gateway(): self.record.write({ 'name': 'Test2', 'customer_id': self.user_admin.partner_id.id, }) self.flush_tracking() self.assertEqual(len(self.record.message_ids), 2, 'should have 2 new messages: one for tracking, one for template') # one new message containing the template linked to tracking self.assertEqual(self.record.message_ids[0].author_id, self.partner_employee) self.assertEqual(self.record.message_ids[0].subject, 'Test Template') self.assertEqual(self.record.message_ids[0].body, 'Hello Test2
') # one email send due to template self.assertSentEmail(self.record.env.user.partner_id, [self.partner_admin], body='Hello Test2
') # one new message containing tracking; without subtype linked to tracking self.assertEqual(self.record.message_ids[1].subtype_id, self.env.ref('mail.mt_note')) self.assertTracking( self.record.message_ids[1], [('customer_id', 'many2one', False, self.user_admin.partner_id) # onchange tracked field ]) @mute_logger('odoo.addons.mail.models.mail_mail') def test_message_track_template_at_create(self): """ Create a record with tracking template on create, template should be sent.""" Model = self.env['mail.test.ticket'].with_user(self.user_employee).with_context(self._test_context) Model = Model.with_context(mail_notrack=False) with self.mock_mail_gateway(): record = Model.create({ 'name': 'Test', 'customer_id': self.user_admin.partner_id.id, 'mail_template': self.env.ref('test_mail.mail_test_ticket_tracking_tpl').id, }) self.flush_tracking() self.assertEqual(len(record.message_ids), 1, 'should have 1 new messages for template') # one new message containing the template linked to tracking self.assertEqual(record.message_ids[0].author_id, self.partner_employee) self.assertEqual(record.message_ids[0].subject, 'Test Template') self.assertEqual(record.message_ids[0].body, 'Hello Test
') # one email send due to template self.assertSentEmail(self.record.env.user.partner_id, [self.partner_admin], body='Hello Test
') @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread') def test_message_track_template_at_create_from_message(self): """Make sure records created through aliasing show the original message before the template""" # setup test_model = self.env['ir.model']._get('mail.test.ticket') original_sender = self.user_admin.partner_id custom_values = {'name': 'Test', 'customer_id': original_sender.id, 'mail_template': self.env.ref('test_mail.mail_test_ticket_tracking_tpl').id} self.env['mail.alias'].create({ 'alias_name': 'groups', 'alias_model_id': test_model.id, 'alias_contact': 'everyone', 'alias_defaults': custom_values}) record = self.format_and_process(MAIL_TEMPLATE, '"Sylvie Lelitre"Hello {custom_values['name']}
", } ) @mute_logger('odoo.addons.mail.models.mail_mail') def test_message_track_template_create_partner_multicompany(self): """ Test partner created due to usage of a mail.template, triggered by a tracking, in a multi company environment. """ company1 = self.env['res.company'].create({'name': 'company1'}) self.env.user.write({'company_ids': [(4, company1.id, False)]}) self.assertNotEqual(self.env.company, company1) email_new_partner = "diamonds@rust.com" Partner = self.env['res.partner'] self.assertFalse(Partner.search([('email', '=', email_new_partner)])) template = self.env['mail.template'].create({ 'model_id': self.env['ir.model']._get('mail.test.track').id, 'name': 'AutoTemplate', 'subject': 'autoresponse', 'email_from': self.env.user.email_formatted, 'email_to': "{{ object.email_from }}", 'body_html': "Html Value
', 'integer_field': 42, 'many2one_field_id': self.test_partner.id, 'monetary_field': 42.42, 'selection_field': 'first', 'text_field': 'text_value', }) self.flush_tracking() new_message = test_record.message_ids - messages self.assertEqual(len(new_message), 1, 'Should have generated a tracking value') self.assertTracking( new_message, [ ('boolean_field', 'boolean', 0, 1), ('char_field', 'char', False, 'char_value'), ('date_field', 'date', False, today_dt), ('datetime_field', 'datetime', False, now), ('float_field', 'float', 0, 3.22), ('integer_field', 'integer', 0, 42), ('many2one_field_id', 'many2one', self.env['res.partner'], self.test_partner), ('monetary_field', 'monetary', False, (42.42, self.env.ref('base.USD'))), ('selection_field', 'selection', '', 'FIRST'), ('text_field', 'text', False, 'text_value'), ], strict=True ) @users('employee') def test_mail_track_compute(self): """ Test tracking of computed fields """ # no tracking at creation compute_record = self.env['mail.test.track.compute'].create({}) self.flush_tracking() self.assertEqual(len(compute_record.message_ids), 1) self.assertEqual(len(compute_record.message_ids[0].tracking_value_ids), 0) # assign partner_id: one tracking message for the modified field and all # the stored and non-stored computed fields on the record partner_su = self.env['res.partner'].sudo().create({ 'name': 'Foo', 'email': 'foo@example.com', 'phone': '1234567890', }) compute_record.partner_id = partner_su self.flush_tracking() self.assertEqual(len(compute_record.message_ids), 2) self.assertEqual(len(compute_record.message_ids[0].tracking_value_ids), 4) self.assertEqual(compute_record.message_ids.author_id, self.partner_employee) self.assertTracking(compute_record.message_ids[0], [ ('partner_id', 'many2one', False, partner_su), ('partner_name', 'char', False, 'Foo'), ('partner_email', 'char', False, 'foo@example.com'), ('partner_phone', 'char', False, '1234567890'), ]) # modify partner: one tracking message for the only recomputed field partner_su.write({'name': 'Fool'}) self.flush_tracking() self.assertEqual(len(compute_record.message_ids), 3) self.assertEqual(len(compute_record.message_ids[0].tracking_value_ids), 1) self.assertTracking(compute_record.message_ids[0], [ ('partner_name', 'char', 'Foo', 'Fool'), ]) # modify partner: one tracking message for both stored computed fields; # the non-stored computed fields have no tracking partner_su.write({ 'name': 'Bar', 'email': 'bar@example.com', 'phone': '0987654321', }) # force recomputation of 'partner_phone' to make sure it does not # generate tracking values self.assertEqual(compute_record.partner_phone, '0987654321') self.flush_tracking() self.assertEqual(len(compute_record.message_ids), 4) self.assertEqual(len(compute_record.message_ids[0].tracking_value_ids), 2) self.assertTracking(compute_record.message_ids[0], [ ('partner_name', 'char', 'Fool', 'Bar'), ('partner_email', 'char', 'foo@example.com', 'bar@example.com'), ]) @users('employee') def test_mail_track_monetary(self): """ Update a record with a tracked monetary field """ monetary_record = self.env['mail.test.track.monetary'].with_user(self.user_employee).create({ 'company_id': self.user_employee.company_id.id, }) self.flush_tracking() self.assertEqual(len(monetary_record.message_ids), 1) # Check if the tracking value have the correct currency and values monetary_record.write({ 'revenue': 100, }) self.flush_tracking() self.assertEqual(len(monetary_record.message_ids), 2) self.assertTracking(monetary_record.message_ids[0], [ ('revenue', 'monetary', 0, (100, self.env.company.currency_id)), ]) # Check if the tracking value have the correct currency and values after changing the value and the company monetary_record.write({ 'revenue': 200, 'company_id': self.company_2.id, }) self.flush_tracking() self.assertEqual(len(monetary_record.message_ids), 3) self.assertTracking(monetary_record.message_ids[0], [ ('revenue', 'monetary', 100, (200, self.company_2.currency_id)), ('company_currency', 'many2one', self.user_employee.company_id.currency_id, self.company_2.currency_id) ]) @users('employee') def test_mail_track_selection_invalid(self): """ Check that initial invalid selection values are allowed when tracking """ # Create a record with an initially invalid selection value invalid_value = 'I love writing tests!' record = self.env['mail.test.track.selection'].create({ 'name': 'Test Invalid Selection Values', 'selection_type': 'first', }) self.flush_tracking() self.env.cr.execute( """ UPDATE mail_test_track_selection SET selection_type = %s WHERE id = %s """, [invalid_value, record.id] ) record.invalidate_recordset() self.assertEqual(record.selection_type, invalid_value) # Write a valid selection value record.selection_type = "second" self.flush_tracking() self.assertTracking(record.message_ids, [ ('selection_type', 'char', invalid_value, 'Second'), ]) def test_track_groups(self): """ Test field groups and filtering when using standard helpers """ # say that 'email_from' is accessible to erp_managers only field = self.record._fields['email_from'] self.addCleanup(setattr, field, 'groups', field.groups) field.groups = 'base.group_erp_manager' self.record.sudo().write({'email_from': 'X'}) self.flush_tracking() msg_emp = Store(self.record.message_ids, for_current_user=True).get_result() msg_admin = Store( self.record.with_user(self.user_admin).message_ids, for_current_user=True ).get_result() msg_sudo = Store(self.record.sudo().message_ids, for_current_user=True).get_result() tracking_values = self.env['mail.tracking.value'].search([('mail_message_id', '=', self.record.message_ids[0].id)]) formatted_tracking_values = [{ 'changedField': 'Email From', 'id': tracking_values[0]['id'], 'fieldName': 'email_from', 'fieldType': 'char', 'newValue': { 'currencyId': False, 'value': 'X', }, 'oldValue': { 'currencyId': False, 'value': False, }, }] self.assertEqual( msg_emp["mail.message"][0].get("trackingValues"), [], "should not have protected tracking values", ) self.assertEqual( msg_admin["mail.message"][0].get("trackingValues"), formatted_tracking_values, "should have protected tracking values", ) self.assertEqual( msg_sudo["mail.message"][0].get("trackingValues"), formatted_tracking_values, "should have protected tracking values", ) values_emp = self.record._notify_by_email_prepare_rendering_context(self.record.message_ids[0], {}) values_admin = self.record.with_user(self.user_admin)._notify_by_email_prepare_rendering_context(self.record.message_ids[0], {}) values_sudo = self.record.sudo()._notify_by_email_prepare_rendering_context(self.record.message_ids[0], {}) self.assertFalse(values_emp.get('tracking_values'), "should not have protected tracking values") self.assertTrue(values_admin.get('tracking_values'), "should have protected tracking values") self.assertTrue(values_sudo.get('tracking_values'), "should have protected tracking values") # test editing the record with user not in the group of the field self.env.invalidate_all() self.env.registry.clear_cache() record_form = Form(self.record.with_user(self.user_employee)) record_form.name = 'TestDoNoCrash' # the employee user must be able to save the fields on which they can write # if we fetch all the tracked fields, ignoring the group of the current user # it will crash and it shouldn't record = record_form.save() self.assertEqual(record.name, 'TestDoNoCrash') @users('employee') def test_track_invalid(self): """ Test invalid use cases: unknown field, unsupported type, ... """ test_record = self.env['mail.test.track.all'].create({ 'company_id': self.env.company.id, }) self.flush_tracking() # raise on non existing field with self.assertRaises(ValueError): self.env['mail.tracking.value']._create_tracking_values( '', 'Test', 'not_existing_field', {'string': 'Test', 'type': 'char'}, test_record, ) # raise on unsupported field type with self.assertRaises(NotImplementedError): self.env['mail.tracking.value']._create_tracking_values( '', 'Html
', 'html_field', {'string': 'HTML', 'type': 'html'}, test_record, ) @users('employee') def test_track_multi_models(self): """ Some models track value coming from another model e.g. when having a sub model (lines) on which some value should be tracked on a parent model. Test there is no model mismatch. """ main_track = self.env['mail.test.track.all'].create({ 'name': 'Multi Models Tracking', 'char_field': 'char_value', }) self.flush_tracking() self.assertEqual(len(main_track.message_ids), 1) self.assertFalse(main_track.message_ids.tracking_value_ids) sub_track = self.env['mail.test.track.groups'].create({ 'name': 'Groups', 'secret': 'secret', }) # some custom code generates tracking values on main_track main_track.message_post( body='Custom Log with Tracking', tracking_value_ids=[ (0, 0, { 'field_id': self.env['ir.model.fields']._get(sub_track._name, 'secret').id, 'new_value_char': 'secret', 'old_value_char': False, }), (0, 0, { 'field_id': False, 'new_value_integer': self.env.uid, 'old_value_integer': False, }), (0, 0, { 'field_id': False, 'field_info': { 'desc': 'Old integer', 'name': 'Removed', 'sequence': 35, 'type': 'integer', }, 'new_value_integer': 35, 'old_value_integer': 30, }), ], ) trackings = main_track.message_ids.sudo().tracking_value_ids self.assertEqual(len(trackings), 3) # check groups, as it depends on model for tracking, exp_groups in zip(trackings, ['base.group_user', 'base.group_system', 'base.group_system']): groups = 'base.group_system' if tracking.field_id: field = self.env[tracking.field_id.model]._fields[tracking.field_id.name] groups = field.groups self.assertEqual(groups, exp_groups) # check formatting, as it fetches info on model formatted = trackings._tracking_value_format() self.assertEqual( formatted, [ { 'changedField': 'Secret', 'id': trackings[0].id, 'fieldName': 'secret', 'fieldType': 'char', 'newValue': {'currencyId': False, 'value': 'secret'}, 'oldValue': {'currencyId': False, 'value': False} }, { 'changedField': 'Old integer', 'id': trackings[2].id, 'fieldName': 'Removed', 'fieldType': 'integer', 'newValue': {'currencyId': False, 'value': 35}, 'oldValue': {'currencyId': False, 'value': 30} }, { 'changedField': 'Unknown', 'id': trackings[1].id, 'fieldName': 'unknown', 'fieldType': 'char', 'newValue': {'currencyId': False, 'value': False}, 'oldValue': {'currencyId': False, 'value': False} } ] ) @users('employee') def test_track_sequence(self): """ Update some tracked fields and check that the mail.tracking.value are ordered according to their tracking_sequence """ record = self.record.with_env(self.env) self.assertEqual(len(record.message_ids), 1) # order: user_id -> 1, customer_id -> 2, container_id -> True -> 100, email_from -> True -> 100 ordered_fnames = ['user_id', 'customer_id', 'container_id', 'email_from'] # Update tracked fields, should generate tracking values correctly ordered record.write({ 'container_id': self.env['mail.test.container'].with_context(mail_create_nosubscribe=True).create({'name': 'Container'}).id, 'customer_id': self.user_admin.partner_id.id, 'email_from': 'new.from@test.example.com', 'name': 'Zboub', 'user_id': self.user_admin.id, }) self.flush_tracking() self.assertEqual(len(record.message_ids), 2, 'should have 1 new tracking message') tracking_values = self.env['mail.tracking.value'].sudo().search( [('mail_message_id', '=', record.message_ids[0].id)] ) self.assertEqual( tracking_values.field_id.mapped('name'), ordered_fnames, 'Track: order, based on ID DESC, should follow tracking sequence (or name) on field' ) # Manually create trackings, format should be the fallback to reorder them new_msg = record.message_post( body='Manual Hack of tracking', subtype_xmlid='mail.mt_note', ) custom_order_fnames = ['container_id', 'customer_id', 'email_from', 'user_id'] field_ids = [ self.env['ir.model.fields']._get(record._name, fname).id for fname in custom_order_fnames ] self.env['mail.tracking.value'].sudo().create([ { 'field_id': field_id, 'mail_message_id': new_msg.id, 'old_value_char': 'unimportant', 'new_value_char': 'unimportant', } for field_id in field_ids ]) tracking_values = self.env['mail.tracking.value'].sudo().search( [('mail_message_id', '=', record.message_ids[0].id)] ) self.assertEqual( tracking_values.field_id.mapped('name'), list(reversed(custom_order_fnames)), 'Tracking model: order, based on ID DESC, following reverted insertion' ) tracking_formatted = tracking_values._tracking_value_format() self.assertEqual( [t['fieldName'] for t in tracking_formatted], ordered_fnames, 'Track: formatted order is correctly based on field sequence definition' ) @users('employee') def test_unlinked_field(self): """ Check that removing a field removes its tracking values. """ record = self.record.with_env(self.env) record.write({'email_from': 'new_value'}) # create a tracking value record_other = self.env['mail.test.ticket'].create({}) self.flush_tracking() record_other.write({'email_from': 'email.from.1@example.com'}) self.flush_tracking() record_other.write({ 'customer_id': self.test_partner.id, 'email_from': 'email.from.2@example.com', 'user_id': self.env.user.id, }) self.flush_tracking() self.assertTracking( record.message_ids[0], [('email_from', 'char', False, 'new_value')], strict=True, ) self.assertTracking( record_other.message_ids[0], [('customer_id', 'integer', False, self.test_partner.id), ('email_from', 'char', 'email.from.1@example.com', 'email.from.2@example.com'), ('user_id', 'integer', False, self.env.user.id)], strict=True, ) self.assertTracking( record_other.message_ids[1], [('email_from', 'char', False, 'email.from.1@example.com')], strict=True, ) # check display / format trackings_all = (record + record_other).message_ids.sudo().tracking_value_ids trackings_all_sorted = [ trackings_all.filtered(lambda t: t.field_id.name == 'user_id'), # tracking=1 trackings_all.filtered(lambda t: t.field_id.name == 'customer_id'), # tracking=2 trackings_all.filtered(lambda t: t.field_id.name == 'email_from')[0], # tracking=True -> 100 trackings_all.filtered(lambda t: t.field_id.name == 'email_from')[1], # tracking=True -> 100 trackings_all.filtered(lambda t: t.field_id.name == 'email_from')[2], # tracking=True -> 100 ] fields_info = [ ('user_id', 'many2one', 'Responsible'), ('customer_id', 'many2one', 'Customer'), ('email_from', 'char', 'Email From'), ('email_from', 'char', 'Email From'), ('email_from', 'char', 'Email From'), ] values_info = [ ('', self.env.user.name), ('', self.test_partner.name), (False, 'new_value'), ('email.from.1@example.com', 'email.from.2@example.com'), (False, 'email.from.1@example.com'), ] formatted = trackings_all._tracking_value_format() self.assertEqual( formatted, [ { 'changedField': field_info[2], 'id': tracking.id, 'fieldName': field_info[0], 'fieldType': field_info[1], 'newValue': { 'currencyId': False, 'value': values[1], }, 'oldValue': { 'currencyId': False, 'value': values[0], }, } for tracking, field_info, values in zip(trackings_all_sorted, fields_info, values_info) ] ) # remove fields fields_toremove = self.env['ir.model.fields'].sudo().search([ ('model', '=', 'mail.test.ticket'), ('name', 'in', ('email_from', 'user_id', 'datetime')) # also include a non tracked field ]) fields_toremove.with_context(_force_unlink=True).unlink() self.assertEqual(len(trackings_all.exists()), 5) # check display / format, even if field is removed formatted = trackings_all._tracking_value_format() self.assertEqual( formatted, [ { 'changedField': field_info[2], 'id': tracking.id, 'fieldName': field_info[0], 'fieldType': field_info[1], 'newValue': { 'currencyId': False, 'value': values[1], }, 'oldValue': { 'currencyId': False, 'value': values[0], }, } for tracking, field_info, values in zip(trackings_all_sorted, fields_info, values_info) ] )