From e8949d9f79c58775db64c4e634a7cb59f2abef12 Mon Sep 17 00:00:00 2001 From: Umesh Gupta Date: Fri, 8 Apr 2022 06:20:27 +0000 Subject: [PATCH] [IMP] event: prevent communication line duplication on an event Currently, when we change the event template, all the communication lines linked with are added to the event. That means, even if some of the lines are sent, and if the new templates lines that has exact same configuration than the sent ones, they are still added to the event, which means the same mails will be sent again to the attendees. With this commit, we check that the communication line being added (as a result of template change) has the same configuration against any of the already sent one, it is skipped. As a result, attendees will not be spammed with the same emails. taskID-2811310 closes odoo/odoo#89961 Signed-off-by: Thibault Delavallee (tde) --- addons/event/models/event_event.py | 11 ++-- addons/event/models/event_mail.py | 26 ++++++--- .../event/tests/test_event_mail_schedule.py | 57 +++++++++++++++++++ 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/addons/event/models/event_event.py b/addons/event/models/event_event.py index b2d21a57d8f..b272dd6e1b5 100644 --- a/addons/event/models/event_event.py +++ b/addons/event/models/event_event.py @@ -460,11 +460,14 @@ class EventEvent(models.Model): lambda mail: not(mail._origin.mail_done) and not(mail._origin.mail_registration_ids) ) command = [Command.unlink(mail.id) for mail in mails_to_remove] + + # lines to add: those which do not have the exact copy available in lines to keep if event.event_type_id.event_type_mail_ids: - command += [ - Command.create(line._prepare_event_mail_values()) - for line in event.event_type_id.event_type_mail_ids - ] + mails_to_keep_vals = {mail._prepare_event_mail_values() for mail in event.event_mail_ids - mails_to_remove} + for mail in event.event_type_id.event_type_mail_ids: + mail_values = mail._prepare_event_mail_values() + if mail_values not in mails_to_keep_vals: + command.append(Command.create(mail_values._asdict())) if command: event.event_mail_ids = command diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py index 9f404b970e0..5f4af1c38e7 100644 --- a/addons/event/models/event_mail.py +++ b/addons/event/models/event_mail.py @@ -5,6 +5,7 @@ import logging import random import threading +from collections import namedtuple from datetime import datetime from dateutil.relativedelta import relativedelta @@ -59,14 +60,13 @@ class EventTypeMail(models.Model): def _prepare_event_mail_values(self): self.ensure_one() - return { - 'notification_type': self.notification_type, - 'interval_nbr': self.interval_nbr, - 'interval_unit': self.interval_unit, - 'interval_type': self.interval_type, - 'template_ref': '%s,%i' % (self.template_ref._name, self.template_ref.id) - } - + return namedtuple("MailValues", ['notification_type', 'interval_nbr', 'interval_unit', 'interval_type', 'template_ref'])( + self.notification_type, + self.interval_nbr, + self.interval_unit, + self.interval_type, + '%s,%i' % (self.template_ref._name, self.template_ref.id) + ) class EventMailScheduler(models.Model): """ Event automated mailing. This model replaces all existing fields and @@ -182,6 +182,16 @@ class EventMailScheduler(models.Model): return self.env['event.mail.registration'].create(new) return self.env['event.mail.registration'] + def _prepare_event_mail_values(self): + self.ensure_one() + return namedtuple("MailValues", ['notification_type', 'interval_nbr', 'interval_unit', 'interval_type', 'template_ref'])( + self.notification_type, + self.interval_nbr, + self.interval_unit, + self.interval_type, + '%s,%i' % (self.template_ref._name, self.template_ref.id) + ) + @api.model def _warn_template_error(self, scheduler, exception): # We warn ~ once by hour ~ instead of every 10 min if the interval unit is more than 'hours'. diff --git a/addons/event/tests/test_event_mail_schedule.py b/addons/event/tests/test_event_mail_schedule.py index 1089c8358a5..a18610f8837 100644 --- a/addons/event/tests/test_event_mail_schedule.py +++ b/addons/event/tests/test_event_mail_schedule.py @@ -5,6 +5,7 @@ from datetime import datetime from dateutil.relativedelta import relativedelta from freezegun import freeze_time +from odoo import Command from odoo.addons.event.tests.common import EventCase from odoo.addons.mail.tests.common import MockEmail from odoo.tools import formataddr, mute_logger @@ -276,3 +277,59 @@ class TestMailSchedule(EventCase, MockEmail): fields_values={'subject': '%s: today' % test_event.name, 'email_from': self.user_eventmanager.company_id.email_formatted, }) + + @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') + def test_unique_event_mail_ids(self): + # create event with default event_mail_ids lines + test_event = self.env['event.event'].with_user(self.user_eventmanager).create({ + 'name': "TestEvent", + 'auto_confirm': True, + 'date_begin': datetime.now(), + 'date_end': datetime.now() + relativedelta(days=1), + 'seats_max': 2, + 'seats_limited': True, + }) + + event_mail_ids_initial = test_event.event_mail_ids + self._create_registrations(test_event, 1) + + mail_done = test_event.event_mail_ids.filtered(lambda mail: mail.mail_done and mail.mail_registration_ids) + + self.assertEqual(len(test_event.event_mail_ids), 3, "Should have 3 communication lines") + self.assertEqual(len(mail_done), 1, "Should have sent first mail immediately") + + # change the event type that has event_type_mail_ids having one identical and one non-identical configuration + event_type = self.env['event.type'].create({ + 'name': "Go Sports", + 'event_type_mail_ids': [ + Command.create({ + 'notification_type': 'mail', + 'interval_nbr': 0, + 'interval_unit': 'now', + 'interval_type': 'after_sub', + 'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_subscription')}), + Command.create({ + 'notification_type': 'mail', + 'interval_nbr': 5, + 'interval_unit': 'hours', + 'interval_type': 'before_event', + 'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_reminder')}), + ] + }) + test_event.event_type_id = event_type + + self.assertTrue(mail_done in test_event.event_mail_ids, "Sent communication should not have been removed") + mail_not_done = event_mail_ids_initial - mail_done + self.assertFalse(test_event.event_mail_ids & mail_not_done, "Other default communication lines should have been removed") + + self.assertEqual(len(test_event.event_mail_ids), 2, "Should now have only two communication lines") + mails_to_send = test_event.event_mail_ids - mail_done + duplicate_mails = mails_to_send.filtered(lambda mail: + mail.notification_type == 'mail' and\ + mail.interval_nbr == 0 and\ + mail.interval_unit == 'now' and\ + mail.interval_type == 'after_sub' and\ + mail.template_ref.id == self.env['ir.model.data']._xmlid_to_res_id('event.event_subscription')) + + self.assertEqual(len(duplicate_mails), 0, + "The duplicate configuration (first one from event_type.event_type_mail_ids which has same configuration as the sent one) should not have been added")