From 9a9225fbb77e82a5a5369f318d46038963b824c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 14 May 2020 12:37:47 +0000 Subject: [PATCH 1/2] [FIX] mass_mailing: fix statistic replied state computation Trace status is a computed field based on other fields. Those store the datetime at which a specific action has been performed: opened, replied, bounced, ... depending on those fields status of trace is computed. Previous backport fixed mailing trace management to match heuristics used in 12.0 . However when replying, trace is considered as open instead of replied. It is now fixed as we consider replied being more important than opened. Task ID 2257717 PR #51445 Forward-port-of: #51319 Forward-port-of: #51247 X-original-commit: 8df2b0b8baabb35fdd44f5cbcc1cf72ebb03cc79 --- addons/mass_mailing/models/mailing_trace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/mass_mailing/models/mailing_trace.py b/addons/mass_mailing/models/mailing_trace.py index f79bda46ea6..b4a798e0dee 100644 --- a/addons/mass_mailing/models/mailing_trace.py +++ b/addons/mass_mailing/models/mailing_trace.py @@ -80,10 +80,10 @@ class MailingTrace(models.Model): stat.state = 'ignored' elif stat.exception: stat.state = 'exception' - elif stat.opened or stat.clicked: - stat.state = 'opened' elif stat.replied: stat.state = 'replied' + elif stat.opened or stat.clicked: + stat.state = 'opened' elif stat.bounced: stat.state = 'bounced' elif stat.sent: From 5a3fc4684741d0c537d32da1533ec7275915ab1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 14 May 2020 10:33:52 +0000 Subject: [PATCH 2/2] [FIX] mass_mailing: correctly consider replies for traces Consider an email going through the mail gateway. Its headers References and In-Reply-To are used in mail gateway to find if it is an answer to an existing discussion thread. Notably in mass mailing it is used to set mailing traces (called statistics before 13.0) as opened and replied. However currently only the References header is used. In this commit we now use References or In-Reply-To like what is done in mail gateway. == 13.2 FORWARD PORT SPECIFIC == Code in tests has been updated to use new mockups tools and variables available in test_mail LINKS Task ID 2257717 PR #51445 Forward-port-of: #51319 Forward-port-of: #51247 X-original-commit: 9921e38692e7dc4547afc34acc395da340eca410 --- addons/mail/tests/common.py | 32 ++++++++ addons/mass_mailing/models/mail_thread.py | 14 ++-- addons/test_mass_mailing/__init__.py | 1 + addons/test_mass_mailing/data/__init__.py | 4 + .../test_mass_mailing/data/mail_test_data.py | 42 ++++++++++ addons/test_mass_mailing/tests/__init__.py | 1 + .../tests/test_mailing_internals.py | 76 +++++++++++++++++++ 7 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 addons/test_mass_mailing/data/__init__.py create mode 100644 addons/test_mass_mailing/data/mail_test_data.py create mode 100644 addons/test_mass_mailing/tests/test_mailing_internals.py diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py index 366d1a84870..fa764015c7c 100644 --- a/addons/mail/tests/common.py +++ b/addons/mail/tests/common.py @@ -120,6 +120,30 @@ class MockEmail(common.BaseCase): self.env['mail.thread'].with_context(mail_channel_noautofollow=True).message_process(model, mail) return self.env[target_model].search([(target_field, '=', subject)]) + def gateway_reply_wrecord(self, template, record, use_in_reply_to=True): + """ 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. """ + mail_mail = self._find_mail_mail_wrecord(record) + + if use_in_reply_to: + extra = 'In-Reply-To:\r\n\t%s\n' % mail_mail.message_id + else: + disturbing_other_msg_id = '<123456.654321@another.host.com>' + extra = 'References:\r\n\t%s\n\r%s' % (mail_mail.message_id, disturbing_other_msg_id) + + return self.format_and_process( + template, + mail_mail.email_to, + mail_mail.reply_to, + 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, + ) + def from_string(self, text): return email.message_from_string(pycompat.to_text(text), policy=email.policy.SMTP) @@ -158,6 +182,14 @@ class MockEmail(common.BaseCase): raise AssertionError('mail.mail not found for message %s / recipients %s' % (mail_message, recipients.ids)) return mail + def _find_mail_mail_wrecord(self, record): + for mail in self._new_mails: + if mail.model == record._name and mail.res_id == record.id: + break + else: + raise AssertionError('mail.mail not found for record %s in %s' % (record, repr([m.email_to for m in self._new_mails]))) + return mail + def assertMailFailed(self, author, recipients, mail_message): mail = self._find_mail(author, recipients, mail_message) self.assertEqual(mail.state, 'exception') diff --git a/addons/mass_mailing/models/mail_thread.py b/addons/mass_mailing/models/mail_thread.py index 3850cf19200..e1370de2447 100644 --- a/addons/mass_mailing/models/mail_thread.py +++ b/addons/mass_mailing/models/mail_thread.py @@ -3,7 +3,7 @@ import datetime -from odoo import api, models, fields +from odoo import api, models, fields, tools BLACKLIST_MAX_BOUNCED_LIMIT = 5 @@ -17,10 +17,14 @@ class MailThread(models.AbstractModel): """ Override to update the parent mailing traces. The parent is found by using the References header of the incoming message and looking for matching message_id in mailing.trace. """ - if message.get('References') and routes: - message_ids = [x.strip() for x in message['References'].split()] - self.env['mailing.trace'].set_opened(mail_message_ids=message_ids) - self.env['mailing.trace'].set_replied(mail_message_ids=message_ids) + if routes: + # even if 'reply_to' in ref (cfr mail/mail_thread) that indicates a new thread redirection + # (aka bypass alias configuration in gateway) consider it as a reply for statistics purpose + thread_references = message_dict['references'] or message_dict['in_reply_to'] + msg_references = tools.mail_header_msgid_re.findall(thread_references) + if msg_references: + self.env['mailing.trace'].set_opened(mail_message_ids=msg_references) + self.env['mailing.trace'].set_replied(mail_message_ids=msg_references) return super(MailThread, self)._message_route_process(message, message_dict, routes) def message_post_with_template(self, template_id, **kwargs): diff --git a/addons/test_mass_mailing/__init__.py b/addons/test_mass_mailing/__init__.py index dc5e6b693d1..b1475da49a9 100644 --- a/addons/test_mass_mailing/__init__.py +++ b/addons/test_mass_mailing/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import data from . import models diff --git a/addons/test_mass_mailing/data/__init__.py b/addons/test_mass_mailing/data/__init__.py new file mode 100644 index 00000000000..5d1373b6266 --- /dev/null +++ b/addons/test_mass_mailing/data/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import mail_test_data diff --git a/addons/test_mass_mailing/data/mail_test_data.py b/addons/test_mass_mailing/data/mail_test_data.py new file mode 100644 index 00000000000..92183d04ffc --- /dev/null +++ b/addons/test_mass_mailing/data/mail_test_data.py @@ -0,0 +1,42 @@ + +MAIL_TEMPLATE = """Return-Path: +To: {to} +cc: {cc} +Received: by mail1.openerp.com (Postfix, from userid 10002) + id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST) +From: {email_from} +Subject: {subject} +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_Part_4200734_24778174.1344608186754" +Date: Fri, 10 Aug 2012 14:16:26 +0000 +Message-ID: {msg_id} +{extra} +------=_Part_4200734_24778174.1344608186754 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +I would gladly answer to your mass mailing ! + +-- +Your Dear Customer +------=_Part_4200734_24778174.1344608186754 +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + + + + =20 + + =20 + =20 + +

I would gladly answer to your mass mailing !

+ +

--
+ Your Dear Customer +

+ + +------=_Part_4200734_24778174.1344608186754-- +""" diff --git a/addons/test_mass_mailing/tests/__init__.py b/addons/test_mass_mailing/tests/__init__.py index eb8c757f2ba..2391896e3e4 100644 --- a/addons/test_mass_mailing/tests/__init__.py +++ b/addons/test_mass_mailing/tests/__init__.py @@ -5,5 +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_performance diff --git a/addons/test_mass_mailing/tests/test_mailing_internals.py b/addons/test_mass_mailing/tests/test_mailing_internals.py new file mode 100644 index 00000000000..4d91af5b5b6 --- /dev/null +++ b/addons/test_mass_mailing/tests/test_mailing_internals.py @@ -0,0 +1,76 @@ +# -*- 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 + + +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' + }) + + 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"