Odoo18-Base/addons/test_mail/tests/test_message_track.py
2025-01-06 10:57:38 +07:00

1029 lines
44 KiB
Python

# -*- 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, '<p>There was a change on Test for fields "user_id"</p>',
'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, '<p>Hi</p>', '_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'<p>Template {n}</p>',
} 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, '<p>Hello Test2</p>')
# one email send due to template
self.assertSentEmail(self.record.env.user.partner_id, [self.partner_admin], body='<p>Hello Test2</p>')
# 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, '<p>Hello Test</p>')
# one email send due to template
self.assertSentEmail(self.record.env.user.partner_id, [self.partner_admin], body='<p>Hello Test</p>')
@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" <test.sylvie.lelitre@agrolait.com>',
'groups@test.mycompany.com', target_field='customer_id', subject=custom_values['customer_id'],
target_model='mail.test.ticket')
with self.mock_mail_gateway(mail_unlink_sent=False):
self.flush_tracking()
# Should be trigger message and response template
self.assertEqual(len(record.message_ids), 2)
messages = list(record.message_ids)
messages.sort(key=lambda msg: msg.id)
trigger = messages[0]
template = messages[1]
self.assertIn('Please call me as soon as possible this afternoon!', trigger.body)
self.assertIn(f"Hello {custom_values['name']}", template.body)
self.assertMailMail(
original_sender,
'sent',
author=self.env.ref('base.partner_root'),
email_values={
'body_content': f"<p>Hello {custom_values['name']}</p>",
}
)
@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': "<div>A nice body</div>",
})
def patched_message_track_post_template(*args, **kwargs):
if args[0]._name == "mail.test.track":
args[0].message_post_with_source(template)
return True
with patch('odoo.addons.mail.models.mail_thread.MailThread._message_track_post_template', patched_message_track_post_template):
self.env['mail.test.track'].create({
'email_from': email_new_partner,
'company_id': company1.id,
'user_id': self.env.user.id, # trigger track template
})
self.flush_tracking()
new_partner = Partner.search([('email', '=', email_new_partner)])
self.assertTrue(new_partner)
self.assertEqual(new_partner.company_id, company1)
def test_message_track_template_defaults(self):
""" Check that default_* keys are not taken into account in
_message_track_post_template """
magic_code = 'Up-Up-Down-Down-Left-Right-Left-Right-Square-Triangle'
mt_name_changed = self.env['mail.message.subtype'].create({
'name': 'MAGIC CODE WOOP WOOP',
'description': 'SPECIAL CONTENT UNLOCKED'
})
self.env['ir.model.data'].create({
'name': 'mt_name_changed',
'model': 'mail.message.subtype',
'module': 'mail',
'res_id': mt_name_changed.id
})
mail_template = self.env['mail.template'].create({
'name': 'SPECIAL CONTENT UNLOCKED',
'subject': 'SPECIAL CONTENT UNLOCKED',
'model_id': self.env.ref('test_mail.model_mail_test_container').id,
'auto_delete': True,
'body_html': '''<div>WOOP WOOP</div>''',
})
def _track_subtype(self, init_values):
if 'name' in init_values and init_values['name'] == magic_code:
return 'mail.mt_name_changed'
return False
self.patch(self.registry('mail.test.container'), '_track_subtype', _track_subtype)
def _track_template(self, changes):
res = {}
if 'name' in changes:
res['name'] = (mail_template, {'composition_mode': 'mass_mail'})
return res
self.patch(self.registry('mail.test.container'), '_track_template', _track_template)
cls = type(self.env['mail.test.container'])
self.assertFalse(hasattr(getattr(cls, 'name'), 'track_visibility'))
getattr(cls, 'name').track_visibility = 'always'
@self.addCleanup
def cleanup():
del getattr(cls, 'name').track_visibility
test_mail_record = self.env['mail.test.container'].create({
'name': 'Zizizatestmailname',
'description': 'Zizizatestmaildescription',
})
test_mail_record.with_context(default_parent_id=2147483647).write({'name': magic_code})
@tagged('mail_track')
class TestTrackingInternals(MailCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.record = cls.env['mail.test.ticket'].with_user(cls.user_employee).create({
'name': 'Test',
})
cls.test_partner = cls.env['res.partner'].create({
'country_id': cls.env.ref('base.be').id,
'email': 'test.partner@test.example.com',
'name': 'Test Partner',
'phone': '0456001122',
})
@users('employee')
def test_mail_track_2many(self):
""" Check result of tracking one2many and many2many fields. Current
usage is to aggregate names into value_char fields. """
# Create a record with an initially invalid selection value
test_tags = self.env['mail.test.track.all.m2m'].create([
{'name': 'Tag1',},
{'name': 'Tag2',},
{'name': 'Tag3',},
])
test_record = self.env['mail.test.track.all'].create({
'name': 'Test 2Many fields tracking',
})
self.flush_tracking()
# no tracked field, no tracking at create
last_message = test_record.message_ids[0]
self.assertFalse(last_message.tracking_value_ids)
# update m2m
test_record.write({
'many2many_field': [(4, test_tags[0].id), (4, test_tags[1].id)],
})
self.flush_tracking()
last_message = test_record.message_ids[0]
self.assertTracking(
last_message,
[('many2many_field', 'many2many', '', ', '.join(test_tags[:2].mapped('name')))]
)
# update m2m + o2m
test_record.write({
'many2many_field': [(3, test_tags[0].id), (4, test_tags[2].id)],
'one2many_field': [
(0, 0, {'name': 'Child1'}),
(0, 0, {'name': 'Child2'}),
(0, 0, {'name': 'Child3'}),
],
})
self.flush_tracking()
last_message = test_record.message_ids[0]
self.assertTracking(
last_message,
[
('many2many_field', 'many2many', ', '.join(test_tags[:2].mapped('name')), ', '.join((test_tags[1] + test_tags[2]).mapped('name'))),
('one2many_field', 'one2many', '', ', '.join(('Child1', 'Child2', 'Child3'))),
]
)
# remove from o2m
test_record.write({'one2many_field': [(3, test_record.one2many_field[0].id)]})
self.flush_tracking()
last_message = test_record.message_ids[0]
self.assertTracking(
last_message,
[('one2many_field', 'one2many', ', '.join(('Child1', 'Child2', 'Child3')), ', '.join(('Child2', 'Child3')))]
)
@users('employee')
def test_mail_track_all_no2many(self):
test_record = self.env['mail.test.track.all'].create({
'company_id': self.env.company.id,
})
self.flush_tracking()
self.assertEqual(test_record.currency_id, self.env.ref('base.USD'))
messages = test_record.message_ids
today = fields.Date.today()
today_dt = fields.Datetime.to_datetime(today)
now = fields.Datetime.now()
test_record.write({
'boolean_field': True,
'char_field': 'char_value',
'date_field': today,
'datetime_field': now,
'float_field': 3.22,
'html_field': '<p>Html Value</p>',
'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(
'', '<p>Html</p>',
'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)
]
)