diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py index af4b10a36ca..326edbfacf0 100644 --- a/addons/mail/__manifest__.py +++ b/addons/mail/__manifest__.py @@ -2,7 +2,7 @@ { 'name': 'Discuss', - 'version': '1.3', + 'version': '1.4', 'category': 'Productivity/Discuss', 'sequence': 145, 'summary': 'Chat, mail gateway and private channels', diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 64d580dcab4..36ea4ba46cf 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -156,9 +156,9 @@ class Message(models.Model): help='Tracked values are stored in a separate model. This field allow to reconstruct ' 'the tracking and to generate statistics on the model.') # mail gateway - no_auto_thread = fields.Boolean( + reply_to_force_new = fields.Boolean( 'No threading for answers', - help='Answers do not go in the original document discussion thread. This has an impact on the generated message-id.') + help='If true, answers do not go in the original document discussion thread. Instead, it will check for the reply_to in tracking message-id and redirected accordingly. This has an impact on the generated message-id.') message_id = fields.Char('Message-Id', help='Message unique identifier', index=True, readonly=1, copy=False) reply_to = fields.Char('Reply-To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.') mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server') @@ -1207,7 +1207,7 @@ class Message(models.Model): @api.model def _get_message_id(self, values): - if values.get('no_auto_thread', False) is True: + if values.get('reply_to_force_new', False) is True: message_id = tools.generate_tracking_message_id('reply_to') elif self.is_thread_message(values): message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values) diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index af9e3b57469..68b642fac90 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -43,7 +43,7 @@ class MailTemplate(models.Model): partner_to = fields.Char('To (Partners)', help="Comma-separated ids of recipient partners (placeholders may be used here)") email_cc = fields.Char('Cc', help="Carbon copy recipients (placeholders may be used here)") - reply_to = fields.Char('Reply-To', help="Preferred response address (placeholders may be used here)") + reply_to = fields.Char('Reply To', help="Email address to which replies will be redirected when sending emails in mass; only used when the reply is not logged in the original discussion thread.") # content body_html = fields.Html('Body', translate=True, sanitize=False) attachment_ids = fields.Many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id', diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py index d8dcf213197..650f273e1ae 100644 --- a/addons/mail/tests/common.py +++ b/addons/mail/tests/common.py @@ -128,7 +128,8 @@ class MockEmail(common.BaseCase): """ Deprecated, remove in 14.4 """ return self.gateway_mail_reply_wrecord(template, record, use_in_reply_to=use_in_reply_to) - def gateway_mail_reply_wrecord(self, template, record, use_in_reply_to=True): + def gateway_mail_reply_wrecord(self, template, record, use_in_reply_to=True, + target_model=None, target_field=None): """ Simulate a reply through the mail gateway. Usage: giving a record, find an email sent to him and use its message-ID to simulate a reply. @@ -148,8 +149,32 @@ class MockEmail(common.BaseCase): subject='Re: %s' % mail_mail.subject, extra=extra, msg_id='<123456.%s.%d@test.example.com>' % (record._name, record.id), - target_model=record._name, - target_field=record._rec_name, + target_model=target_model or record._name, + target_field=target_field or record._rec_name, + ) + + def gateway_mail_reply_wemail(self, template, email_to, use_in_reply_to=True, + target_model=None, target_field=None): + """ Simulate a reply through the mail gateway. Usage: giving a record, + find an email sent to him and use its message-ID to simulate a reply. + + Some noise is added in References just to test some robustness. """ + sent_mail = self._find_sent_mail_wemail(email_to) + + if use_in_reply_to: + extra = 'In-Reply-To:\r\n\t%s\n' % sent_mail['message_id'] + else: + disturbing_other_msg_id = '<123456.654321@another.host.com>' + extra = 'References:\r\n\t%s\n\r%s' % (sent_mail['message_id'], disturbing_other_msg_id) + + return self.format_and_process( + template, + sent_mail['email_to'], + sent_mail['reply_to'], + subject='Re: %s' % sent_mail['subject'], + extra=extra, + target_model=target_model, + target_field=target_field or 'name', ) def from_string(self, text): diff --git a/addons/mail/views/mail_message_views.xml b/addons/mail/views/mail_message_views.xml index 401c4131e2c..57d3df6316b 100644 --- a/addons/mail/views/mail_message_views.xml +++ b/addons/mail/views/mail_message_views.xml @@ -52,7 +52,7 @@ - + diff --git a/addons/mail/views/mail_template_views.xml b/addons/mail/views/mail_template_views.xml index 14381000377..7626f6fe867 100644 --- a/addons/mail/views/mail_template_views.xml +++ b/addons/mail/views/mail_template_views.xml @@ -59,7 +59,7 @@ + placeholder="Email address to which replies will be redirected when sending emails in mass"/> diff --git a/addons/mail/views/res_partner_views.xml b/addons/mail/views/res_partner_views.xml index da5cc279ede..54aaaab18bb 100644 --- a/addons/mail/views/res_partner_views.xml +++ b/addons/mail/views/res_partner_views.xml @@ -111,7 +111,7 @@ 'default_composition_mode': 'mass_mail', 'default_partner_to': '${object.id or \'\'}', 'default_use_template': False, - 'default_no_auto_thread': True, + 'default_reply_to_force_new': True, }"/> list diff --git a/addons/mail/wizard/invite.py b/addons/mail/wizard/invite.py index ffafa2788be..ef4400441ab 100644 --- a/addons/mail/wizard/invite.py +++ b/addons/mail/wizard/invite.py @@ -67,7 +67,7 @@ class Invite(models.TransientModel): 'reply_to': email_from, 'model': wizard.res_model, 'res_id': wizard.res_id, - 'no_auto_thread': True, + 'reply_to_force_new': True, 'add_sign': True, }) partners_data = [] diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index db33cad58af..1b0e4437160 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -67,10 +67,10 @@ class MailComposer(models.TransientModel): result['model'] = self._context.get('active_model') if 'res_id' in fields and 'res_id' not in result: result['res_id'] = self._context.get('active_id') - if 'no_auto_thread' in fields and 'no_auto_thread' not in result and result.get('model'): + if 'reply_to_mode' in fields and 'reply_to_mode' not in result and result.get('model'): # doesn't support threading if result['model'] not in self.env or not hasattr(self.env[result['model']], 'message_post'): - result['no_auto_thread'] = True + result['reply_to_mode'] = 'new' if 'active_domain' in self._context: # not context.get() because we want to keep global [] domains result['active_domain'] = '%s' % self._context.get('active_domain') @@ -123,10 +123,15 @@ class MailComposer(models.TransientModel): 'mail.activity.type', 'Mail Activity Type', index=True, ondelete='set null') # destination - reply_to = fields.Char('Reply-To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.') - no_auto_thread = fields.Boolean( - 'No threading for answers', - help='Answers do not go in the original document discussion thread. This has an impact on the generated message-id.') + reply_to = fields.Char('Reply To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.') + reply_to_force_new = fields.Boolean( + string='Considers answers as new thread', + help='Manage answers as new incoming emails instead of replies going to the same thread.') + reply_to_mode = fields.Selection([ + ('update', 'Log in the original discussion thread'), + ('new', 'Redirect to another email address')], + string='Replies', compute='_compute_reply_to_mode', inverse='_inverse_reply_to_mode', + help="Original Discussion: Answers go in the original document discussion thread. \n Another Email Address: Answers go to the email address mentioned in the tracking message-id instead of original document discussion thread. \n This has an impact on the generated message-id.") is_log = fields.Boolean('Log an Internal Note', help='Whether the message is an internal note (comment mode only)') partner_ids = fields.Many2many( @@ -140,6 +145,15 @@ class MailComposer(models.TransientModel): auto_delete_message = fields.Boolean('Delete Message Copy', help='Do not keep a copy of the email in the document communication history (mass mailing only)') mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server') + @api.depends('reply_to_force_new') + def _compute_reply_to_mode(self): + for composer in self: + composer.reply_to_mode = 'new' if composer.reply_to_force_new else 'update' + + def _inverse_reply_to_mode(self): + for composer in self: + composer.reply_to_force_new = composer.reply_to_mode == 'new' + @api.model def get_record_data(self, values): """ Returns a defaults-like dict with initial values for the composition @@ -275,7 +289,7 @@ class MailComposer(models.TransientModel): rendered_values = self.render_message(res_ids) # compute alias-based reply-to in batch reply_to_value = dict.fromkeys(res_ids, None) - if mass_mail_mode and not self.no_auto_thread: + if mass_mail_mode and not self.reply_to_force_new: records = self.env[self.model].browse(res_ids) reply_to_value = records._notify_get_reply_to(default=self.email_from) @@ -301,7 +315,7 @@ class MailComposer(models.TransientModel): 'author_id': self.author_id.id, 'email_from': self.email_from, 'record_name': self.record_name, - 'no_auto_thread': self.no_auto_thread, + 'reply_to_force_new': self.reply_to_force_new, 'mail_server_id': self.mail_server_id.id, 'mail_activity_type_id': self.mail_activity_type_id.id, } @@ -319,11 +333,11 @@ class MailComposer(models.TransientModel): email_dict = rendered_values[res_id] mail_values['partner_ids'] += email_dict.pop('partner_ids', []) mail_values.update(email_dict) - if not self.no_auto_thread: + if not self.reply_to_force_new: mail_values.pop('reply_to') if reply_to_value.get(res_id): mail_values['reply_to'] = reply_to_value[res_id] - if self.no_auto_thread and not mail_values.get('reply_to'): + if self.reply_to_force_new and not mail_values.get('reply_to'): mail_values['reply_to'] = mail_values['email_from'] # mail_mail values: body -> body_html, partner_ids -> recipient_ids mail_values['body_html'] = mail_values.get('body', '') diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index 95a78247852..d8cac0c74c2 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -62,10 +62,11 @@ - - + + + diff --git a/addons/mass_mailing/__manifest__.py b/addons/mass_mailing/__manifest__.py index 69ae29f29c0..51f1bd6f86d 100644 --- a/addons/mass_mailing/__manifest__.py +++ b/addons/mass_mailing/__manifest__.py @@ -5,7 +5,7 @@ 'name': 'Email Marketing', 'summary': 'Design, send and track emails', 'description': "", - 'version': '2.3', + 'version': '2.4', 'sequence': 60, 'website': 'https://www.odoo.com/page/mailing', 'category': 'Marketing/Email Marketing', diff --git a/addons/mass_mailing/data/mass_mailing_demo.xml b/addons/mass_mailing/data/mass_mailing_demo.xml index 17674fb4136..15e9cc2b790 100644 --- a/addons/mass_mailing/data/mass_mailing_demo.xml +++ b/addons/mass_mailing/data/mass_mailing_demo.xml @@ -83,7 +83,7 @@ - email + new Info <info@yourcompany.example.com>
diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py index c4e3c1e9724..82abae99248 100644 --- a/addons/mass_mailing/models/mailing.py +++ b/addons/mass_mailing/models/mailing.py @@ -113,7 +113,7 @@ class MassMailing(models.Model): # mailing options mailing_type = fields.Selection([('mail', 'Email')], string="Mailing Type", default="mail", required=True) reply_to_mode = fields.Selection([ - ('thread', 'Recipient Followers'), ('email', 'Specified Email Address')], + ('update', 'Recipient Followers'), ('new', 'Specified Email Address')], string='Reply-To Mode', compute='_compute_reply_to_mode', readonly=False, store=True, help='Thread: replies go to target document. Email: replies are routed to a given email.') @@ -252,16 +252,16 @@ class MassMailing(models.Model): def _compute_reply_to_mode(self): for mailing in self: if mailing.mailing_model_real in ['res.partner', 'mailing.contact']: - mailing.reply_to_mode = 'email' + mailing.reply_to_mode = 'new' else: - mailing.reply_to_mode = 'thread' + mailing.reply_to_mode = 'update' @api.depends('reply_to_mode') def _compute_reply_to(self): for mailing in self: - if mailing.reply_to_mode == 'email' and not mailing.reply_to: + if mailing.reply_to_mode == 'new' and not mailing.reply_to: mailing.reply_to = self.env.user.email_formatted - elif mailing.reply_to_mode == 'thread': + elif mailing.reply_to_mode == 'update': mailing.reply_to = False @api.depends('mailing_model_name', 'contact_list_ids') @@ -631,11 +631,11 @@ class MassMailing(models.Model): 'composition_mode': 'mass_mail', 'mass_mailing_id': mailing.id, 'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids], - 'no_auto_thread': mailing.reply_to_mode != 'thread', + 'reply_to_force_new': mailing.reply_to_mode == 'new', 'template_id': None, 'mail_server_id': mailing.mail_server_id.id, } - if mailing.reply_to_mode == 'email': + if mailing.reply_to_mode == 'new': composer_values['reply_to'] = mailing.reply_to composer = self.env['mail.compose.message'].with_context(active_ids=res_ids).create(composer_values) diff --git a/addons/mass_mailing/tests/test_mailing_internals.py b/addons/mass_mailing/tests/test_mailing_internals.py index bc5408c288b..361de85c299 100644 --- a/addons/mass_mailing/tests/test_mailing_internals.py +++ b/addons/mass_mailing/tests/test_mailing_internals.py @@ -72,7 +72,7 @@ class TestMassMailValues(MassMailCommon): self.assertEqual(mailing.medium_id, self.env.ref('utm.utm_medium_email')) self.assertEqual(mailing.mailing_model_name, 'res.partner') self.assertEqual(mailing.mailing_model_real, 'res.partner') - self.assertEqual(mailing.reply_to_mode, 'email') + self.assertEqual(mailing.reply_to_mode, 'new') self.assertEqual(mailing.reply_to, self.user_marketing.email_formatted) # default for partner: remove blacklisted self.assertEqual(literal_eval(mailing.mailing_domain), [('is_blacklisted', '=', False)]) @@ -89,7 +89,7 @@ class TestMassMailValues(MassMailCommon): }) self.assertEqual(mailing.mailing_model_name, 'mailing.list') self.assertEqual(mailing.mailing_model_real, 'mailing.contact') - self.assertEqual(mailing.reply_to_mode, 'email') + self.assertEqual(mailing.reply_to_mode, 'new') self.assertEqual(mailing.reply_to, self.email_reply_to) # default for mailing list: depends upon contact_list_ids self.assertEqual(literal_eval(mailing.mailing_domain), []) @@ -104,7 +104,7 @@ class TestMassMailValues(MassMailCommon): }) self.assertEqual(mailing.mailing_model_name, 'mail.channel') self.assertEqual(mailing.mailing_model_real, 'mail.channel') - self.assertEqual(mailing.reply_to_mode, 'thread') + self.assertEqual(mailing.reply_to_mode, 'update') self.assertFalse(mailing.reply_to) @users('user_marketing') @@ -213,6 +213,79 @@ class TestMassMailFeatures(MassMailCommon, CronMixinCase): capt.records.ensure_one() self.assertLessEqual(capt.records.call_at, truth) + @users('user_marketing') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_mailing_deletion(self): + """ Test deletion in various use case, depending on reply-to """ + # 1- Keep archives and reply-to set to 'answer = new thread' + mailing = self.env['mailing.mailing'].create({ + 'name': 'TestSource', + 'subject': 'TestDeletion', + 'body_html': "
Hello {object.name}
", + 'mailing_model_id': self.env['ir.model']._get('mailing.list').id, + 'contact_list_ids': [(6, 0, self.mailing_list_1.ids)], + 'keep_archives': True, + 'reply_to_mode': 'new', + 'reply_to': self.email_reply_to, + }) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + with self.mock_mail_gateway(mail_unlink_sent=True): + mailing.action_send_mail() + + self.assertEqual(len(self._mails), 3) + self.assertEqual(len(self._new_mails.exists()), 3) + self.assertEqual(len(self.mailing_list_1.contact_ids.message_ids), 3) + + # 2- Keep archives and reply-to set to 'answer = update thread' + self.mailing_list_1.contact_ids.message_ids.unlink() + mailing = mailing.copy() + mailing.write({ + 'reply_to_mode': 'update', + }) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + with self.mock_mail_gateway(mail_unlink_sent=True): + mailing.action_send_mail() + + self.assertEqual(len(self._mails), 3) + self.assertEqual(len(self._new_mails.exists()), 3) + self.assertEqual(len(self.mailing_list_1.contact_ids.message_ids), 3) + + # 3- Remove archives and reply-to set to 'answer = new thread' + self.mailing_list_1.contact_ids.message_ids.unlink() + mailing = mailing.copy() + mailing.write({ + 'keep_archives': False, + 'reply_to_mode': 'new', + 'reply_to': self.email_reply_to, + }) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + with self.mock_mail_gateway(mail_unlink_sent=True): + mailing.action_send_mail() + + self.assertEqual(len(self._mails), 3) + self.assertEqual(len(self._new_mails.exists()), 0) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + # 4- Remove archives and reply-to set to 'answer = update thread' + # Imply keeping mail.message for gateway reply) + self.mailing_list_1.contact_ids.message_ids.unlink() + mailing = mailing.copy() + mailing.write({ + 'keep_archives': False, + 'reply_to_mode': 'update', + }) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + with self.mock_mail_gateway(mail_unlink_sent=True): + mailing.action_send_mail() + + self.assertEqual(len(self._mails), 3) + self.assertEqual(len(self._new_mails.exists()), 0) + self.assertEqual(len(self.mailing_list_1.contact_ids.message_ids), 3) + @users('user_marketing') @mute_logger('odoo.addons.mail.models.mail_mail') def test_mailing_on_res_partner(self): @@ -262,7 +335,7 @@ Website3: ${httpurl} External1: Youpie Email: test@odoo.com
""", 'mailing_model_id': self.env['ir.model']._get('mailing.list').id, - 'reply_to_mode': 'email', + 'reply_to_mode': 'new', 'reply_to': self.email_reply_to, 'contact_list_ids': [(6, 0, self.mailing_list_1.ids)], 'keep_archives': True, diff --git a/addons/mass_mailing/views/mailing_mailing_views.xml b/addons/mass_mailing/views/mailing_mailing_views.xml index 127b8349cd1..1236fbd6431 100644 --- a/addons/mass_mailing/views/mailing_mailing_views.xml +++ b/addons/mass_mailing/views/mailing_mailing_views.xml @@ -241,14 +241,14 @@ }"/>
+ attrs="{'invisible': ['|', ('reply_to_mode', '=', 'update'), ('mailing_model_name', 'in', ['mailing.contact', 'res.partner', 'mailing.list'])],}"> To track replies, this address must belong to this database.
diff --git a/addons/mass_mailing/wizard/mail_compose_message.py b/addons/mass_mailing/wizard/mail_compose_message.py index 19a345a9e47..39043268a3f 100644 --- a/addons/mass_mailing/wizard/mail_compose_message.py +++ b/addons/mass_mailing/wizard/mail_compose_message.py @@ -25,20 +25,18 @@ class MailComposeMessage(models.TransientModel): self.env['ir.model'].sudo().search([('model', '=', self.model), ('is_mail_thread', '=', True)], limit=1): mass_mailing = self.mass_mailing_id if not mass_mailing: - reply_to_mode = 'email' if self.no_auto_thread else 'thread' - reply_to = self.reply_to if self.no_auto_thread else False mass_mailing = self.env['mailing.mailing'].create({ - 'campaign_id': self.campaign_id.id, - 'name': self.mass_mailing_name, - 'subject': self.subject, - 'state': 'done', - 'reply_to_mode': reply_to_mode, - 'reply_to': reply_to, - 'sent_date': fields.Datetime.now(), - 'body_html': self.body, - 'mailing_model_id': self.env['ir.model']._get(self.model).id, - 'mailing_domain': self.active_domain, - 'attachment_ids': [(6, 0, self.attachment_ids.ids)], + 'campaign_id': self.campaign_id.id, + 'name': self.mass_mailing_name, + 'subject': self.subject, + 'state': 'done', + 'reply_to_mode': self.reply_to_mode, + 'reply_to': self.reply_to if self.reply_to_mode == 'new' else False, + 'sent_date': fields.Datetime.now(), + 'body_html': self.body, + 'mailing_model_id': self.env['ir.model']._get(self.model).id, + 'mailing_domain': self.active_domain, + 'attachment_ids': [(6, 0, self.attachment_ids.ids)], }) # Preprocess res.partners to batch-fetch from db @@ -87,7 +85,7 @@ class MailComposeMessage(models.TransientModel): 'mailing_id': mass_mailing.id, 'mailing_trace_ids': [(0, 0, trace_vals)], # email-mode: keep original message for routing - 'notification': mass_mailing.reply_to_mode == 'thread', + 'notification': mass_mailing.reply_to_mode == 'update', 'auto_delete': not mass_mailing.keep_archives, }) return res diff --git a/addons/mass_mailing_crm/data/mass_mailing_demo.xml b/addons/mass_mailing_crm/data/mass_mailing_demo.xml index 41dcaf2d0eb..21817f9bcf9 100644 --- a/addons/mass_mailing_crm/data/mass_mailing_demo.xml +++ b/addons/mass_mailing_crm/data/mass_mailing_demo.xml @@ -11,7 +11,7 @@ [] - email + new ${object.company_id.email}
diff --git a/addons/mass_mailing_sale/data/mass_mailing_demo.xml b/addons/mass_mailing_sale/data/mass_mailing_demo.xml index c1872022fa3..d80d975c1e9 100644 --- a/addons/mass_mailing_sale/data/mass_mailing_demo.xml +++ b/addons/mass_mailing_sale/data/mass_mailing_demo.xml @@ -11,7 +11,7 @@ [] - email + new ${object.company_id.email}
diff --git a/addons/test_mail/tests/test_mail_composer.py b/addons/test_mail/tests/test_mail_composer.py index 6c60ea3d293..17f8e1f5afb 100644 --- a/addons/test_mail/tests/test_mail_composer.py +++ b/addons/test_mail/tests/test_mail_composer.py @@ -110,7 +110,7 @@ class TestComposerForm(TestMailComposer): 'MailComposer: comment mode should have default subject Re: record_name') # record name not displayed currently in view # self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name') - self.assertFalse(composer_form.no_auto_thread) + self.assertFalse(composer_form.reply_to_force_new) self.assertEqual(composer_form.composition_mode, 'comment') self.assertEqual(composer_form.model, self.test_record._name) @@ -172,7 +172,7 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.subject, 'MailComposer: mass mode should have void default subject if no template') # record name not displayed currently in view # self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') - self.assertFalse(composer_form.no_auto_thread) + self.assertFalse(composer_form.reply_to_force_new) self.assertEqual(composer_form.composition_mode, 'mass_mail') self.assertEqual(composer_form.model, self.test_records._name) @@ -186,7 +186,7 @@ class TestComposerForm(TestMailComposer): 'MailComposer: mass mode should have template raw body if template') # record name not displayed currently in view # self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') - self.assertFalse(composer_form.no_auto_thread) + self.assertFalse(composer_form.reply_to_force_new) self.assertEqual(composer_form.composition_mode, 'mass_mail') self.assertEqual(composer_form.model, self.test_records._name) diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py index 8da6b228b9f..b76e5d9752b 100644 --- a/addons/test_mail/tests/test_mail_gateway.py +++ b/addons/test_mail/tests/test_mail_gateway.py @@ -1043,14 +1043,14 @@ class TestMailgateway(TestMailCommon): first_record = self.env['mail.test.simple'].with_user(self.user_employee).create({'name': 'Replies to Record'}) record_msg = first_record.message_post( subject='Discussion', - no_auto_thread=False, + reply_to_force_new=False, subtype_xmlid='mail.mt_comment', ) self.assertEqual(record_msg.reply_to, formataddr(('%s %s' % (self.user_employee.company_id.name, first_record.name), '%s@%s' % ('catchall.test', 'test.com')))) mail_msg = first_record.message_post( subject='Replies to Record', reply_to='groups@test.com', - no_auto_thread=True, + reply_to_force_new=True, subtype_xmlid='mail.mt_comment', ) self.assertEqual(mail_msg.reply_to, 'groups@test.com') diff --git a/addons/test_mail/tests/test_mail_message.py b/addons/test_mail/tests/test_mail_message.py index 8cf04011b6b..c654d9f58fc 100644 --- a/addons/test_mail/tests/test_mail_message.py +++ b/addons/test_mail/tests/test_mail_message.py @@ -169,11 +169,11 @@ class TestMessageValues(TestMailCommon): self.assertEqual(msg.reply_to, formataddr((reply_to_name, reply_to_email))) self.assertEqual(msg.email_from, formataddr((self.user_employee.name, self.user_employee.email))) - def test_mail_message_values_no_auto_thread(self): + def test_mail_message_values_reply_to_force_new(self): msg = self.Message.create({ 'model': 'mail.test.container', 'res_id': self.alias_record.id, - 'no_auto_thread': True, + 'reply_to_force_new': True, }) self.assertIn('reply_to', msg.message_id.split('@')[0]) self.assertNotIn('mail.test.container', msg.message_id.split('@')[0]) diff --git a/addons/test_mass_mailing/tests/__init__.py b/addons/test_mass_mailing/tests/__init__.py index 2391896e3e4..978ceb47d01 100644 --- a/addons/test_mass_mailing/tests/__init__.py +++ b/addons/test_mass_mailing/tests/__init__.py @@ -5,6 +5,6 @@ from . import test_blacklist from . import test_blacklist_behavior from . import test_blacklist_mixin from . import test_link_tracker -from . import test_mailing_internals -from . import test_mass_mailing +from . import test_mailing_test +from . import test_mailing from . import test_performance diff --git a/addons/test_mass_mailing/tests/common.py b/addons/test_mass_mailing/tests/common.py index 43b1ad27ea1..2a132a9656b 100644 --- a/addons/test_mass_mailing/tests/common.py +++ b/addons/test_mass_mailing/tests/common.py @@ -11,6 +11,13 @@ class TestMassMailCommon(MassMailCommon, TestMailCommon): def setUpClass(cls): super(TestMassMailCommon, cls).setUpClass() + cls.test_alias = cls.env['mail.alias'].create({ + 'alias_name': 'test.alias', + 'alias_user_id': False, + 'alias_model_id': cls.env['ir.model']._get('mailing.test.simple').id, + 'alias_contact': 'everyone' + }) + # enforce last update by user_marketing to match _process_mass_mailing_queue # taking last writer as user running a batch cls.mailing_bl = cls.env['mailing.mailing'].with_user(cls.user_marketing).create({ @@ -32,6 +39,7 @@ class TestMassMailCommon(MassMailCommon, TestMailCommon): """, 'mailing_type': 'mail', 'mailing_model_id': cls.env['ir.model']._get('mailing.test.blacklist').id, + 'reply_to_mode': 'update', }) @classmethod diff --git a/addons/test_mass_mailing/tests/test_mass_mailing.py b/addons/test_mass_mailing/tests/test_mailing.py similarity index 53% rename from addons/test_mass_mailing/tests/test_mass_mailing.py rename to addons/test_mass_mailing/tests/test_mailing.py index 0a2f45f0e17..7615d858a45 100644 --- a/addons/test_mass_mailing/tests/test_mass_mailing.py +++ b/addons/test_mass_mailing/tests/test_mailing.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon from odoo.tests import tagged from odoo.tests.common import users @@ -14,6 +15,62 @@ class TestMassMailing(TestMassMailCommon): def setUpClass(cls): super(TestMassMailing, cls).setUpClass() + @users('user_marketing') + @mute_logger('odoo.addons.mail.models.mail_thread') + def test_mailing_gateway_reply(self): + customers = self.env['res.partner'] + for x in range(0, 3): + customers |= self.env['res.partner'].create({ + 'name': 'Customer_%02d' % x, + 'email': '"Customer_%02d" Hello ${object.name}

', + 'reply_to_mode': 'new', + 'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain), + 'keep_archives': True, + 'mailing_model_id': self.env['ir.model']._get('mailing.list').id, + 'contact_list_ids': [(4, self.mailing_list_1.id)], + 'source_id': source.id, + 'medium_id': medium.id, + 'campaign_id': campaign.id + }) + + mailing.action_put_in_queue() + with self.mock_mail_gateway(mail_unlink_sent=False): + mailing._process_mass_mailing_queue() + + traces = self.env['mailing.trace'].search([('model', '=', self.mailing_list_1.contact_ids._name), ('res_id', 'in', self.mailing_list_1.contact_ids.ids)]) + self.assertEqual(len(traces), 3) + + # simulate response to mailing + self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[0], use_in_reply_to=True) + self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[1], use_in_reply_to=False) + + mailing_test_utms = self.env['mailing.test.utm'].search([('name', '=', 'Re: %s' % subject)]) + self.assertEqual(len(mailing_test_utms), 2) + for test_utm in mailing_test_utms: + self.assertEqual(test_utm.campaign_id, campaign) + self.assertEqual(test_utm.source_id, source) + self.assertEqual(test_utm.medium_id, medium) + @users('user_marketing') @mute_logger('odoo.addons.mail.models.mail_mail') def test_mailing_w_blacklist(self): diff --git a/addons/test_mass_mailing/tests/test_mailing_internals.py b/addons/test_mass_mailing/tests/test_mailing_internals.py deleted file mode 100644 index 555578e492d..00000000000 --- a/addons/test_mass_mailing/tests/test_mailing_internals.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon -from odoo.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE -from odoo.tests.common import users -from odoo.tools import mute_logger - - -class TestMailingInternals(TestMassMailCommon): - - def setUp(self): - super(TestMailingInternals, self).setUp() - - self.env['ir.config_parameter'].set_param('mail.bounce.alias', 'bounce.test') - self.env['ir.config_parameter'].set_param('mail.catchall.alias', 'catchall.test') - self.env['ir.config_parameter'].set_param('mail.catchall.domain', 'test.example.com') - - self.test_alias = self.env['mail.alias'].create({ - 'alias_name': 'test.alias', - 'alias_user_id': False, - 'alias_model_id': self.env['ir.model']._get('mailing.test.simple').id, - 'alias_contact': 'everyone' - }) - - @mute_logger('odoo.addons.mail.models.mail_render_mixin') - def test_mailing_test_button(self): - mailing = self.env['mailing.mailing'].create({ - 'name': 'TestButton', - 'subject': 'Subject ${object.name}', - 'preview': 'Preview ${object.name}', - 'state': 'draft', - 'mailing_type': 'mail', - 'body_html': '

Hello ${object.name}

', - 'mailing_model_id': self.env['ir.model']._get('res.partner').id, - }) - mailing_test = self.env['mailing.mailing.test'].create({ - 'email_to': 'test@test.com', - 'mass_mailing_id': mailing.id, - }) - - with self.mock_mail_gateway(): - mailing_test.send_mail_test() - - # Test if bad jinja in the subject raises an error - mailing.write({'subject': 'Subject ${object.name_id.id}'}) - with self.mock_mail_gateway(), self.assertRaises(Exception): - mailing_test.send_mail_test() - - # Test if bad jinja in the body raises an error - mailing.write({ - 'subject': 'Subject ${object.name}', - 'body_html': '

Hello ${object.name_id.id}

', - }) - with self.mock_mail_gateway(), self.assertRaises(Exception): - mailing_test.send_mail_test() - - # Test if bad jinja in the preview raises an error - mailing.write({ - 'body_html': '

Hello ${object.name}

', - 'preview': 'Preview ${object.name_id.id}', - }) - with self.mock_mail_gateway(), self.assertRaises(Exception): - mailing_test.send_mail_test() - - def test_mailing_trace_update(self): - customers = self.env['res.partner'] - for x in range(0, 3): - customers |= self.env['res.partner'].create({ - 'name': 'Customer_%02d' % x, - 'email': '"Customer_%02d" Hello ${object.name}

', - 'reply_to_mode': 'email', - 'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain), - 'keep_archives': True, - 'mailing_model_id': self.env['ir.model']._get('mailing.list').id, - 'contact_list_ids': [(4, self.mailing_list_1.id)], - 'source_id': source.id, - 'medium_id': medium.id, - 'campaign_id': campaign.id - }) - - mailing.action_put_in_queue() - with self.mock_mail_gateway(mail_unlink_sent=False): - mailing._process_mass_mailing_queue() - - traces = self.env['mailing.trace'].search([('model', '=', self.mailing_list_1.contact_ids._name), ('res_id', 'in', self.mailing_list_1.contact_ids.ids)]) - self.assertEqual(len(traces), 3) - - # simulate response to mailing - self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[0], use_in_reply_to=True) - self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[1], use_in_reply_to=False) - - mailing_test_utms = self.env['mailing.test.utm'].search([('name', '=', 'Re: %s' % subject)]) - self.assertEqual(len(mailing_test_utms), 2) - for test_utm in mailing_test_utms: - self.assertEqual(test_utm.campaign_id, campaign) - self.assertEqual(test_utm.source_id, source) - self.assertEqual(test_utm.medium_id, medium) diff --git a/addons/test_mass_mailing/tests/test_mailing_test.py b/addons/test_mass_mailing/tests/test_mailing_test.py new file mode 100644 index 00000000000..33c0419af07 --- /dev/null +++ b/addons/test_mass_mailing/tests/test_mailing_test.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon +from odoo.tests.common import users +from odoo.tools import mute_logger + + +class TestMailingTest(TestMassMailCommon): + + @users('user_marketing') + @mute_logger('odoo.addons.mail.models.mail_render_mixin') + def test_mailing_test_button(self): + mailing = self.env['mailing.mailing'].create({ + 'name': 'TestButton', + 'subject': 'Subject ${object.name}', + 'preview': 'Preview ${object.name}', + 'state': 'draft', + 'mailing_type': 'mail', + 'body_html': '

Hello ${object.name}

', + 'mailing_model_id': self.env['ir.model']._get('res.partner').id, + }) + mailing_test = self.env['mailing.mailing.test'].create({ + 'email_to': 'test@test.com', + 'mass_mailing_id': mailing.id, + }) + + with self.mock_mail_gateway(): + mailing_test.send_mail_test() + + # Test if bad jinja in the subject raises an error + mailing.write({'subject': 'Subject ${object.name_id.id}'}) + with self.mock_mail_gateway(), self.assertRaises(Exception): + mailing_test.send_mail_test() + + # Test if bad jinja in the body raises an error + mailing.write({ + 'subject': 'Subject ${object.name}', + 'body_html': '

Hello ${object.name_id.id}

', + }) + with self.mock_mail_gateway(), self.assertRaises(Exception): + mailing_test.send_mail_test() + + # Test if bad jinja in the preview raises an error + mailing.write({ + 'body_html': '

Hello ${object.name}

', + 'preview': 'Preview ${object.name_id.id}', + }) + with self.mock_mail_gateway(), self.assertRaises(Exception): + mailing_test.send_mail_test() diff --git a/addons/test_mass_mailing/tests/test_performance.py b/addons/test_mass_mailing/tests/test_performance.py index 127857c4d4f..33ea1885522 100644 --- a/addons/test_mass_mailing/tests/test_performance.py +++ b/addons/test_mass_mailing/tests/test_performance.py @@ -45,7 +45,7 @@ class TestMassMailPerformance(TestMassMailPerformanceBase): 'name': 'Test', 'subject': 'Test', 'body_html': '

Hello quuxUnsubscribe

', - 'reply_to_mode': 'email', + 'reply_to_mode': 'new', 'mailing_model_id': self.ref('test_mass_mailing.model_mailing_performance'), 'mailing_domain': [('id', 'in', self.mm_recs.ids)], }) @@ -85,7 +85,7 @@ class TestMassMailBlPerformance(TestMassMailPerformanceBase): 'name': 'Test', 'subject': 'Test', 'body_html': '

Hello quuxUnsubscribe

', - 'reply_to_mode': 'email', + 'reply_to_mode': 'new', 'mailing_model_id': self.ref('test_mass_mailing.model_mailing_performance_blacklist'), 'mailing_domain': [('id', 'in', self.mm_recs.ids)], }) diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py index 3d3bdb87812..5a60a7e60a3 100644 --- a/addons/website_form/controllers/main.py +++ b/addons/website_form/controllers/main.py @@ -223,7 +223,6 @@ class WebsiteForm(http.Controller): 'body': nl2br(custom_content), 'model': model_name, 'message_type': 'comment', - 'no_auto_thread': False, 'res_id': record.id, } mail_id = request.env['mail.message'].with_user(SUPERUSER_ID).create(values) @@ -258,7 +257,6 @@ class WebsiteForm(http.Controller): 'body': _('

Attached files :

'), 'model': model_name, 'message_type': 'comment', - 'no_auto_thread': False, 'res_id': id_record, 'attachment_ids': [(6, 0, orphan_attachment_ids)], 'subtype_id': request.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment'), diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 5c867047313..6e3433e9fa7 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -109,7 +109,6 @@ class WebsiteSaleForm(WebsiteForm): 'body': nl2br(data['custom']), 'model': 'sale.order', 'message_type': 'comment', - 'no_auto_thread': False, 'res_id': order.id, } request.env['mail.message'].with_user(SUPERUSER_ID).create(values)