[IMP] event: allow to send registrations emails asynchronously
This commit introduces a new configuration parameter 'event.event_mail_async' forcing registrations-based communication to be asynchronous. Instead of directly sending communication it triggers the cron to be run as soon as possible. When having large volume of registrations, and especially concurrent registrations it saves a DB to avoid generating tickets and preparing emails synchronously to the registration creation. Task-3764894: Event: Allow using cron triggers for communication Part of Task-3084943: Event: Improve communication scheduler scalability Part-of: odoo/odoo#155777
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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": """
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="registration">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<p>This is a sample of an external report.</p>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>""",
|
||||
"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": """<div>Hello your registration to <t t-out="object.event_id.name"/> is confirmed.</div>""",
|
||||
"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": """<div>Hello this is a reminder for your registration to <t t-out="object.event_id.name"/>.</div>""",
|
||||
"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)],
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user