feat: add ability to override middlewares for recurring nudges

* feat: add ability to override middlewares for recurring nudges

* feat: add ability to run command for all sites
This commit is contained in:
Kaustav Banerjee
2025-08-18 18:14:00 +05:30
committed by Farhaan Bukhsh
parent 1eb387b11b
commit 6b48ff9470
4 changed files with 59 additions and 13 deletions

View File

@@ -29,12 +29,28 @@ class SendEmailBaseCommand(PrefixedDebugLoggerMixin, BaseCommand): # lint-amnes
'--override-recipient-email',
help='Send all emails to this address instead of the actual recipient'
)
parser.add_argument('site_domain_name')
parser.add_argument(
'site_domain_name',
nargs='?',
default=None,
help=(
'Domain name for the site to use. '
'Do not provide a domain if you wish to run this for all sites'
)
)
parser.add_argument(
'--weeks',
type=int,
help='Number of weekly emails to be sent',
)
parser.add_argument(
'--override-middlewares',
action='append',
help=(
'Use this middleware when emulating http requests. '
'To use multiple middlewares, provide this argument multiple times'
)
)
def handle(self, *args, **options):
self.log_debug('Args = %r', options)
@@ -49,19 +65,26 @@ class SendEmailBaseCommand(PrefixedDebugLoggerMixin, BaseCommand): # lint-amnes
tzinfo=pytz.UTC
)
self.log_debug('Current date = %s', current_date.isoformat())
site = Site.objects.get(domain__iexact=options['site_domain_name'])
self.log_debug('Running for site %s', site.domain)
override_recipient_email = options.get('override_recipient_email')
self.send_emails(site, current_date, override_recipient_email)
override_middlewares = options.get('override_middlewares')
def enqueue(self, day_offset, site, current_date, override_recipient_email=None):
site_domain_name = options['site_domain_name']
sites = Site.objects.filter(domain__iexact=site_domain_name) if site_domain_name else Site.objects.all()
if sites:
for site in sites:
self.log_debug('Running for site %s', site.domain)
self.send_emails(site, current_date, override_recipient_email, override_middlewares)
else:
self.log_info("No matching site found")
def enqueue(self, day_offset, site, current_date, override_recipient_email=None, override_middlewares=None):
self.async_send_task.enqueue(
site,
current_date,
day_offset,
override_recipient_email,
override_middlewares,
)
def send_emails(self, *args, **kwargs):

View File

@@ -10,6 +10,7 @@ from unittest.mock import DEFAULT, Mock, patch
import ddt
import pytz
from django.conf import settings
from django.contrib.sites.models import Site
from openedx.core.djangoapps.schedules.management.commands import SendEmailBaseCommand
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory
@@ -33,9 +34,23 @@ class TestSendEmailBaseCommand(CacheIsolationTestCase): # lint-amnesty, pylint:
send_emails.assert_called_once_with(
self.site,
datetime.datetime(2017, 9, 29, tzinfo=pytz.UTC),
None,
None
)
def test_handle_all_sites(self):
with patch.object(self.command, 'send_emails') as send_emails:
self.command.handle(site_domain_name=None, date='2017-09-29')
expected_sites = Site.objects.all()
for expected_site in expected_sites:
send_emails.assert_any_call(
expected_site,
datetime.datetime(2017, 9, 29, tzinfo=pytz.UTC),
None,
None
)
assert send_emails.call_count == len(expected_sites)
def test_weeks_option(self):
with patch.object(self.command, 'enqueue') as enqueue:
self.command.handle(site_domain_name=self.site.domain, date='2017-09-29', weeks=12)

View File

@@ -20,6 +20,7 @@ from edx_django_utils.monitoring import (
set_custom_attribute
)
from eventtracking import tracker
from importlib import import_module
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
@@ -103,7 +104,7 @@ class BinnedScheduleMessageBaseTask(ScheduleMessageBaseTask):
task_instance = None
@classmethod
def enqueue(cls, site, current_date, day_offset, override_recipient_email=None): # lint-amnesty, pylint: disable=missing-function-docstring
def enqueue(cls, site, current_date, day_offset, override_recipient_email=None, override_middlewares=None): # lint-amnesty, pylint: disable=missing-function-docstring
set_code_owner_attribute_from_module(__name__)
current_date = resolvers._get_datetime_beginning_of_day(current_date) # lint-amnesty, pylint: disable=protected-access
@@ -120,6 +121,7 @@ class BinnedScheduleMessageBaseTask(ScheduleMessageBaseTask):
day_offset,
bin,
override_recipient_email,
override_middlewares,
)
cls.log_info('Launching task with args = %r', task_args)
cls.task_instance.apply_async(
@@ -128,16 +130,17 @@ class BinnedScheduleMessageBaseTask(ScheduleMessageBaseTask):
)
def run( # lint-amnesty, pylint: disable=arguments-differ
self, site_id, target_day_str, day_offset, bin_num, override_recipient_email=None,
self, site_id, target_day_str, day_offset, bin_num, override_recipient_email=None, override_middlewares=None,
):
set_code_owner_attribute_from_module(__name__)
site = Site.objects.select_related('configuration').get(id=site_id)
with emulate_http_request(site=site):
middlewares = [self.class_from_classpath(cls) for cls in override_middlewares] if override_middlewares else None
with emulate_http_request(site=site, middleware_classes=middlewares) as request:
msg_type = self.make_message_type(day_offset)
_annotate_for_monitoring(msg_type, site, bin_num, target_day_str, day_offset)
_annotate_for_monitoring(msg_type, request.site, bin_num, target_day_str, day_offset)
return self.resolver( # lint-amnesty, pylint: disable=not-callable
self.async_send_task,
site,
request.site,
deserialize(target_day_str),
day_offset,
bin_num,
@@ -147,6 +150,11 @@ class BinnedScheduleMessageBaseTask(ScheduleMessageBaseTask):
def make_message_type(self, day_offset):
raise NotImplementedError
def class_from_classpath(self, class_path):
module_name, klass = class_path.rsplit('.', 1)
module = import_module(module_name)
return getattr(module, klass)
@shared_task(base=LoggedTask, ignore_result=True)
@set_code_owner_attribute

View File

@@ -45,7 +45,7 @@ def emulate_http_request(site=None, user=None, middleware_classes=None):
_run_method_if_implemented(middleware, 'process_request', request)
try:
yield
yield request
except Exception as exc:
for middleware in reversed(middleware_instances):
_run_method_if_implemented(middleware, 'process_exception', request, exc)