diff --git a/addons/event/models/event_registration.py b/addons/event/models/event_registration.py index c7537e476da..df073e59c02 100644 --- a/addons/event/models/event_registration.py +++ b/addons/event/models/event_registration.py @@ -308,10 +308,15 @@ class EventRegistration(models.Model): return onsubscribe_schedulers.update({'mail_done': False}) - # we could simply call _create_missing_mail_registrations and let cron do their job - # but it currently leads to several delays. We therefore call execute until - # cron triggers are correctly used - onsubscribe_schedulers.with_user(SUPERUSER_ID).execute() + # either trigger the cron, either run schedulers immediately (scaling choice) + async_scheduler = self.env['ir.config_parameter'].sudo().get_param('event.event_mail_async') + if async_scheduler: + self.env.ref('event.event_mail_scheduler')._trigger() + else: + # we could simply call _create_missing_mail_registrations and let cron do their job + # but it currently leads to several delays. We therefore call execute until + # cron triggers are correctly used + onsubscribe_schedulers.with_user(SUPERUSER_ID).execute() # ------------------------------------------------------------ # MAILING / GATEWAY diff --git a/addons/event/tests/common.py b/addons/event/tests/common.py index 1122af2381f..a7fbac4fef9 100644 --- a/addons/event/tests/common.py +++ b/addons/event/tests/common.py @@ -104,3 +104,56 @@ class EventCase(common.TransactionCase): 'phone': f'04560000{idx}{idx}', } for idx in range(0, reg_count)]) return registrations + + @classmethod + def _setup_test_reports(cls): + cls.test_report_view = cls.env["ir.ui.view"].create({ + "arch_db": """ + + + +
+

This is a sample of an external report.

+
+
+
+
""", + "key": "event_registration_test_report", + "name": "event_registration_test_report", + "type": "qweb", + }) + cls.env["ir.model.data"].create({ + "model": "ir.ui.view", + "module": "event", + "name": "event_registration_test_report", + "res_id": cls.test_report_view.id, + }) + + cls.test_report_action = cls.env['ir.actions.report'].create({ + 'name': 'Test Report on event.registration', + 'model': 'event.registration', + 'print_report_name': "f'TestReport for {object.name}'", + 'report_type': 'qweb-pdf', + 'report_name': 'event.event_registration_test_report', + }) + + cls.template_subscription = cls.env['mail.template'].create({ + "body_html": """
Hello your registration to is confirmed.
""", + "email_from": "{{ (object.event_id.organizer_id.email_formatted or object.event_id.user_id.email_formatted or '') }}", + "email_to": """{{ (object.email and '"%s" <%s>' % (object.name, object.email)) or object.partner_id.email_formatted or '' }}""", + "lang": "{{ object.event_id.lang or object.partner_id.lang }}", + "model_id": cls.env['ir.model']._get_id("event.registration"), + "name": "Event: Registration Confirmation TEST", + "subject": "Confirmation for {{ object.event_id.name }}", + "report_template_ids": [(4, cls.test_report_action.id)], + }) + cls.template_reminder = cls.env['mail.template'].create({ + "body_html": """
Hello this is a reminder for your registration to .
""", + "email_from": "{{ (object.event_id.organizer_id.email_formatted or object.event_id.user_id.email_formatted or '') }}", + "email_to": """{{ (object.email and '"%s" <%s>' % (object.name, object.email)) or object.partner_id.email_formatted or '' }}""", + "lang": "{{ object.event_id.lang or object.partner_id.lang }}", + "model_id": cls.env['ir.model']._get_id("event.registration"), + "name": "Event: Registration Reminder TEST", + "subject": "Reminder for {{ object.event_id.name }}: {{ object.get_date_range_str() }}", + "report_template_ids": [(4, cls.test_report_action.id)], + }) diff --git a/addons/event/tests/test_event_mail_schedule.py b/addons/event/tests/test_event_mail_schedule.py index 511bfcc477b..f448e9b6003 100644 --- a/addons/event/tests/test_event_mail_schedule.py +++ b/addons/event/tests/test_event_mail_schedule.py @@ -6,6 +6,7 @@ from dateutil.relativedelta import relativedelta from freezegun import freeze_time from odoo import Command +from odoo.addons.base.tests.test_ir_cron import CronMixinCase from odoo.addons.event.tests.common import EventCase from odoo.addons.mail.tests.common import MockEmail from odoo.tests import tagged, users @@ -13,7 +14,7 @@ from odoo.tools import formataddr, mute_logger @tagged('event_mail', 'post_install', '-at_install') -class TestMailSchedule(EventCase, MockEmail): +class TestMailSchedule(EventCase, MockEmail, CronMixinCase): @classmethod def setUpClass(cls): @@ -27,6 +28,8 @@ class TestMailSchedule(EventCase, MockEmail): # deactivate other schedulers to avoid messing with crons cls.env['event.mail'].search([]).unlink() + # consider asynchronous sending as default sending + cls.env["ir.config_parameter"].set_param("event.event_mail_async", False) # freeze some datetimes, and ensure more than 1D+1H before event starts # to ease time-based scheduler check @@ -37,9 +40,7 @@ class TestMailSchedule(EventCase, MockEmail): cls.event_date_begin = datetime(2021, 3, 22, 8, 0, 0) cls.event_date_end = datetime(2021, 3, 24, 18, 0, 0) - cls.template_subscription_id = cls.env['ir.model.data']._xmlid_to_res_id('event.event_subscription') - cls.template_reminder_id = cls.env['ir.model.data']._xmlid_to_res_id('event.event_reminder') - + cls._setup_test_reports() with cls.mock_datetime_and_now(cls, cls.reference_now): # create with admin to force create_date cls.test_event = cls.env['event.event'].create({ @@ -52,28 +53,28 @@ class TestMailSchedule(EventCase, MockEmail): 'interval_unit': 'now', 'interval_type': 'after_sub', 'notification_type': 'mail', - 'template_ref': f'mail.template,{cls.template_subscription_id}', + 'template_ref': f'mail.template,{cls.template_subscription.id}', }), (0, 0, { # one hour after subscription 'interval_nbr': 1, 'interval_unit': 'hours', 'interval_type': 'after_sub', 'notification_type': 'mail', - 'template_ref': f'mail.template,{cls.template_subscription_id}', + 'template_ref': f'mail.template,{cls.template_subscription.id}', }), (0, 0, { # 1 days before event 'interval_nbr': 1, 'interval_unit': 'days', 'interval_type': 'before_event', 'notification_type': 'mail', - 'template_ref': f'mail.template,{cls.template_reminder_id}', + 'template_ref': f'mail.template,{cls.template_reminder.id}', }), (0, 0, { # immediately after event 'interval_nbr': 1, 'interval_unit': 'hours', 'interval_type': 'after_event', 'notification_type': 'mail', - 'template_ref': f'mail.template,{cls.template_reminder_id}', + 'template_ref': f'mail.template,{cls.template_reminder.id}', }), ] }) @@ -160,9 +161,10 @@ class TestMailSchedule(EventCase, MockEmail): [formataddr((reg1.name, reg1.email)), formataddr((reg2.name, reg2.email))], 'outgoing', content=None, - fields_values={'subject': 'Your registration at %s' % test_event.name, - 'email_from': self.user_eventmanager.company_id.email_formatted, - }) + fields_values={ + 'email_from': self.user_eventmanager.company_id.email_formatted, + 'subject': f'Confirmation for {test_event.name}', + }) # same for second scheduler: scheduled but not sent self.assertEqual(len(after_sub_scheduler_2.mail_registration_ids), 2, 'event: should have 2 scheduled communication (1 / registration)') @@ -198,9 +200,10 @@ class TestMailSchedule(EventCase, MockEmail): [formataddr((reg1.name, reg1.email)), formataddr((reg2.name, reg2.email))], 'outgoing', content=None, - fields_values={'subject': 'Your registration at %s' % test_event.name, - 'email_from': self.user_eventmanager.company_id.email_formatted, - }) + fields_values={ + 'email_from': self.user_eventmanager.company_id.email_formatted, + 'subject': f'Confirmation for {test_event.name}', + }) # PRE SCHEDULERS (MOVE FORWARD IN TIME) # -------------------------------------------------- @@ -233,9 +236,10 @@ class TestMailSchedule(EventCase, MockEmail): [formataddr((reg1.name, reg1.email)), formataddr((reg2.name, reg2.email))], 'outgoing', content=None, - fields_values={'subject': '%s: tomorrow' % test_event.name, - 'email_from': self.user_eventmanager.company_id.email_formatted, - }) + fields_values={ + 'email_from': self.user_eventmanager.company_id.email_formatted, + 'subject': f'Reminder for {test_event.name}: tomorrow', + }) # NEW REGISTRATION EFFECT ON SCHEDULERS # -------------------------------------------------- @@ -283,7 +287,7 @@ class TestMailSchedule(EventCase, MockEmail): # manual check because 2 identical mails are sent and mail tools do not support it easily for mail in self._new_mails: self.assertEqual(mail.email_from, self.user_eventmanager.company_id.email_formatted) - self.assertEqual(mail.subject, 'Your registration at %s' % test_event.name) + self.assertEqual(mail.subject, f'Confirmation for {test_event.name}') self.assertEqual(mail.state, 'outgoing') self.assertEqual(mail.email_to, formataddr((reg3.name, reg3.email))) @@ -308,9 +312,10 @@ class TestMailSchedule(EventCase, MockEmail): [formataddr((reg1.name, reg1.email)), formataddr((reg2.name, reg2.email)), formataddr((reg3.name, reg3.email))], 'outgoing', content=None, - fields_values={'subject': '%s: today' % test_event.name, - 'email_from': self.user_eventmanager.company_id.email_formatted, - }) + fields_values={ + 'email_from': self.user_eventmanager.company_id.email_formatted, + 'subject': f"Reminder for {test_event.name}: today", + }) @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') @users('user_eventmanager') @@ -327,8 +332,8 @@ class TestMailSchedule(EventCase, MockEmail): # consider having hanging registrations, still not processed (e.g. adding # a new scheduler after) self.env.invalidate_all() - # com 58, event 36 - with self.assertQueryCount(61), self.mock_datetime_and_now(reference_now), \ + # com 59, event 37 + with self.assertQueryCount(62), self.mock_datetime_and_now(reference_now), \ self.mock_mail_gateway(): _existing = self.env['event.registration'].create([ { @@ -347,7 +352,7 @@ class TestMailSchedule(EventCase, MockEmail): 'interval_unit': 'now', 'interval_type': 'after_sub', 'notification_type': 'mail', - 'template_ref': f'mail.template,{self.template_subscription_id}', + 'template_ref': f'mail.template,{self.template_subscription.id}', }), ]}) self.env.invalidate_all() @@ -369,6 +374,44 @@ class TestMailSchedule(EventCase, MockEmail): self.assertEqual(self.mail_mail_create_mocked.call_count, 7, 'EventMail: TODO: should create one mail / new registration') + @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') + @users('user_eventmanager') + def test_event_mail_schedule_on_subscription_async(self): + """ Async mode for schedulers activated, should not send communication + in the same transaction. """ + test_event = self.test_event.with_env(self.env) + cron = self.env.ref('event.event_mail_scheduler') + reference_now = self.reference_now + + self.env['ir.config_parameter'].sudo().set_param('event.event_mail_async', True) + with self.capture_triggers(cron.id) as capt, \ + self.mock_datetime_and_now(reference_now + relativedelta(minutes=10)), \ + self.mock_mail_gateway(): + existing = self.env['event.registration'].create([ + { + 'email': f'new.async.attendee.{idx}@test.example.com', + 'event_id': test_event.id, + 'name': f'New Async Attendee {idx}', + } for idx in range(5) + ]) + self.assertEqual(len(self._new_mails), 0) + self.assertEqual(self.mail_mail_create_mocked.call_count, 0) + capt.records.ensure_one() + self.assertEqual(capt.records.call_at, reference_now.replace(microsecond=0) + relativedelta(minutes=10)) + + # run cron: emails should be send for registrations + with self.mock_datetime_and_now(reference_now + relativedelta(minutes=10)), \ + self.mock_mail_gateway(): + cron.sudo().method_direct_trigger() + self.assertMailMailWEmails( + [formataddr((reg.name, reg.email)) for reg in existing], + "outgoing", + content=f"Hello your registration to {test_event.name} is confirmed", + fields_values={ + 'email_from': self.user_eventmanager.company_id.email_formatted, + 'subject': f'Confirmation for {test_event.name}', + }) + @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 diff --git a/addons/event_sms/tests/test_sms_schedule.py b/addons/event_sms/tests/test_sms_schedule.py index 4b68c01e118..f27a880a63c 100644 --- a/addons/event_sms/tests/test_sms_schedule.py +++ b/addons/event_sms/tests/test_sms_schedule.py @@ -17,6 +17,9 @@ class TestSMSSchedule(EventCase, SMSCase): def setUpClass(cls): super(TestSMSSchedule, cls).setUpClass() + # consider asynchronous sending as default sending + cls.env["ir.config_parameter"].set_param("event.event_mail_async", False) + cls.sms_template_sub = cls.env['sms.template'].create({ 'name': 'Test subscription', 'model_id': cls.env.ref('event.model_event_registration').id,