# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import base64 from datetime import datetime, timedelta from freezegun import freeze_time from itertools import product from markupsafe import escape, Markup from unittest.mock import patch from odoo import tools from odoo.addons.base.tests.test_ir_cron import CronMixinCase from odoo.addons.mail.tests.common import mail_new_test_user, MailCommon from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE_PLAINTEXT from odoo.addons.test_mail.models.test_mail_models import MailTestSimple from odoo.addons.test_mail.tests.common import TestRecipients from odoo.api import call_kw from odoo.exceptions import AccessError from odoo.tests import tagged from odoo.tools import mute_logger, formataddr from odoo.tests.common import users class TestMessagePostCommon(MailCommon, TestRecipients): @classmethod def setUpClass(cls): super(TestMessagePostCommon, cls).setUpClass() # portal user, notably for ACLS / notifications cls.user_portal = cls._create_portal_user() cls.partner_portal = cls.user_portal.partner_id # another standard employee to test follow and notifications between two # users (and not admin / user) cls.user_employee_2 = mail_new_test_user( cls.env, login='employee2', groups='base.group_user', company_id=cls.company_admin.id, email='eglantine@example.com', # check: use a formatted email name='Eglantine Employee2', notification_type='email', signature='--\nEglantine', ) cls.partner_employee_2 = cls.user_employee_2.partner_id cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({ 'name': 'Test', 'email_from': 'ignasse@example.com' }) cls._reset_mail_context(cls.test_record) cls.test_message = cls.env['mail.message'].create({ 'author_id': cls.partner_employee.id, 'body': '
Notify Body Woop Woop
', 'email_from': cls.partner_employee.email_formatted, 'is_internal': False, 'message_id': tools.mail.generate_tracking_message_id('dummy-generate'), 'message_type': 'comment', 'model': cls.test_record._name, 'record_name': False, 'reply_to': 'wrong.alias@test.example.com', 'subtype_id': cls.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'), 'subject': 'Notify Test', }) cls.user_admin.write({'notification_type': 'email'}) def setUp(self): super(TestMessagePostCommon, self).setUp() # patch registry to simulate a ready environment; see ``_message_auto_subscribe_notify`` self.patch(self.env.registry, 'ready', True) @tagged('mail_post') class TestMailNotifyAPI(TestMessagePostCommon): @mute_logger('odoo.models.unlink') @users('employee') def test_email_notifiction_layouts(self): self.user_employee.write({'notification_type': 'email'}) test_record = self.env['mail.test.simple'].browse(self.test_record.ids) test_message = self.env['mail.message'].browse(self.test_message.ids) recipients_data = self._generate_notify_recipients(self.partner_1 + self.partner_2 + self.partner_employee) for email_xmlid in ['mail.mail_notification_light', 'mail.mail_notification_layout', 'mail.mail_notification_layout_with_responsible_signature']: test_message.sudo().notification_ids.unlink() # otherwise partner/message constraint fails test_message.write({'email_layout_xmlid': email_xmlid}) with self.mock_mail_gateway(): test_record._notify_thread_by_email( test_message, recipients_data, force_send=False ) self.assertEqual(len(self._new_mails), 2, 'Should have 2 emails: one for customers, one for internal users') # check customer email customer_email = self._new_mails.filtered(lambda mail: mail.recipient_ids == self.partner_1 + self.partner_2) self.assertTrue(customer_email) # check internal user email user_email = self._new_mails.filtered(lambda mail: mail.recipient_ids == self.partner_employee) self.assertTrue(user_email) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_notify_by_mail_add_signature(self): test_track = self.env['mail.test.track'].with_context(self._test_context).with_user(self.user_employee).create({ 'name': 'Test', 'email_from': 'ignasse@example.com' }) test_track.user_id = self.env.user signature = self.env.user.signature template = self.env.ref('mail.mail_notification_layout_with_responsible_signature', raise_if_not_found=True).sudo() self.assertIn("record.user_id.sudo().signature", template.arch) with self.mock_mail_gateway(): test_track.message_post( body="Test body", email_add_signature=True, email_layout_xmlid="mail.mail_notification_layout_with_responsible_signature", mail_auto_delete=False, partner_ids=[self.partner_1.id, self.partner_2.id], ) found_mail = self._new_mails self.assertIn(signature, found_mail.body_html) self.assertEqual(found_mail.body_html.count(signature), 1) with self.mock_mail_gateway(): test_track.message_post( body="Test body", email_add_signature=False, email_layout_xmlid="mail.mail_notification_layout_with_responsible_signature", mail_auto_delete=False, partner_ids=[self.partner_1.id, self.partner_2.id], ) found_mail = self._new_mails self.assertNotIn(signature, found_mail.body_html) self.assertEqual(found_mail.body_html.count(signature), 0) @users('employee') def test_notify_by_email_add_signature_no_author_user_or_no_user(self): test_record = self.env['mail.test.simple'].browse(self.test_record.ids) test_message = self.env['mail.message'].browse(self.test_message.ids) test_message.write({ 'author_id': self.env['res.partner'].sudo().create({ 'name': 'Steve', }).id }) # TOFIX: the test is actually broken because test_message cannot be # read; this populates the cache to make it work, but that's cheating... test_message.sudo().email_add_signature template_values = test_record._notify_by_email_prepare_rendering_context(test_message, {}) self.assertNotEqual(escape(template_values['signature']), escape('--
Steve
You have received a notification
', 'message_type': 'user_notification', 'message_values': { 'author_id': self.partner_employee, 'body': 'You have received a notification
', 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'message_type': 'user_notification', 'model': test_record._name, 'notified_partner_ids': self.partner_1 | self.partner_employee_2 | self.partner_admin, 'res_id': test_record.id, 'subtype_id': self.env.ref('mail.mt_note'), }, 'subtype': 'mail.mt_note', }, ): new_notification = test_record.message_notify( body=Markup('You have received a notification
'), partner_ids=[self.partner_1.id, self.partner_admin.id, self.partner_employee_2.id], subject='This should be a subject', ) self.assertNotIn(new_notification, self.test_record.message_ids) # notified_partner_ids should be empty after copying the message copy = new_notification.copy() self.assertFalse(copy.notified_partner_ids) admin_mails = [mail for mail in self._mails if self.partner_admin.name in mail.get('email_to')[0]] self.assertEqual(len(admin_mails), 1, 'There should be exactly one email sent to admin') admin_mail_body = admin_mails[0].get('body') self.assertTrue('model=' in admin_mail_body, 'The email sent to admin should contain an access link') admin_access_link = admin_mail_body[ admin_mail_body.index('model='):admin_mail_body.index('/>', admin_mail_body.index('model=')) - 1] self.assertIn(f'model={self.test_record._name}', admin_access_link, 'The access link should contain a valid model argument') self.assertIn(f'res_id={self.test_record.id}', admin_access_link, 'The access link should contain a valid res_id argument') partner_mails = [x for x in self._mails if self.partner_1.name in x.get('email_to')[0]] self.assertEqual(len(partner_mails), 1, 'There should be exactly one email sent to partner') partner_mail_body = partner_mails[0].get('body') self.assertNotIn('/mail/view?model=', partner_mail_body, 'The email sent to customer should not contain an access link') @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_notify_author(self): """ Author is not added in notified people by default, unless asked to using the 'notify_author' parameter or context key. """ test_record = self.env['mail.test.simple'].browse(self.test_record.ids) with self.mock_mail_gateway(): new_notification = test_record.message_notify( body=Markup('You have received a notification
'), partner_ids=(self.partner_1 + self.partner_employee).ids, subject='This should be a subject', ) self.assertEqual(new_notification.notified_partner_ids, self.partner_1) with self.mock_mail_gateway(): new_notification = test_record.message_notify( body=Markup('You have received a notification
'), notify_author=True, partner_ids=(self.partner_1 + self.partner_employee).ids, subject='This should be a subject', ) self.assertEqual( new_notification.notified_partner_ids, self.partner_1 + self.partner_employee, 'Notify: notify_author parameter skips the author restriction' ) with self.mock_mail_gateway(): new_notification = test_record.with_context(mail_notify_author=True).message_notify( body=Markup('You have received a notification
'), partner_ids=(self.partner_1 + self.partner_employee).ids, subject='This should be a subject', ) self.assertEqual( new_notification.notified_partner_ids, self.partner_1 + self.partner_employee, 'Notify: mail_notify_author context key skips the author restriction' ) @users('employee') def test_notify_batch(self): """ Test notify in batch. Currently not supported. """ test_records, _partners = self._create_records_for_batch('mail.test.simple', 10) with self.assertRaises(ValueError): test_records.message_notify( body=Markup('Nice notification content
'), partner_ids=self.partner_employee_2.ids, subject='Notify Subject', ) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_notify_from_user_id(self): """ Test notify coming from user_id assignment (in batch) """ test_records, _ = self._create_records_for_batch( 'mail.test.track', 10, { 'company_id': self.env.user.company_id.id, 'email_from': self.env.user.email_formatted, 'user_id': False, } ) test_records = self.env['mail.test.track'].browse(test_records.ids) self.flush_tracking() with self.mock_mail_gateway(), self.mock_mail_app(): test_records.write({'user_id': self.user_employee_2.id}) self.flush_tracking() self.assertEqual(len(self._new_msgs), 20, 'Should have 20 messages: 10 tracking and 10 assignments') model_name = self.env['ir.model'].sudo()._get(test_records._name).name for test_record in test_records: assign_notif = self._new_msgs.filtered(lambda msg: msg.message_type == 'user_notification' and msg.res_id == test_record.id) self.assertTrue(assign_notif) self.assertMailNotifications( assign_notif, [{ 'content': f'You have been assigned to the {model_name}', 'email_values': { # used to distinguished outgoing emails 'subject': f'You have been assigned to {test_record.name}', }, 'message_type': 'user_notification', 'message_values': { 'author_id': self.partner_employee, 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'model': test_record._name, 'notified_partner_ids': self.partner_employee_2, 'res_id': test_record.id, }, 'notif': [ {'partner': self.partner_employee_2, 'type': 'email',}, ], 'subtype': 'mail.mt_note', }], ) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') def test_notify_parameters(self): """ Test usage of parameters in notify, both for unwanted side effects and magic parameters. """ test_record = self.test_record.with_env(self.env) for parameters in [ {'message_type': 'comment'}, {'child_ids': []}, {'mail_ids': []}, {'notification_ids': []}, {'notified_partner_ids': []}, {'reaction_ids': []}, {'starred_partner_ids': []}, ]: with self.subTest(parameters=parameters), \ self.mock_mail_gateway(), \ self.assertRaises(ValueError): _new_message = test_record.message_notify( body=Markup('You will not receive a notification
'), partner_ids=self.partner_1.ids, subject='This should not be accepted', **parameters ) # support of subtype xml id new_message = test_record.message_notify( body=Markup('You will not receive a notification
'), partner_ids=self.partner_1.ids, subtype_xmlid='mail.mt_note', ) self.assertEqual(new_message.subtype_id, self.env.ref('mail.mt_note')) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_notify_thread(self): """ Test notify on ``mail.thread`` model, which is pushing a message to people without having a document. """ with self.mock_mail_gateway(): new_notification = self.env['mail.thread'].message_notify( body=Markup('You have received a notification
'), partner_ids=[self.partner_1.id, self.partner_admin.id, self.partner_employee_2.id], subject='This should be a subject', ) self.assertMailNotifications( new_notification, [{ 'content': 'You have received a notification
', 'message_type': 'user_notification', 'message_values': { 'author_id': self.partner_employee, 'body': 'You have received a notification
', 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'model': False, 'res_id': False, 'notified_partner_ids': self.partner_1 | self.partner_employee_2 | self.partner_admin, 'subtype_id': self.env.ref('mail.mt_note'), }, 'notif': [ {'partner': self.partner_1, 'type': 'email',}, {'partner': self.partner_employee_2, 'type': 'email',}, {'partner': self.partner_admin, 'type': 'email',}, ], 'subtype': 'mail.mt_note', }], ) @tagged('mail_post') class TestMessageLog(TestMessagePostCommon): @classmethod def setUpClass(cls): super(TestMessageLog, cls).setUpClass() # ensure employee can create partners, necessary for templates cls.user_employee.write({ 'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)], }) cls.test_records, cls.test_partners = cls._create_records_for_batch( 'mail.test.ticket', 10, ) @users('employee') def test_message_log(self): test_record = self.env['mail.test.simple'].browse(self.test_record.ids) test_record.message_subscribe(self.partner_employee_2.ids) with self.mock_mail_gateway(): new_note = test_record._message_log( body=Markup('Labrador
'), ) self.assertMailNotifications( new_note, [{ 'content': 'Labrador
', 'message_type': 'notification', 'message_values': { 'author_id': self.partner_employee, 'body': 'Labrador
', 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': True, 'model': test_record._name, 'notified_partner_ids': self.env['res.partner'], 'partner_ids': self.env['res.partner'], 'reply_to': formataddr((self.company_admin.name, f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, }, 'notif': [], 'subtype': 'mail.mt_note', }], ) @users('employee') def test_message_log_batch(self): test_records = self.test_records.with_env(self.env) test_records.message_subscribe(self.partner_employee_2.ids) with self.mock_mail_gateway(): new_notes = test_records._message_log_batch( bodies={ test_record.id: Markup('Test _message_log_batch
') for test_record in test_records }, ) for test_record, new_note in zip(test_records, new_notes): self.assertMailNotifications( new_note, [{ 'content': 'Test _message_log_batch
', 'message_type': 'notification', 'message_values': { 'author_id': self.partner_employee, 'body': 'Test _message_log_batch
', 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': True, 'model': test_record._name, 'notified_partner_ids': self.env['res.partner'], 'partner_ids': self.env['res.partner'], 'reply_to': formataddr((self.company_admin.name, f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, }, 'notif': [], 'subtype': 'mail.mt_note', }], ) @users('employee') def test_message_log_batch_with_partners(self): """ Partners can be given to log, but this should not generate any notification. """ test_records = self.test_records.with_env(self.env) test_records.message_subscribe(self.partner_employee_2.ids) with self.mock_mail_gateway(): new_notes = test_records._message_log_batch( bodies={ test_record.id: Markup('Test _message_log_batch
') for test_record in test_records }, partner_ids=self.test_partners[:5].ids, ) for test_record, new_note in zip(test_records, new_notes): self.assertMailNotifications( new_note, [{ 'content': 'Test _message_log_batch
', 'message_type': 'notification', 'message_values': { 'author_id': self.partner_employee, 'body': 'Test _message_log_batch
', 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': True, 'model': test_record._name, 'notified_partner_ids': self.env['res.partner'], 'partner_ids': self.test_partners[:5], 'reply_to': formataddr((self.company_admin.name, f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, }, 'notif': [], 'subtype': 'mail.mt_note', }], ) @users('employee') def test_message_log_with_view(self): test_records = self.test_records.with_env(self.env) test_records.message_subscribe(self.partner_employee_2.ids) with self.mock_mail_gateway(): new_notes = test_records._message_log_with_view( 'test_mail.mail_template_simple_test', render_values={'partner': self.user_employee.partner_id} ) for test_record, new_note in zip(test_records, new_notes): self.assertMailNotifications( new_note, [{ 'content': f'Hello {self.user_employee.name}, this comes from {test_record.name}.
', 'message_type': 'notification', 'message_values': { 'author_id': self.partner_employee, 'body': f'Hello {self.user_employee.name}, this comes from {test_record.name}.
', 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': True, 'model': test_record._name, 'notified_partner_ids': self.env['res.partner'], 'reply_to': formataddr((self.company_admin.name, f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, }, 'notif': [], 'subtype': 'mail.mt_note', }], ) @tagged('mail_post') class TestMessagePost(TestMessagePostCommon, CronMixinCase): def test_assert_initial_values(self): """ Be sure of what we are testing """ self.assertFalse(self.test_record.message_ids) self.assertFalse(self.test_record.message_follower_ids) self.assertFalse(self.test_record.message_partner_ids) @mute_logger('odoo.addons.mail.models.mail_mail') def test_manual_send_user_notification_email_from_queue(self): """ Test sending a mail from the queue that is not related to the admin user sending it. Will throw a security error not having access to the mail.""" with self.mock_mail_gateway(): new_notification = self.test_record.message_notify( subject='This should be a subject', body='You have received a notification
', partner_ids=[self.partner_1.id], subtype_xmlid='mail.mt_note', force_send=False ) self.assertNotIn(self.user_admin.partner_id, new_notification.mail_ids.partner_ids, "Our admin user should not be within the partner_ids") with self.mock_mail_gateway(): new_notification.mail_ids.with_user(self.user_admin).send() self.assertEqual(new_notification.mail_ids.state, 'exception', 'Email will be sent but with exception state - write access denied') @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') @users('employee') def test_message_post(self): self.user_employee_2.write({'notification_type': 'inbox'}) test_record = self.env['mail.test.simple'].browse(self.test_record.ids) with self.assertSinglePostNotifications( [{'partner': self.partner_employee_2, 'type': 'inbox'}], message_info={ 'content': 'Body', 'message_values': { 'author_id': self.partner_employee, 'body': 'Body
', 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': False, 'message_type': 'comment', 'model': test_record._name, 'notified_partner_ids': self.partner_employee_2, 'reply_to': formataddr((f'{self.company_admin.name} {test_record.name}', f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, 'subtype_id': self.env.ref('mail.mt_comment'), }, } ): new_message = test_record.message_post( body='Body', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[self.partner_employee_2.id], ) self.assertEqual(test_record.message_partner_ids, self.partner_employee) # subscribe partner_1, check notifications test_record.message_subscribe(self.partner_1.ids) with self.assertSinglePostNotifications( [{'partner': self.partner_employee_2, 'type': 'inbox'}, {'partner': self.partner_1, 'type': 'email'}], message_info={ 'content': 'NewBody', 'email_values': { 'headers': { 'Return-Path': f'{self.alias_bounce}@{self.alias_domain}', }, }, 'message_values': { 'notified_partner_ids': self.partner_1 + self.partner_employee_2, }, }, mail_unlink_sent=True ): new_message = test_record.message_post( body='NewBody', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[self.partner_employee_2.id], ) # notifications emails should have been deleted self.assertFalse(self.env['mail.mail'].sudo().search_count([('mail_message_id', '=', new_message.id)])) with self.assertSinglePostNotifications( [{'partner': self.partner_1, 'type': 'email'}, {'partner': self.partner_portal, 'type': 'email'}], message_info={ 'content': 'ToPortal', } ): test_record.message_post( body='ToPortal', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=self.partner_portal.ids, ) @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') @users('employee') def test_message_post_author(self): """ Test author recognition """ test_record = self.test_record.with_env(self.env) # when a user spoofs the author: the actual author is the current user # and not the message author with self.assertSinglePostNotifications( [{'partner': self.partner_admin, 'type': 'email'}], message_info={ 'content': 'Body', 'mail_mail_values': { 'author_id': self.partner_employee_2, 'email_from': formataddr((self.partner_employee_2.name, self.partner_employee_2.email_normalized)), }, 'message_values': { 'author_id': self.partner_employee_2, 'email_from': formataddr((self.partner_employee_2.name, self.partner_employee_2.email_normalized)), 'message_type': 'comment', 'notified_partner_ids': self.partner_admin, 'subtype_id': self.env.ref('mail.mt_comment'), }, }, ): _new_message = test_record.message_post( author_id=self.partner_employee_2.id, body='Body', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[self.partner_admin.id], ) self.assertEqual(test_record.message_partner_ids, self.partner_employee, 'Real author is added in followers, not message author') # should be skipped with notifications test_record.message_unsubscribe(partner_ids=self.partner_employee.ids) _new_message = test_record.message_post( author_id=self.partner_employee_2.id, body='Body', message_type='notification', subtype_xmlid='mail.mt_comment', partner_ids=[self.partner_admin.id], ) self.assertFalse(test_record.message_partner_ids, 'Notification should not add author in followers') # inactive users are not considered as authors self.env.user.with_user(self.user_admin).active = False _new_message = test_record.message_post( author_id=self.partner_employee_2.id, body='Body', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[self.partner_admin.id], ) self.assertEqual(test_record.message_partner_ids, self.partner_employee_2, 'Author is the message author when user is inactive, and shoud be added in followers') @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') @users('employee') def test_message_post_defaults(self): """ Test default values when posting a classic message. """ _original_compute_subject = MailTestSimple._message_compute_subject _original_notify_headers = MailTestSimple._notify_by_email_get_headers _original_notify_mailvals = MailTestSimple._notify_by_email_get_final_mail_values test_record = self.env['mail.test.simple'].create([{'name': 'Defaults'}]) creation_msg = test_record.message_ids self.assertEqual(len(creation_msg), 1) with patch.object(MailTestSimple, '_message_compute_subject', autospec=True, side_effect=_original_compute_subject) as mock_compute_subject, \ patch.object(MailTestSimple, '_notify_by_email_get_headers', autospec=True, side_effect=_original_notify_headers) as mock_notify_headers, \ patch.object(MailTestSimple, '_notify_by_email_get_final_mail_values', autospec=True, side_effect=_original_notify_mailvals) as mock_notify_mailvals, \ self.mock_mail_gateway(), self.mock_mail_app(): new_message = test_record.message_post( body='Body', partner_ids=[self.partner_employee_2.id], ) self.assertEqual(mock_compute_subject.call_count, 1, 'Should call model-based subject computation for outgoing emails') self.assertEqual(mock_notify_headers.call_count, 1, 'Should call model-based headers computation for outgoing emails') self.assertEqual(mock_notify_mailvals.call_count, 1, 'Should call model-based headers computation for outgoing emails') self.assertMailNotifications( new_message, [{ 'content': 'Body
', 'message_type': 'notification', 'message_values': { 'author_id': self.partner_employee, 'body': 'Body
', 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': False, 'model': test_record._name, 'notified_partner_ids': self.partner_employee_2, 'parent_id': creation_msg, 'record_name': test_record.name, 'reply_to': formataddr((f'{self.company_admin.name} {test_record.name}', f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, 'subject': test_record.name, }, 'notif': [ {'partner': self.partner_employee_2, 'type': 'email',}, ], 'subtype': 'mail.mt_note', }], ) @users('employee') @mute_logger('odoo.models.unlink') def test_message_post_inactive_follower(self): """ Test posting with inactive followers does not notify them (e.g. odoobot) """ test_record = self.env['mail.test.simple'].browse(self.test_record.ids) test_record._message_subscribe(self.user_employee_2.partner_id.ids) self.user_employee_2.write({'active': False}) self.partner_employee_2.write({'active': False}) with self.assertPostNotifications([{'content': 'Test', 'notif': []}]): test_record.message_post( body='Test', message_type='comment', subtype_xmlid='mail.mt_comment', ) @mute_logger('odoo.addons.mail.models.mail_mail') @users('employee') def test_message_post_keep_emails(self): test_record = self.env['mail.test.simple'].browse(self.test_record.ids) test_record.message_subscribe(partner_ids=self.partner_employee_2.ids) with self.mock_mail_gateway(mail_unlink_sent=True): msg = test_record.message_post( body='Test', mail_auto_delete=False, message_type='comment', partner_ids=[self.partner_1.id, self.partner_2.id], subject='Test', subtype_xmlid='mail.mt_comment', ) # notifications emails should not have been deleted: one for customers, one for user self.assertEqual(self.env['mail.mail'].sudo().search_count([('mail_message_id', '=', msg.id)]), 2) @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') @users('erp_manager') def test_message_post_mc(self): """ Test posting in multi-company environment, notably with aliases """ records = self.env['mail.test.ticket.mc'].create([ { 'name': 'No Specific Company', }, { 'company_id': self.company_admin.id, 'name': 'Company1', }, { 'company_id': self.company_2.id, 'name': 'Company2', }, ]) expected_companies = [self.company_2, self.company_admin, self.company_2] expected_alias_domains = [self.mail_alias_domain_c2, self.mail_alias_domain, self.mail_alias_domain_c2] for record, expected_company, expected_alias_domain in zip( records, expected_companies, expected_alias_domains ): with self.subTest(record=record): with self.assertSinglePostNotifications( [{'partner': self.partner_employee_2, 'type': 'email'}], message_info={ 'content': 'Body', 'email_values': { 'headers': { 'Return-Path': f'{expected_alias_domain.bounce_alias}@{expected_alias_domain.name}', }, }, 'mail_mail_values': { 'headers': { 'Return-Path': f'{expected_alias_domain.bounce_alias}@{expected_alias_domain.name}', 'X-Odoo-Objects': f'{record._name}-{record.id}', }, }, 'message_values': { 'author_id': self.user_erp_manager.partner_id, 'email_from': formataddr((self.user_erp_manager.name, self.user_erp_manager.email_normalized)), 'is_internal': False, 'notified_partner_ids': self.partner_employee_2, 'reply_to': formataddr( ( f'{expected_company.name} {record.name}', f'{expected_alias_domain.catchall_alias}@{expected_alias_domain.name}' ) ), }, } ): _new_message = record.message_post( body='Body', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[self.partner_employee_2.id], ) @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.tests') def test_message_post_recipients_email_field(self): """ Test various combinations of corner case / not standard filling of email fields: multi email, formatted emails, ... """ partner_emails = [ 'valid.lelitre@agrolait.com, valid.lelitre.cc@agrolait.com', # multi email '"Valid Lelitre"Test
'), message_type='comment', subject='Subject', subtype_xmlid='mail.mt_comment', scheduled_date=scheduled_datetime, ) self.assertEqual(capt.records.call_at, scheduled_datetime, msg='Should have created a cron trigger for the scheduled sending') self.assertFalse(self._new_mails) self.assertFalse(self._mails) schedules = self.env['mail.message.schedule'].sudo().search([('mail_message_id', '=', msg.id)]) self.assertEqual(len(schedules), 1, msg='Should have scheduled the message') self.assertEqual(schedules.scheduled_datetime, scheduled_datetime) # trigger cron now -> should not sent as in future with self.mock_datetime_and_now(now): self.env['mail.message.schedule'].sudo()._send_notifications_cron() self.assertTrue(schedules.exists(), msg='Should not have sent the message') # Send the scheduled message from the cron at right date with self.mock_datetime_and_now(now + timedelta(days=5)), self.mock_mail_gateway(mail_unlink_sent=True): self.env['mail.message.schedule'].sudo()._send_notifications_cron() self.assertFalse(schedules.exists(), msg='Should have sent the message') # check notifications have been sent recipients_info = [{'content': 'Test
', 'notif': [ {'partner': self.partner_admin, 'type': 'inbox'}, {'partner': self.partner_1, 'type': 'email'}, ]}] self.assertMailNotifications(msg, recipients_info) # manually create a new schedule date, resend it -> should not crash (aka # don't create duplicate notifications, ...) self.env['mail.message.schedule'].sudo().create({ 'mail_message_id': msg.id, 'scheduled_datetime': scheduled_datetime, }) # Send the scheduled message from the CRON with self.mock_datetime_and_now(now + timedelta(days=5)), self.assertNoNotifications(): self.env['mail.message.schedule'].sudo()._send_notifications_cron() # schedule in the past = send when posting with self.mock_datetime_and_now(now), \ self.mock_mail_gateway(mail_unlink_sent=False), \ self.capture_triggers(cron_id) as capt: msg = test_record.message_post( body=Markup('Test
'), message_type='comment', subject='Subject', subtype_xmlid='mail.mt_comment', scheduled_date=now, ) self.assertFalse(capt.records) recipients_info = [{'content': 'Test
', 'notif': [ {'partner': self.partner_admin, 'type': 'inbox'}, {'partner': self.partner_1, 'type': 'email'}, ]}] self.assertMailNotifications(msg, recipients_info) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_message_schedule', 'odoo.models.unlink') def test_message_post_schedule_update(self): """ Test tools to update scheduled notifications """ cron = self.env.ref('mail.ir_cron_send_scheduled_message') now = datetime.utcnow().replace(second=0, microsecond=0) scheduled_datetime = now + timedelta(days=5) self.user_admin.write({'notification_type': 'inbox'}) test_record = self.test_record.with_env(self.env) test_record.message_subscribe((self.partner_1 | self.partner_admin).ids) with freeze_time(now), \ self.assertMsgWithoutNotifications(): msg = test_record.message_post( body=Markup('Test
'), message_type='comment', subject='Subject', subtype_xmlid='mail.mt_comment', scheduled_date=scheduled_datetime, ) schedules = self.env['mail.message.schedule'].sudo().search([('mail_message_id', '=', msg.id)]) self.assertEqual(len(schedules), 1, msg='Should have scheduled the message') # update scheduled datetime, should create new triggers with freeze_time(now), \ self.assertNoNotifications(), \ self.capture_triggers(cron.id) as capt: self.env['mail.message.schedule'].sudo()._update_message_scheduled_datetime(msg, now - timedelta(hours=1)) self.assertEqual(capt.records.call_at, now - timedelta(hours=1), msg='Should have created a new cron trigger for the new scheduled sending') self.assertTrue(schedules.exists(), msg='Should not have sent the message') # run cron, notifications have been sent with freeze_time(now), self.mock_mail_gateway(mail_unlink_sent=False): schedules._send_notifications_cron() self.assertFalse(schedules.exists(), msg='Should have sent the message') recipients_info = [{'content': 'Test
', 'notif': [ {'partner': self.partner_admin, 'type': 'inbox'}, {'partner': self.partner_1, 'type': 'email'}, ]}] self.assertMailNotifications(msg, recipients_info) self.assertFalse(self.env['mail.message.schedule'].sudo()._update_message_scheduled_datetime(msg, now - timedelta(hours=1)), 'Mail scheduler: should return False when no schedule is found') @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_message_schedule') def test_message_post_w_attachments_filtering(self): """ Test the message_main_attachment heuristics with an emphasis on the XML/Octet/PDF types. -> we don't want XML nor Octet-Stream files to be set as message_main_attachment """ xml_attachment, octet_attachment, pdf_attachment = ( [('List1', b'Test Body
', partner_ids=[self.partner_1.id, self.partner_2.id], subject='1st line\n2nd line', ) self.assertEqual(msg.subject, '1st line 2nd line') @mute_logger('odoo.addons.base.models.ir_model', 'odoo.addons.mail.models.mail_mail') def test_portal_acls(self): self.test_record.message_subscribe((self.partner_1 | self.user_employee.partner_id).ids) with self.assertPostNotifications( [{'content': 'Test
', 'notif': [ {'partner': self.partner_employee, 'type': 'inbox'}, {'partner': self.partner_1, 'type': 'email'}]} ] ), patch.object(MailTestSimple, '_check_access', return_value=None): new_msg = self.test_record.with_user(self.user_portal).message_post( body=Markup('Test
'), message_type='comment', subject='Subject', subtype_xmlid='mail.mt_comment', ) self.assertEqual(new_msg.sudo().notified_partner_ids, (self.partner_1 | self.user_employee.partner_id)) with self.assertRaises(AccessError): self.test_record.with_user(self.user_portal).message_post( body=Markup('Test
'), message_type='comment', subject='Subject', subtype_xmlid='mail.mt_comment', ) @mute_logger('odoo.addons.mail.models.mail_mail') @users('employee') def test_post_answer(self): test_record = self.env['mail.test.simple'].browse(self.test_record.ids) with self.mock_mail_gateway(): parent_msg = test_record.message_post( body=Markup('Test
'), message_type='comment', subject='Test Subject', subtype_xmlid='mail.mt_comment', ) self.assertFalse(parent_msg.partner_ids) self.assertNotSentEmail() # post a first reply with self.assertPostNotifications( [{'content': 'Test Answer
', 'notif': [{'partner': self.partner_1, 'type': 'email'}]}] ): msg = test_record.message_post( body=Markup('Test Answer
'), message_type='comment', parent_id=parent_msg.id, partner_ids=[self.partner_1.id], subject='Welcome', subtype_xmlid='mail.mt_comment', ) self.assertEqual(msg.parent_id, parent_msg) self.assertEqual(msg.partner_ids, self.partner_1) self.assertFalse(parent_msg.partner_ids) # check notification emails: references self.assertSentEmail( self.user_employee.partner_id, [self.partner_1], references_content='openerp-%d-mail.test.simple' % self.test_record.id, # references should be sorted from the oldest to the newest references=f'{parent_msg.message_id} {msg.message_id}', ) # post a reply to the reply: check parent is the first one with self.mock_mail_gateway(): new_msg = test_record.message_post( body=Markup('Test Answer Bis
'), message_type='comment', subtype_xmlid='mail.mt_comment', parent_id=msg.id, partner_ids=[self.partner_2.id], ) self.assertEqual(new_msg.parent_id, parent_msg, 'message_post: flatten error') self.assertEqual(new_msg.partner_ids, self.partner_2) self.assertSentEmail( self.user_employee.partner_id, [self.partner_2], body_content='Test Answer Bis
', reply_to=msg.reply_to, subject=self.test_record.name, references_content='openerp-%d-mail.test.simple' % self.test_record.id, references=f'{parent_msg.message_id} {new_msg.message_id}', ) @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread') @users('employee') def test_post_internal(self): test_record = self.env['mail.test.simple'].browse(self.test_record.ids) test_record.message_subscribe([self.user_admin.partner_id.id]) with self.mock_mail_gateway(): msg = test_record.message_post( body='My Body', message_type='comment', subject='My Subject', subtype_xmlid='mail.mt_note', ) self.assertFalse(msg.is_internal, 'Notes are not "internal" but replies will be. Subtype being internal should be sufficient from ACLs point of view.') self.assertFalse(msg.partner_ids) self.assertFalse(msg.notified_partner_ids) self.format_and_process( MAIL_TEMPLATE_PLAINTEXT, self.user_admin.email, 'not_my_businesss@example.com', msg_id='<1198923581.41972151344608186800.JavaMail.diff1@agrolait.example.com>', extra=f'In-Reply-To:\r\n\t{msg.message_id}\n', target_model='mail.test.simple') reply = test_record.message_ids - msg self.assertTrue(reply) self.assertTrue(reply.is_internal) self.assertEqual(reply.notified_partner_ids, self.user_employee.partner_id) self.assertEqual(reply.parent_id, msg) self.assertEqual(reply.subtype_id, self.env.ref('mail.mt_note')) @tagged('mail_post') class TestMessagePostHelpers(TestMessagePostCommon): @classmethod def setUpClass(cls): super(TestMessagePostHelpers, cls).setUpClass() # ensure employee can create partners, necessary for templates cls.user_employee.write({ 'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)], }) cls.user_employee.write({ 'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)], }) cls.test_records, cls.test_partners = cls._create_records_for_batch( 'mail.test.ticket', 10, ) cls._attachments = cls._generate_attachments_data(2, 'mail.template', 0) cls.email_1 = 'test1@example.com' cls.email_2 = 'test2@example.com' cls.test_template = cls._create_template('mail.test.ticket', { 'attachment_ids': [(0, 0, attach_vals) for attach_vals in cls._attachments], 'auto_delete': True, # After the HTML sanitizer, it will become "Body for:
Hello {self.user_employee.partner_id.name}, this comes from {test_record.name}.
', 'subject': 'About mass mailing', }, fields_values={ 'auto_delete': False, 'is_internal': False, 'is_notification': False, # no to_delete -> no keep_log 'message_type': 'email_outgoing', 'model': test_record._name, 'notified_partner_ids': self.env['res.partner'], 'recipient_ids': test_record.customer_id, 'subtype_id': self.env['mail.message.subtype'], 'reply_to': formataddr((f'{self.company_admin.name} {test_record.name}', f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, } ) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_message_post_with_source_subtype(self): """ Test subtype tweaks when posting with a source """ test_record = self.test_records.with_env(self.env)[0] test_template = self.test_template.with_env(self.env) with self.mock_mail_gateway(): new_message = test_record.with_user(self.user_employee).message_post_with_source( test_template, subtype_xmlid='mail.mt_activities', ) self.assertEqual(new_message.subtype_id, self.env.ref("mail.mt_activities")) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_message_post_with_template(self): """ Test posting on a document based on a template content """ test_record = self.test_records.with_env(self.env)[0] test_record.message_subscribe(test_record.customer_id.ids) test_template = self.test_template.with_env(self.env) with self.mock_mail_gateway(): new_message = test_record.with_user(self.user_employee).message_post_with_source( test_template, message_type='comment', subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'), ) # created partners from inline email addresses new_partners = self.env['res.partner'].search([('email', 'in', [self.email_1, self.email_2])]) self.assertEqual(len(new_partners), 2, 'Post with template: should have created partners based on template emails') # check notifications have been sent self.assertMailNotifications( new_message, [{ 'content': f'Body for: {test_record.name}link
', 'message_type': 'comment', 'message_values': { 'author_id': self.partner_employee, 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': False, 'model': test_record._name, 'reply_to': formataddr((f'{self.company_admin.name} {test_record.name}', f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, }, 'notif': [ {'partner': self.partner_1, 'type': 'email'}, {'partner': self.partner_2, 'type': 'email'}, {'partner': new_partners[0], 'type': 'email'}, {'partner': new_partners[1], 'type': 'email'}, {'partner': test_record.customer_id, 'type': 'email'}, ], 'subtype': 'mail.mt_comment', }] ) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_message_post_with_template_defaults(self): """ Test default values, notably subtype being a comment """ test_record = self.test_records.with_env(self.env)[0] test_record.message_subscribe(test_record.customer_id.ids) test_template = self.test_template.with_env(self.env) with self.mock_mail_gateway(): new_message = test_record.with_user(self.user_employee).message_post_with_source( test_template, ) # created partners from inline email addresses new_partners = self.env['res.partner'].search([('email', 'in', [self.email_1, self.email_2])]) self.assertEqual(len(new_partners), 2, 'Post with template: should have created partners based on template emails') # check notifications have been sent self.assertMailNotifications(new_message, [{ 'content': f'Body for: {test_record.name}link
', 'message_type': 'notification', 'message_values': { 'author_id': self.partner_employee, 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': False, 'model': test_record._name, 'reply_to': formataddr((f'{self.company_admin.name} {test_record.name}', f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, }, 'notif': [ {'partner': self.partner_1, 'type': 'email'}, {'partner': self.partner_2, 'type': 'email'}, {'partner': new_partners[0], 'type': 'email'}, {'partner': new_partners[1], 'type': 'email'}, {'partner': test_record.customer_id, 'type': 'email'}, ], 'subtype': 'mail.mt_note', }]) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.tests') def test_message_post_with_view(self): """ Test posting on documents based on a view """ test_record = self.test_records.with_env(self.env)[0] test_record.message_subscribe(test_record.customer_id.ids) with self.mock_mail_gateway(): new_message = test_record.message_post_with_source( 'test_mail.mail_template_simple_test', message_type='comment', render_values={'partner': self.user_employee.partner_id}, subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'), ) # check notifications have been sent self.assertMailNotifications(new_message, [{ 'content': f'Hello {self.user_employee.partner_id.name}, this comes from {test_record.name}.
', 'message_type': 'comment', 'message_values': { 'author_id': self.partner_employee, 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': False, 'message_type': 'comment', 'model': test_record._name, 'reply_to': formataddr((f'{self.company_admin.name} {test_record.name}', f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, }, 'notif': [ {'partner': test_record.customer_id, 'type': 'email'}, ], 'subtype': 'mail.mt_comment', }]) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.tests') def test_message_post_with_view_defaults(self): """ Test posting on documents based on a view, check default values """ test_record = self.test_records.with_env(self.env)[0] test_record.message_subscribe(test_record.customer_id.ids) # defaults is a note, take into account specified recipients with self.mock_mail_gateway(): new_message = test_record.message_post_with_source( 'test_mail.mail_template_simple_test', render_values={'partner': self.user_employee.partner_id}, partner_ids=test_record.customer_id.ids, ) # check notifications have been sent self.assertMailNotifications(new_message, [{ 'content': f'Hello {self.user_employee.partner_id.name}, this comes from {test_record.name}.
', 'message_type': 'notification', 'message_values': { 'author_id': self.partner_employee, 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), 'is_internal': False, 'message_type': 'notification', 'model': test_record._name, 'reply_to': formataddr((f'{self.company_admin.name} {test_record.name}', f'{self.alias_catchall}@{self.alias_domain}')), 'res_id': test_record.id, }, 'notif': [ {'partner': test_record.customer_id, 'type': 'email'}, ], 'subtype': 'mail.mt_note', }]) @tagged('mail_post', 'post_install', '-at_install') class TestMessagePostGlobal(TestMessagePostCommon): @users('employee') def test_message_post_return(self): """ Ensures calling message_post through RPC always return an ID. """ test_record = self.env['mail.test.simple'].browse(self.test_record.ids) # Use call_kw as shortcut to simulate a RPC call. message_id = call_kw(self.env['mail.test.simple'], 'message_post', [test_record.id], {'body': 'test'}) self.assertTrue(isinstance(message_id, int)) @tagged('mail_post', 'multi_lang') class TestMessagePostLang(MailCommon, TestRecipients): @classmethod def setUpClass(cls): super(TestMessagePostLang, cls).setUpClass() cls.test_records = cls.env['mail.test.lang'].create([ {'customer_id': False, 'email_from': 'test.record.1@test.customer.com', 'lang': 'es_ES', 'name': 'TestRecord1', }, {'customer_id': cls.partner_2.id, 'email_from': 'valid.other@gmail.com', 'name': 'TestRecord2', }, ]) cls.test_template = cls.env['mail.template'].create({ 'auto_delete': True, 'body_html': 'EnglishBody for
Hello
'), email_layout_xmlid='mail.test_layout', message_type='comment', subject='Subject', subtype_xmlid='mail.mt_comment', ) customer_email = self._find_sent_mail_wemail(self.partner_2.email_formatted) self.assertTrue(customer_email) body = customer_email['body'] # check content self.assertIn('Hello
', body, 'Body of posted message should be present') # check notification layout content self.assertIn('Spanish Layout para', body, 'Layout content should be translated') self.assertNotIn('English Layout for', body) self.assertIn('Spanish Layout para Spanish Model Description', body, 'Model name should be translated') # check notification layout strings self.assertIn('SpanishView Spanish Model Description', body, '"View document" should be translated') self.assertNotIn(f'View {test_records[1]._description}', body, '"View document" should be translated') self.assertIn('SpanishButtonTitle', body, 'Groups-based action names should be translated') self.assertNotIn('NotificationButtonTitle', body) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_layout_email_lang_template(self): """ Test language support when posting in batch using a template. Content is translated based on template definition, layout based on customer lang. """ test_records = self.test_records.with_user(self.env.user) test_template = self.test_template.with_user(self.env.user) with self.mock_mail_gateway(): test_records.message_post_with_source( test_template, email_layout_xmlid='mail.test_layout', message_type='comment', subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'), ) record0_customer = self.env['res.partner'].search([('email_normalized', '=', 'test.record.1@test.customer.com')], limit=1) self.assertTrue(record0_customer, 'Template usage should have created a contact based on record email') for record, customer, exp_notif_lang in zip( test_records, record0_customer + self.partner_2, ('en_US', 'es_ES') # new customer is en_US, partner_2 is es_ES ): customer_email = self._find_sent_mail_wemail(customer.email_formatted) self.assertTrue(customer_email) # body and layouting are translated partly based on template. Bits # of layout are not translated due to lang not being correctly # propagate everywhere we need it body = customer_email['body'] # check content self.assertIn(f'SpanishBody for {record.name}', body, 'Body based on template should be translated') # check subject self.assertEqual(f'SpanishSubject for {record.name}', customer_email['subject'], 'Subject based on template should be translated') # check notification layout translation if exp_notif_lang == 'en_US': self.assertNotIn('Spanish Layout para', body, 'Layout content should be translated') self.assertIn('English Layout for', body) self.assertNotIn('Spanish Layout para Spanish Model Description', body, 'Model name should be translated') self.assertNotIn('SpanishView Spanish Model Description', body, '"View document" should be translated') self.assertIn(f'View {test_records[1]._description}', body, '"View document" should be translated') self.assertNotIn('SpanishButtonTitle', body, 'Groups-based action names should be translated') self.assertIn('NotificationButtonTitle', body, 'Groups-based action names should be translated') else: self.assertIn('Spanish Layout para', body, 'Layout content should be translated') self.assertNotIn('English Layout for', body) self.assertIn('Spanish Layout para Spanish Model Description', body, 'Model name should be translated') self.assertIn('SpanishView Spanish Model Description', body, '"View document" should be translated') self.assertNotIn(f'View {test_records[1]._description}', body, '"View document" should be translated') self.assertIn('SpanishButtonTitle', body, 'Groups-based action names should be translated') self.assertNotIn('NotificationButtonTitle', body, 'Groups-based action names should be translated') @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_post_multi_lang_inactive(self): """ Test posting using an inactive lang, due do some data in DB. It should not crash when trying to search for translated terms / fetch lang bits. """ installed = self.env['res.lang'].get_installed() self.assertNotIn('fr_FR', [code for code, _name in installed]) test_records = self.test_records.with_env(self.env) customer_inactive_lang = self.env['res.partner'].create({ 'email': 'test.partner.fr@test.example.com', 'lang': 'fr_FR', 'name': 'French Inactive Customer', }) test_records.message_subscribe(partner_ids=customer_inactive_lang.ids) for record in test_records: with self.subTest(record=record.name): with self.mock_mail_gateway(mail_unlink_sent=False), \ self.mock_mail_app(): record.message_post( body=Markup('Hi there
'), email_layout_xmlid='mail.test_layout', message_type='comment', subject='TeDeum', subtype_xmlid='mail.mt_comment', ) message = record.message_ids[0] self.assertEqual(message.notified_partner_ids, customer_inactive_lang) email = self._find_sent_email( self.partner_employee.email_formatted, [customer_inactive_lang.email_formatted] ) self.assertTrue(bool(email), 'Email not found, check recipients') exp_layout_content_en = 'English Layout for Lang Chatter Model' exp_button_en = 'View Lang Chatter Model' exp_action_en = 'NotificationButtonTitle' self.assertIn(exp_layout_content_en, email['body']) self.assertIn(exp_button_en, email['body']) self.assertIn(exp_action_en, email['body']) @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_post_multi_lang_recipients(self): """ Test posting on a document in a multilang environment. Currently current user's lang determines completely language used for notification layout notably, when no template is involved. Lang layout for this test (to better check various configuration and check which lang wins the final output, if any) * current users: various between en and es; * partner1: es * partner2: en """ test_records = self.test_records.with_env(self.env) test_records.message_subscribe(partner_ids=(self.partner_1 + self.partner_2).ids) for employee_lang, email_layout_xmlid in product( ('en_US', 'es_ES'), (False, 'mail.test_layout'), ): with self.subTest(employee_lang=employee_lang, email_layout_xmlid=email_layout_xmlid): self.user_employee.write({ 'lang': employee_lang, }) for record in test_records: with self.mock_mail_gateway(mail_unlink_sent=False), \ self.mock_mail_app(): record.message_post( body=Markup('Hi there
'), email_layout_xmlid=email_layout_xmlid, message_type='comment', subject='TeDeum', subtype_xmlid='mail.mt_comment', ) message = record.message_ids[0] self.assertEqual( message.notified_partner_ids, self.partner_1 + self.partner_2 ) # check created mail.mail and outgoing emails. One email # is generated for each partner 'partner_1' and 'partner_2' # different language thus different layout for partner in self.partner_1 + self.partner_2: _mail = self.assertMailMail( partner, 'sent', mail_message=message, author=self.partner_employee, email_values={ 'body_content': 'Hi there
', 'email_from': self.partner_employee.email_formatted, 'subject': 'TeDeum', }, ) # Low-level checks on outgoing email for the recipient to # check layouting and language. Note that standard layout # is not tested against translations, only the custom one # to ease translations checks. for partner, exp_lang in zip( self.partner_1 + self.partner_2, ('en_US', 'es_ES') ): email = self._find_sent_email( self.partner_employee.email_formatted, [partner.email_formatted] ) self.assertTrue(bool(email), 'Email not found, check recipients') self.assertEqual(partner.lang, exp_lang, 'Test misconfiguration') exp_layout_content_en = 'English Layout for Lang Chatter Model' exp_layout_content_es = 'Spanish Layout para Spanish Model Description' exp_button_en = 'View Lang Chatter Model' exp_button_es = 'SpanishView Spanish Model Description' exp_action_en = 'NotificationButtonTitle' exp_action_es = 'SpanishButtonTitle' if email_layout_xmlid: if exp_lang == 'es_ES': self.assertIn(exp_layout_content_es, email['body']) self.assertIn(exp_button_es, email['body']) self.assertIn(exp_action_es, email['body']) else: self.assertIn(exp_layout_content_en, email['body']) self.assertIn(exp_button_en, email['body']) self.assertIn(exp_action_en, email['body']) else: # check default layouting applies if exp_lang == 'es_ES': self.assertIn('html lang="es_ES"', email['body']) else: self.assertIn('html lang="en_US"', email['body'])