From b4d210b245734e68547c14e313aa024f0478a112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Feb 2023 17:02:41 +0000 Subject: [PATCH] [REF] mail: split notification email generation by recipient language RATIONALE Naming conventions * content = core message / subject (+ other fields); * email layout = layouting applied to messages when sent to recipients who receive notifications by email e.g. access button, ...; * comment mode / mailing mode: composer main composition mode: posting on record(s) / sending a mailing on records; Content situation when using a template to post on a composer * comment, monorecord mode: choose a template, content is rendered directly from template side according to 'lang' field definition -> ok; * comment, multirecord mode / mailing mode: choose a template, content is the raw content. At post / send, content is rendered and translated if matching template content -> ok; Email layout situation when posting / mailing * comment: layout translated based on template 'lang' field definition or fallback on current user's lang; * mailing: no layout supported; PURPOSE Translate layout into recipient's lang when available, instead of either current user or template defined lang. As it is contextual better translate it to the end user lang. SPECIFICATIONS Move recipients and email layout rendering into a method returning an iterator. That way a list of recipient groups (as given by '_notify_get_recipients') can be split into sub-groups, with each having its own rendering values for email notifications. Do a split / recipient lang. It is already fetched when notifying messages as part of '_get_recipient_data' returned values. We now return recipients groups per lang and per usage type (user, portal, customer, ...). Rendering values are computed for each group as several values depend on the lang ( actions, notification buttons, global layout, ...). Layouts when posting are dependent on recipient lang, and not on current user anymore. Template lang that forced the content lang is now used only as a fallback when recipient have no lang. SUMMARY When a user in Spanish, posts using a template specifying the lang of the customer on a record whose customer is in French, with a follower being in English * content will be translated following template choice, aka french; * layouting for the french customer will be in french, layouting for the english customer is in english, while the core content is always the same and in french; When a user in Spanish, posts some custome content on a record whose customer is in French, with a follower being in English * content is whatever the user wrote; * layouting for the french customer will be in french, layouting for the english customer is in english, while the core content is always the same and in french; Task-3046371 (Mail: Better Language Support in Composer) Task-2555155 (Mail: Translate notification action/access buttons) Task-3186426 (Mail: Support notification layout in template / email composer) Part-of: odoo/odoo#106177 Co-authored-by: Julien Banken --- addons/mail/models/mail_thread.py | 180 ++++++++++++++----- addons/mail/models/models.py | 6 +- addons/test_mail/tests/test_mail_composer.py | 126 +++++++++---- addons/test_mail/tests/test_message_post.py | 108 +++++++---- 4 files changed, 310 insertions(+), 110 deletions(-) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index e69e4491f8c..279631b4403 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -3012,36 +3012,9 @@ class MailThread(models.AbstractModel): if not partners_data: return True - lang = force_email_lang if force_email_lang else self.env.context.get('lang') - - if not model_description: - model_description = self.with_context(lang=lang)._get_model_description( - msg_vals['model'] if msg_vals and msg_vals.get('model') else message.model - ) - recipients_groups_list = self.with_context(lang=lang)._notify_get_recipients_classify( + base_mail_values = self._notify_by_email_get_base_mail_values( message, - partners_data, - model_description, - msg_vals=msg_vals, - ) - - if not recipients_groups_list: - return True - force_send = self.env.context.get('mail_notify_force_send', force_send) - - template_values = self.with_context(lang=lang)._notify_by_email_prepare_rendering_context( - message, msg_vals=msg_vals, model_description=model_description, - force_email_company=force_email_company, - force_email_lang=force_email_lang, - ) # 10 queries - if subtitles: - template_values['subtitles'] = subtitles - - email_layout_xmlid = msg_vals.get('email_layout_xmlid') if msg_vals else message.email_layout_xmlid - template_xmlid = email_layout_xmlid if email_layout_xmlid else 'mail.mail_notification_layout' - base_mail_values = self.with_context(lang=lang)._notify_by_email_get_base_mail_values( - message, - additional_values={'auto_delete': mail_auto_delete}, + additional_values={'auto_delete': mail_auto_delete} ) # Clean the context to get rid of residual default_* keys that could cause issues during @@ -3057,18 +3030,23 @@ class MailThread(models.AbstractModel): # loop on groups (customer, portal, user, ... + model specific like group_sale_salesman) notif_create_values = [] recipients_max = 50 - for recipients_group in recipients_groups_list: + for _lang, render_values, recipients_group in self._notify_get_classified_recipients_iterator( + message, + partners_data, + msg_vals=msg_vals, + model_description=model_description, + force_email_company=force_email_company, + force_email_lang=force_email_lang, + subtitles=subtitles, + ): # generate notification email content + mail_body = self._notify_by_email_render_layout( + message, + recipients_group, + msg_vals=msg_vals, + render_values=render_values, + ) recipients_ids = recipients_group.pop('recipients') - render_values = {**template_values, **recipients_group} - # {company, is_discussion, lang, message, model_description, record, record_name, signature, subtype, tracking_values, website_url} - # {actions, button_access, has_button_access, recipients} - - mail_body = self.env['ir.qweb']._render(template_xmlid, render_values, minimal_qcontext=True, raise_if_not_found=False, lang=template_values['lang']) - if not mail_body: - _logger.warning('QWeb template %s not found or is empty when sending notification emails. Sending without layouting.', template_xmlid) - mail_body = message.body - mail_body = self.env['mail.render.mixin']._replace_local_links(mail_body) # create email for recipients_ids_chunk in split_every(recipients_max, recipients_ids): @@ -3113,6 +3091,7 @@ class MailThread(models.AbstractModel): # to prevent sending email during a simple update of the database # using the command-line. test_mode = getattr(threading.current_thread(), 'testing', False) + force_send = self.env.context.get('mail_notify_force_send', force_send) if force_send and len(emails) < recipients_max and (not self.pool._init or test_mode): # unless asked specifically, send emails after the transaction to # avoid side effects due to emails being sent while the transaction fails @@ -3132,6 +3111,86 @@ class MailThread(models.AbstractModel): return True + def _notify_get_classified_recipients_iterator( + self, message, recipients_data, msg_vals=False, + model_description=False, force_email_company=False, force_email_lang=False, # rendering + subtitles=None): + """ Make groups of recipients, based on 'recipients_data' which is a list + of recipients informations. Purpose of this method is to group them by + main usage ('user', 'portal_user', 'follower', 'customer', ... see + @_notify_get_recipients_classify) and lang. Each group is linked to + an evaluation context to render the notification layout. + + :param message: ``mail.message`` record to notify; + :param list recipients_data: see ``MailThread._notify_get_recipients``; + :param msg_vals: dictionary of values used to create the message. If + given it may be used to access values related to ``message``; + + :param str model_description: description of current model, given to + avoid fetching it and easing translation support; + :param record force_email_company: record used when rendering + notification layout. Otherwise computed based on current record; + :param str force_email_lang: when no specific lang is found this is the + default lang to use notably to compute model name or translate access + buttons; + :param list subtitles: optional list set as template value "subtitles"; + + :return: iterator based on recipients classified by lang, with their + rendering evaluation context. Each item is a tuple containing ( + lang: used for rendering (customer language, forced email, default + environment language, + render_values: used to render the notification layout and translated + using lang, + recipients_group: a recipients group is a dict containing data + defined in "_notify_get_recipients_groups" like { + 'active': if not, it is skipped in notification process (ease + inheritance to be already present); + 'actions': list of actions to display as links or buttons in form + {'url': link of the action, 'title': link or button + string}; + 'button_access': main access document button information, {'url' + link of the access, 'title': link or button + string}; + 'has_button_access': display access document main button in email; + 'notification_group_name': name of the group, to ease usage; + 'recipients': list of partner IDs, will be fillup when evaluating + groups; + } + ); + """ + lang_to_recipients = {} + for data in recipients_data: + lang_to_recipients.setdefault( + data.get('lang') or force_email_lang or self.env.lang, + [], + ).append(data) + + for lang, lang_recipients_data in lang_to_recipients.items(): + record_wlang = self.with_context(lang=lang) + lang_model_description = model_description + if not lang_model_description: + lang_model_description = record_wlang._get_model_description( + msg_vals['model'] if msg_vals and msg_vals.get('model') else message.model + ) + recipients_groups_list = record_wlang._notify_get_recipients_classify( + message, + lang_recipients_data, + lang_model_description, + msg_vals=msg_vals, + ) + render_values = record_wlang._notify_by_email_prepare_rendering_context( + message, + msg_vals=msg_vals, + model_description=lang_model_description, + force_email_company=force_email_company, + force_email_lang=lang, + ) # 10 queries + if subtitles: + render_values['subtitles'] = subtitles + + for recipients_group in recipients_groups_list: + yield (lang, render_values, recipients_group) + def _notify_by_email_prepare_rendering_context(self, message, msg_vals=False, model_description=False, force_email_company=False, @@ -3168,7 +3227,7 @@ class MailThread(models.AbstractModel): """ if msg_vals is False: msg_vals = {} - lang = force_email_lang if force_email_lang else self.env.context.get('lang') + lang = force_email_lang if force_email_lang else self.env.lang record_wlang = self.with_context(lang=lang) # compute send user and its related signature; try to use self.env.user instead of browsing @@ -3235,6 +3294,47 @@ class MailThread(models.AbstractModel): 'is_html_empty': is_html_empty, } + def _notify_by_email_render_layout(self, message, recipients_group, + msg_vals=False, + render_values=None): + """ Renders the email layout for a given recipients group which + encapsulate the message body. + + :param record message: record being notified. May be + void as 'msg_vals' superseeds it; + :param dict recipients_group: a dict containing data for the recipients, + see @ _notify_get_recipients_groups; + :param dict msg_vals: values dict used to create the message, allows to + skip message usage and spare some queries; + :param dict render_values: values to render the notification layout; + + At this point expected values are + render_values: company, is_discussion, lang, message, model_description, + record, record_name, signature, subtype, tracking_values, + website_url + recipients_group: actions, button_access, has_button_access, recipients + + :return str: rendered complete layout; + """ + if render_values is None: + render_values = {} + + email_layout_xmlid = msg_vals.get('email_layout_xmlid') if msg_vals else message.email_layout_xmlid + template_xmlid = email_layout_xmlid if email_layout_xmlid else 'mail.mail_notification_layout' + + render_values = {**render_values, **recipients_group} + mail_body = self.env['ir.qweb']._render( + template_xmlid, + render_values, + minimal_qcontext=True, + raise_if_not_found=False, + lang=render_values.get('lang', self.env.lang), + ) + if not mail_body: + _logger.warning('QWeb template %s not found or is empty when sending notification emails. Sending without layouting.', template_xmlid) + mail_body = message.body + return mail_body + def _notify_by_email_get_base_mail_values(self, message, additional_values=None): """ Return model-specific and message-related values to be used when creating notification emails. It serves as a common basis for all diff --git a/addons/mail/models/models.py b/addons/mail/models/models.py index 33ffde8786b..a58ba9116ee 100644 --- a/addons/mail/models/models.py +++ b/addons/mail/models/models.py @@ -221,7 +221,7 @@ class BaseModel(models.AbstractModel): return E.activity(templates, string=self._description) # ------------------------------------------------------------ - # GATEWAY: NOTIFICATION + # DISCUSS # ------------------------------------------------------------ def _mail_get_message_subtypes(self): @@ -229,6 +229,10 @@ class BaseModel(models.AbstractModel): '&', ('hidden', '=', False), '|', ('res_model', '=', self._name), ('res_model', '=', False)]) + # ------------------------------------------------------------ + # GATEWAY: NOTIFICATION + # ------------------------------------------------------------ + def _notify_by_email_get_headers(self): """ Generate the email headers based on record """ if not self: diff --git a/addons/test_mail/tests/test_mail_composer.py b/addons/test_mail/tests/test_mail_composer.py index 4c3f696ef50..1ed0a086087 100644 --- a/addons/test_mail/tests/test_mail_composer.py +++ b/addons/test_mail/tests/test_mail_composer.py @@ -1366,6 +1366,14 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): Test with and without notification layout specified. Test with and without languages. + + Setup with batch and langs + * record1: customer lang=es_ES + follower partner_employee_2 lang=en_US + 3 new partners (en_US) created by template + * record2: customer lang=en_US + follower partner_employee_2 lang=en_US + 3 new partners (en_US) created by template """ attachment_data = self._generate_attachments_data(2, self.template._name, self.template.id) email_to_1 = 'test.to.1@test.example.com' @@ -1465,10 +1473,32 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): self.assertFalse(self._mails) # monorecord: force_send notifications elif not batch: + # as there are recipients with different langs: we have + # 3 outgoing mails: partner_employee2 (user) then customers + # in two langs + if use_lang: + self.assertEqual( + len(self._new_mails), 3, + 'Should have created 1 mail for user, then 2 for customers that belong to 2 langs') + # without lang, recipients are grouped by main usage aka user and customer + else: + self.assertEqual( + len(self._new_mails), 2, + 'Should have created 1 mail for user, then 1 for customers') self.assertEqual(self._new_mails.mapped('state'), ['sent'] * len(self._new_mails)) self.assertEqual(len(self._mails), 5, 'Should have sent 5 emails, one per recipient per record') # multirecord: use email queue else: + # see not-batch comment, then add 2 mails for the second + # record as all customers have same language + if use_lang: + self.assertEqual( + len(self._new_mails), 5, + 'Should have created 3 mails for first record, then 2 for second') + else: + self.assertEqual( + len(self._new_mails), 4, + 'Should have created 2 mails / record (one for user, one for customers)') self.assertEqual(self._new_mails.mapped('state'), ['outgoing'] * len(self._new_mails)) self.assertEqual(len(self._mails), 0, 'Should have put emails in queue and not sent any emails') # simulate cron sending emails @@ -1505,18 +1535,36 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): freeze_time(self.reference_now + timedelta(days=3)): self.env['mail.message.schedule'].sudo()._send_notifications_cron() - # global outgoing: one mail.mail (all customer recipients, then all employee recipients) - # and 5 emails, and 1 inbox notification (admin) - self.assertEqual(len(self._new_mails), 2 * len(test_records), - 'Should have created 1 mail.mail per group per record') + # monorecord: force_send notifications if not batch: + # as there are recipients with different langs: we have + # 3 outgoing mails: partner_employee2 (user) then customers + # in two langs + if use_lang: + self.assertEqual( + len(self._new_mails), 3, + 'Should have created 1 mail for user, then 2 for customers that belong to 2 langs') + # without lang, recipients are grouped by main usage aka user and customer + else: + self.assertEqual( + len(self._new_mails), 2, + 'Should have created 1 mail for user, then 1 for customers') self.assertEqual(self._new_mails.mapped('state'), ['sent'] * len(self._new_mails)) - self.assertEqual(len(self._mails), 5, - 'Should have sent 5 emails, one per recipient per record') + self.assertEqual(len(self._mails), 5, 'Should have sent 5 emails, one per recipient per record') + # multirecord: use email queue else: + # see not-batch comment, then add 2 mails for the second + # record as all customers have same language + if use_lang: + self.assertEqual( + len(self._new_mails), 5, + 'Should have created 3 mails for first record, then 2 for second') + else: + self.assertEqual( + len(self._new_mails), 4, + 'Should have created 2 mails / record (one for user, one for customers)') self.assertEqual(self._new_mails.mapped('state'), ['outgoing'] * len(self._new_mails)) - self.assertEqual(len(self._mails), 0, - 'Should have kept force_send False parameter, and not sent any email') + self.assertEqual(len(self._mails), 0, 'Should have put emails in queue and not sent any emails') # simulate cron sending emails self.env['mail.mail'].sudo().process_email_queue() @@ -1525,12 +1573,13 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): message = test_record.message_ids[0] # check created mail.mail and outgoing emails. In comment - # 2 mails are generated (due to group-based layouting): + # 2 or 3 mails are generated (due to group-based layouting): # - one for recipient that is a user - # - one for recipients that are customers - # Then each recipient receives its own outging email. See + # - one / two for recipients that are customers, one / lang + # Then each recipient receives its own outgoing email. See # 'assertMailMail' for more details. + # user email (one user, one email) if exp_lang == 'es_ES': exp_body = f'SpanishBody for {test_record.name}' exp_subject = f'SpanishSubject for {test_record.name}' @@ -1555,29 +1604,46 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): 'mail_server_id': self.mail_server_domain, }, ) - self.assertMailMail(test_record.customer_id + new_partners, 'sent', - mail_message=message, - author=author, # author is different in batch and monorecord mode (raw or rendered email_from) - email_values={ - 'body_content': exp_body, - 'email_from': test_record.user_id.email_formatted, # set by template - 'subject': exp_subject, - 'attachments_info': [ - {'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'}, - {'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'}, - {'name': f'TestReport for {test_record.name}.html', 'type': 'text/plain'}, - {'name': f'TestReport2 for {test_record.name}.html', 'type': 'text/plain'}, - ] - }, - fields_values={ - 'mail_server_id': self.mail_server_domain, - }, - ) - # Low-level checks on outgoing email for the recipient to + # customers emails (several customers, one or two emails depending + # on multi-lang testing environment) + if use_lang and test_record == test_records[0]: + # in this case, we are in a multi-lang customers testing + emails_recipients = [ + test_record.customer_id, # es_ES + new_partners # en_US (default lang of new customers) + ] + else: + # all recipients have same language, one email + emails_recipients = [test_record.customer_id + new_partners] + + for recipients in emails_recipients: + self.assertMailMail(recipients, 'sent', + mail_message=message, + author=author, # author is different in batch and monorecord mode (raw or rendered email_from) + email_values={ + 'body_content': exp_body, + 'email_from': test_record.user_id.email_formatted, # set by template + 'subject': exp_subject, + 'attachments_info': [ + {'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'}, + {'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'}, + {'name': f'TestReport for {test_record.name}.html', 'type': 'text/plain'}, + {'name': f'TestReport2 for {test_record.name}.html', 'type': 'text/plain'}, + ] + }, + fields_values={ + 'mail_server_id': self.mail_server_domain, + }, + ) + + # Specifically for the language-specific recipient, perform + # 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. + # We could do the check for other layouts but it would be + # mainly noisy / duplicated check email = self._find_sent_email(test_record.user_id.email_formatted, [test_record.customer_id.email_formatted]) self.assertTrue(bool(email), 'Email not found, check recipients') diff --git a/addons/test_mail/tests/test_message_post.py b/addons/test_mail/tests/test_message_post.py index 0d3aa00f546..99aa1a72673 100644 --- a/addons/test_mail/tests/test_message_post.py +++ b/addons/test_mail/tests/test_message_post.py @@ -1518,8 +1518,9 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): @mute_logger('odoo.addons.mail.models.mail_mail') def test_composer_lang_template_comment(self): """ When posting in comment mode, content is rendered using the lang - field of template. Notification layout lang is partly coming from - template, partly from environment (to be improved). """ + field of template. Notification layout lang is the one from the + customer to personalize the context. When not found it fallbacks + on rendered template lang or environment lang. """ test_record = self.test_records[0].with_user(self.env.user) test_template = self.test_template.with_user(self.env.user) @@ -1566,9 +1567,8 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): else: self.assertEqual(f'SpanishSubject for {test_record.name}', customer_email['subject'], 'Subject based on template should be translated') - # check notification layout content: currently partly translated - # based only on template definition - if exp_content_lang == 'en_US': + # check notification layout content: depends on customer lang + if exp_notif_lang == 'en_US': self.assertNotIn('Spanish Layout para', body, 'Layout translation failed') self.assertIn('English Layout for Lang Chatter Model', body, 'Layout / model translation failed') @@ -1583,14 +1583,19 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): self.assertIn('NotificationButtonTitle', body, 'Groups-based action names translation failed') else: + self.assertNotIn('English Layout for', body, 'Layout translation failed') + self.assertIn('Spanish Layout para Spanish Model Description', body, + 'Layout / model translation failed') + self.assertNotIn('Lang Chatter Model', body, 'Model translation failed') + # check notification layout strings self.assertIn('SpanishView Spanish Model Description', body, - '"View document" should be translated') + '"View document" translation failed') self.assertNotIn(f'View {test_record._description}', body, - '"View document" should be translated') + '"View document" translation failed') self.assertIn('SpanishButtonTitle', body, - 'Groups-based action names should be translated') + 'Groups-based action names translation failed') self.assertNotIn('NotificationButtonTitle', body, - 'Groups-based action names should be translated') + 'Groups-based action names translation failed') @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') @@ -1658,7 +1663,8 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): @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 and layout are is translated based on template definition. """ + 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) @@ -1673,7 +1679,11 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): 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 in zip(test_records, record0_customer + self.partner_2): + 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) @@ -1688,19 +1698,34 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): self.assertEqual(f'SpanishSubject for {record.name}', customer_email['subject'], 'Subject based on template should be translated') # check notification layout translation - 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, - 'Fixme: groups-based action names should be translated') + 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') @@ -1743,29 +1768,34 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): ) # check created mail.mail and outgoing emails. One email - # is generated for 'partner_1' and 'partner_2' (same email - # as same configuration (aka all customers)). - _mail = self.assertMailMail( - self.partner_1 + self.partner_2, 'sent', - mail_message=message, - author=self.partner_employee, - email_values={ - 'body_content': '

Hi there

', - 'email_from': self.partner_employee.email_formatted, - 'subject': 'TeDeum', - }, - ) + # 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 in self.partner_1 + self.partner_2: + 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' @@ -1774,7 +1804,7 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): exp_action_en = 'NotificationButtonTitle' exp_action_es = 'SpanishButtonTitle' if email_layout_xmlid: - if employee_lang == 'es_ES': + 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']) @@ -1784,7 +1814,7 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): self.assertIn(exp_action_en, email['body']) else: # check default layouting applies - if employee_lang == 'es_ES': + if exp_lang == 'es_ES': self.assertIn('html lang="es_ES"', email['body']) else: self.assertIn('html lang="en_US"', email['body'])