diff --git a/openedx/core/djangoapps/schedules/management/commands/__init__.py b/openedx/core/djangoapps/schedules/management/commands/__init__.py index 1aaf31701f..d39d4f01eb 100644 --- a/openedx/core/djangoapps/schedules/management/commands/__init__.py +++ b/openedx/core/djangoapps/schedules/management/commands/__init__.py @@ -15,6 +15,8 @@ from openedx.core.djangoapps.site_configuration.models import SiteConfiguration LOG = logging.getLogger(__name__) +# TODO: consider using a LoggerAdapter instead of this mixin: +# https://docs.python.org/2/library/logging.html#logging.LoggerAdapter class PrefixedDebugLoggerMixin(object): def __init__(self, *args, **kwargs): self.log_prefix = self.__class__.__name__ diff --git a/openedx/core/djangoapps/schedules/management/commands/tests/test_base.py b/openedx/core/djangoapps/schedules/management/commands/tests/test_base.py index 6147204d5e..e2cb00a870 100644 --- a/openedx/core/djangoapps/schedules/management/commands/tests/test_base.py +++ b/openedx/core/djangoapps/schedules/management/commands/tests/test_base.py @@ -11,7 +11,7 @@ from openedx.core.djangoapps.schedules.management.commands import ( SendEmailBaseCommand, BinnedSchedulesBaseResolver ) -from openedx.core.djangoapps.schedules.tests.factories import ScheduleConfigFactory, ScheduleFactory +from openedx.core.djangoapps.schedules.tests.factories import ScheduleConfigFactory from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms @@ -96,7 +96,6 @@ class TestBinnedSchedulesBaseResolver(CacheIsolationTestCase): assert not exclude_orgs assert org_list == expected_org_list - # factory_boy doesn't make sense at all @ddt.unpack @ddt.data( (None, []), diff --git a/openedx/core/djangoapps/schedules/management/commands/tests/test_send_recurring_nudge.py b/openedx/core/djangoapps/schedules/management/commands/tests/test_send_recurring_nudge.py index 14ff129f45..3b83a1d952 100644 --- a/openedx/core/djangoapps/schedules/management/commands/tests/test_send_recurring_nudge.py +++ b/openedx/core/djangoapps/schedules/management/commands/tests/test_send_recurring_nudge.py @@ -61,7 +61,7 @@ class TestSendRecurringNudge(CacheIsolationTestCase): retry=False, ) mock_schedule_bin.apply_async.assert_any_call( - (self.site_config.site.id, serialize(test_time), -3, 23, [], True, None), + (self.site_config.site.id, serialize(test_time), -3, tasks.RECURRING_NUDGE_NUM_BINS - 1, [], True, None), retry=False, ) self.assertFalse(mock_ace.send.called) diff --git a/openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py b/openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py new file mode 100644 index 0000000000..329bcdd065 --- /dev/null +++ b/openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py @@ -0,0 +1,257 @@ +import datetime +import itertools +from copy import deepcopy +from unittest import skipUnless + +import attr +import ddt +import pytz +from django.conf import settings +from edx_ace.channel import ChannelType +from edx_ace.test_utils import StubPolicy, patch_channels, patch_policies +from edx_ace.utils.date import serialize +from mock import Mock, patch +from opaque_keys.edx.keys import CourseKey +from opaque_keys.edx.locator import CourseLocator + +from openedx.core.djangoapps.schedules import tasks +from openedx.core.djangoapps.schedules.management.commands import send_upgrade_reminder as reminder +from openedx.core.djangoapps.schedules.tests.factories import ScheduleConfigFactory, ScheduleFactory +from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory +from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms +from student.tests.factories import UserFactory + + +@ddt.ddt +@skip_unless_lms +@skipUnless('openedx.core.djangoapps.schedules.apps.SchedulesConfig' in settings.INSTALLED_APPS, + "Can't test schedules if the app isn't installed") +class TestUpgradeReminder(CacheIsolationTestCase): + # pylint: disable=protected-access + + def setUp(self): + super(TestUpgradeReminder, self).setUp() + + ScheduleFactory.create(upgrade_deadline=datetime.datetime(2017, 8, 1, 15, 44, 30, tzinfo=pytz.UTC)) + ScheduleFactory.create(upgrade_deadline=datetime.datetime(2017, 8, 1, 17, 34, 30, tzinfo=pytz.UTC)) + ScheduleFactory.create(upgrade_deadline=datetime.datetime(2017, 8, 2, 15, 34, 30, tzinfo=pytz.UTC)) + + site = SiteFactory.create() + self.site_config = SiteConfigurationFactory.create(site=site) + ScheduleConfigFactory.create(site=self.site_config.site) + + @patch.object(reminder, 'UpgradeReminderResolver') + def test_handle(self, mock_resolver): + test_time = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC) + reminder.Command().handle(date='2017-08-01', site_domain_name=self.site_config.site.domain) + mock_resolver.assert_called_with(self.site_config.site, test_time) + + mock_resolver().send.assert_any_call(2, None) + + @patch.object(tasks, 'ace') + @patch.object(reminder, 'upgrade_reminder_schedule_bin') + def test_resolver_send(self, mock_schedule_bin, mock_ace): + current_time = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC) + test_time = current_time + datetime.timedelta(days=2) + ScheduleFactory.create(upgrade_deadline=datetime.datetime(2017, 8, 3, 15, 34, 30, tzinfo=pytz.UTC)) + + reminder.UpgradeReminderResolver(self.site_config.site, current_time).send(2) + self.assertFalse(mock_schedule_bin.called) + mock_schedule_bin.apply_async.assert_any_call( + (self.site_config.site.id, serialize(test_time), 2, 0, [], True, None), + retry=False, + ) + mock_schedule_bin.apply_async.assert_any_call( + (self.site_config.site.id, serialize(test_time), 2, tasks.UPGRADE_REMINDER_NUM_BINS - 1, [], True, None), + retry=False, + ) + self.assertFalse(mock_ace.send.called) + + @ddt.data(1, 10, 100) + @patch.object(tasks, 'ace') + @patch.object(tasks, '_upgrade_reminder_schedule_send') + def test_schedule_bin(self, schedule_count, mock_schedule_send, mock_ace): + schedules = [ + ScheduleFactory.create( + upgrade_deadline=datetime.datetime(2017, 8, 3, 18, 44, 30, tzinfo=pytz.UTC), + enrollment__user=UserFactory.create(), + enrollment__course__id=CourseLocator('edX', 'toy', 'Bin') + ) for _ in range(schedule_count) + ] + + test_time = datetime.datetime(2017, 8, 3, 18, tzinfo=pytz.UTC) + test_time_str = serialize(test_time) + with self.assertNumQueries(25): + for b in range(tasks.UPGRADE_REMINDER_NUM_BINS): + tasks.upgrade_reminder_schedule_bin( + self.site_config.site.id, target_day_str=test_time_str, day_offset=2, bin_num=b, + org_list=[schedules[0].enrollment.course.org], + ) + self.assertEqual(mock_schedule_send.apply_async.call_count, schedule_count) + self.assertFalse(mock_ace.send.called) + + @patch.object(tasks, '_upgrade_reminder_schedule_send') + def test_no_course_overview(self, mock_schedule_send): + + schedule = ScheduleFactory.create( + upgrade_deadline=datetime.datetime(2017, 8, 3, 20, 34, 30, tzinfo=pytz.UTC), + ) + schedule.enrollment.course_id = CourseKey.from_string('edX/toy/Not_2012_Fall') + schedule.enrollment.save() + + test_time = datetime.datetime(2017, 8, 3, 20, tzinfo=pytz.UTC) + test_time_str = serialize(test_time) + with self.assertNumQueries(25): + for b in range(tasks.UPGRADE_REMINDER_NUM_BINS): + tasks.upgrade_reminder_schedule_bin( + self.site_config.site.id, target_day_str=test_time_str, day_offset=2, bin_num=b, + org_list=[schedule.enrollment.course.org], + ) + + # There is no database constraint that enforces that enrollment.course_id points + # to a valid CourseOverview object. However, in that case, schedules isn't going + # to attempt to address it, and will instead simply skip those users. + # This happens 'transparently' because django generates an inner-join between + # enrollment and course_overview, and thus will skip any rows where course_overview + # is null. + self.assertEqual(mock_schedule_send.apply_async.call_count, 0) + + @patch.object(tasks, 'ace') + def test_delivery_disabled(self, mock_ace): + ScheduleConfigFactory.create(site=self.site_config.site, deliver_upgrade_reminder=False) + + mock_msg = Mock() + tasks._upgrade_reminder_schedule_send(self.site_config.site.id, mock_msg) + self.assertFalse(mock_ace.send.called) + + @patch.object(tasks, 'ace') + @patch.object(reminder, 'upgrade_reminder_schedule_bin') + def test_enqueue_disabled(self, mock_schedule_bin, mock_ace): + ScheduleConfigFactory.create(site=self.site_config.site, enqueue_upgrade_reminder=False) + + current_time = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC) + reminder.UpgradeReminderResolver(self.site_config.site, current_time).send(3) + self.assertFalse(mock_schedule_bin.called) + self.assertFalse(mock_schedule_bin.apply_async.called) + self.assertFalse(mock_ace.send.called) + + @patch.object(tasks, 'ace') + @patch.object(tasks, '_upgrade_reminder_schedule_send') + @ddt.data( + ((['filtered_org'], False, 1)), + ((['filtered_org'], True, 2)) + ) + @ddt.unpack + def test_site_config(self, org_list, exclude_orgs, expected_message_count, mock_schedule_send, mock_ace): + filtered_org = 'filtered_org' + unfiltered_org = 'unfiltered_org' + site1 = SiteFactory.create(domain='foo1.bar', name='foo1.bar') + limited_config = SiteConfigurationFactory.create(values={'course_org_filter': [filtered_org]}, site=site1) + site2 = SiteFactory.create(domain='foo2.bar', name='foo2.bar') + unlimited_config = SiteConfigurationFactory.create(values={'course_org_filter': []}, site=site2) + + for config in (limited_config, unlimited_config): + ScheduleConfigFactory.create(site=config.site) + + user1 = UserFactory.create(id=tasks.UPGRADE_REMINDER_NUM_BINS) + user2 = UserFactory.create(id=tasks.UPGRADE_REMINDER_NUM_BINS * 2) + + ScheduleFactory.create( + upgrade_deadline=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC), + enrollment__course__org=filtered_org, + enrollment__user=user1, + ) + ScheduleFactory.create( + upgrade_deadline=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC), + enrollment__course__org=unfiltered_org, + enrollment__user=user1, + ) + ScheduleFactory.create( + upgrade_deadline=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC), + enrollment__course__org=unfiltered_org, + enrollment__user=user2, + ) + + test_time = datetime.datetime(2017, 8, 3, 17, tzinfo=pytz.UTC) + test_time_str = serialize(test_time) + with self.assertNumQueries(2): + tasks.upgrade_reminder_schedule_bin( + limited_config.site.id, target_day_str=test_time_str, day_offset=2, bin_num=0, + org_list=org_list, exclude_orgs=exclude_orgs, + ) + + self.assertEqual(mock_schedule_send.apply_async.call_count, expected_message_count) + self.assertFalse(mock_ace.send.called) + + @patch.object(tasks, 'ace') + @patch.object(tasks, '_upgrade_reminder_schedule_send') + def test_multiple_enrollments(self, mock_schedule_send, mock_ace): + user = UserFactory.create() + schedules = [ + ScheduleFactory.create( + upgrade_deadline=datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC), + enrollment__user=user, + enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(course_num)) + ) + for course_num in (1, 2, 3) + ] + + test_time = datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC) + test_time_str = serialize(test_time) + with self.assertNumQueries(2): + tasks.upgrade_reminder_schedule_bin( + self.site_config.site.id, target_day_str=test_time_str, day_offset=2, + bin_num=user.id % tasks.UPGRADE_REMINDER_NUM_BINS, + org_list=[schedules[0].enrollment.course.org], + ) + self.assertEqual(mock_schedule_send.apply_async.call_count, 3) + self.assertFalse(mock_ace.send.called) + + @ddt.data(*itertools.product((1, 10, 100), (2, 10))) + @ddt.unpack + def test_templates(self, message_count, day): + + user = UserFactory.create() + schedules = [ + ScheduleFactory.create( + upgrade_deadline=datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC), + enrollment__user=user, + enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(course_num)) + ) + for course_num in range(message_count) + ] + + test_time = datetime.datetime(2017, 8, 3, 19, tzinfo=pytz.UTC) + test_time_str = serialize(test_time) + + patch_policies(self, [StubPolicy([ChannelType.PUSH])]) + mock_channel = Mock( + name='test_channel', + channel_type=ChannelType.EMAIL + ) + patch_channels(self, [mock_channel]) + + sent_messages = [] + + templates_override = deepcopy(settings.TEMPLATES) + templates_override[0]['OPTIONS']['string_if_invalid'] = "TEMPLATE WARNING - MISSING VARIABLE [%s]" + with self.settings(TEMPLATES=templates_override): + with patch.object(tasks, '_upgrade_reminder_schedule_send') as mock_schedule_send: + mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args) + + with self.assertNumQueries(2): + tasks.upgrade_reminder_schedule_bin( + self.site_config.site.id, target_day_str=test_time_str, day_offset=day, + bin_num=user.id % tasks.UPGRADE_REMINDER_NUM_BINS, + org_list=[schedules[0].enrollment.course.org], + ) + + self.assertEqual(len(sent_messages), message_count) + + for args in sent_messages: + tasks._upgrade_reminder_schedule_send(*args) + + self.assertEqual(mock_channel.deliver.call_count, message_count) + for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls: + for template in attr.astuple(email): + self.assertNotIn("TEMPLATE WARNING", template) diff --git a/openedx/core/djangoapps/schedules/tasks.py b/openedx/core/djangoapps/schedules/tasks.py index c687150c53..a9cdcfcd51 100644 --- a/openedx/core/djangoapps/schedules/tasks.py +++ b/openedx/core/djangoapps/schedules/tasks.py @@ -8,8 +8,9 @@ from django.contrib.auth.models import User from django.contrib.sites.models import Site from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse -from django.db.models import F, Min, Prefetch +from django.db.models import F, Min from django.db.utils import DatabaseError +from django.utils.formats import dateformat, get_format from edx_ace import ace from edx_ace.message import Message @@ -17,7 +18,6 @@ from edx_ace.recipient import Recipient from edx_ace.utils.date import deserialize from opaque_keys.edx.keys import CourseKey -from course_modes.models import CourseMode from edxmako.shortcuts import marketing_link from openedx.core.djangoapps.schedules.message_type import ScheduleMessageType from openedx.core.djangoapps.schedules.models import Schedule, ScheduleConfig @@ -27,7 +27,6 @@ from openedx.core.djangoapps.schedules.template_context import ( encode_urls_in_dict, get_base_template_context ) -from openedx.core.djangoapps.user_api.models import UserPreference LOG = logging.getLogger(__name__) @@ -232,9 +231,7 @@ def _recurring_nudge_schedules_for_bin(target_day, bin_num, org_list, exclude_or class UpgradeReminder(ScheduleMessageType): - def __init__(self, day, *args, **kwargs): - super(UpgradeReminder, self).__init__(*args, **kwargs) - self.name = "upgradereminder".format(day) + pass @task(ignore_result=True, routing_key=ROUTING_KEY) @@ -242,7 +239,7 @@ def upgrade_reminder_schedule_bin( site_id, target_day_str, day_offset, bin_num, org_list, exclude_orgs=False, override_recipient_email=None, ): target_day = deserialize(target_day_str) - msg_type = UpgradeReminder(abs(day_offset)) + msg_type = UpgradeReminder() for (user, language, context) in _upgrade_reminder_schedules_for_bin(target_day, bin_num, org_list, exclude_orgs): msg = msg_type.personalize( @@ -267,29 +264,34 @@ def _upgrade_reminder_schedule_send(site_id, msg_str): def _upgrade_reminder_schedules_for_bin(target_day, bin_num, org_list, exclude_orgs=False): + beginning_of_day = target_day.replace(hour=0, minute=0, second=0) + users = User.objects.filter( + courseenrollment__schedule__upgrade_deadline__gte=beginning_of_day, + courseenrollment__schedule__upgrade_deadline__lt=beginning_of_day + datetime.timedelta(days=1), + courseenrollment__is_active=True, + ).annotate( + first_schedule=Min('courseenrollment__schedule__upgrade_deadline') + ).annotate( + id_mod=F('id') % UPGRADE_REMINDER_NUM_BINS + ).filter( + id_mod=bin_num + ) + schedules = Schedule.objects.select_related( 'enrollment__user__profile', 'enrollment__course', - ).prefetch_related( - Prefetch( - 'enrollment__course__modes', - queryset=CourseMode.objects.filter(mode_slug=CourseMode.VERIFIED), - to_attr='verified_modes' - ), - Prefetch( - 'enrollment__user__preferences', - queryset=UserPreference.objects.filter(key='time_zone'), - to_attr='tzprefs' - ), - ).annotate( - id_mod=F('enrollment__user__id') % UPGRADE_REMINDER_NUM_BINS ).filter( - id_mod=bin_num - ).filter( - upgrade_deadline__year=target_day.year, - upgrade_deadline__month=target_day.month, - upgrade_deadline__day=target_day.day, - ) + enrollment__user__in=users, + upgrade_deadline__gte=beginning_of_day, + upgrade_deadline__lt=beginning_of_day + datetime.timedelta(days=1), + enrollment__is_active=True, + ).order_by('enrollment__user__id') + + if org_list is not None: + if exclude_orgs: + schedules = schedules.exclude(enrollment__course__org__in=org_list) + else: + schedules = schedules.filter(enrollment__course__org__in=org_list) if "read_replica" in settings.DATABASES: schedules = schedules.using("read_replica") @@ -299,7 +301,6 @@ def _upgrade_reminder_schedules_for_bin(target_day, bin_num, org_list, exclude_o user = enrollment.user course_id_str = str(enrollment.course_id) - course = enrollment.course # TODO: group by schedule and user like recurring nudge course_id_strs = [course_id_str] @@ -309,7 +310,14 @@ def _upgrade_reminder_schedules_for_bin(target_day, bin_num, org_list, exclude_o template_context.update({ 'student_name': user.profile.name, 'user_personal_address': user.profile.name if user.profile.name else user.username, - 'user_schedule_upgrade_deadline_time': schedule.upgrade_deadline, + 'user_schedule_upgrade_deadline_time': dateformat.format( + schedule.upgrade_deadline, + get_format( + 'DATE_FORMAT', + lang=first_schedule.enrollment.course.language, + use_l10n=True + ) + ), 'course_name': first_schedule.enrollment.course.display_name, 'course_url': absolute_url(reverse('course_root', args=[str(first_schedule.enrollment.course_id)])), @@ -318,4 +326,4 @@ def _upgrade_reminder_schedules_for_bin(target_day, bin_num, org_list, exclude_o 'course_ids': course_id_strs, }) - yield (user, course.language, template_context) + yield (user, first_schedule.enrollment.course.language, template_context) diff --git a/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.html b/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.html index 70f6fc3299..448b656628 100644 --- a/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.html +++ b/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.html @@ -2,15 +2,15 @@ {% load i18n %} {% block preview_text %} - {% if courses|length > 1 %} + {% if course_ids|length > 1 %} {% blocktrans trimmed %} We hope you are enjoying {{ course_name }}, and other courses on edX.org. - Upgrade by {{ user_schedule_upgrade_deadline_time|date:"l, F dS, Y" }} to get a shareable certificate! + Upgrade by {{ user_schedule_upgrade_deadline_time }} to get a shareable certificate! {% endblocktrans %} {% else %} {% blocktrans trimmed %} We hope you are enjoying {{ course_name }}. - Upgrade by {{ user_schedule_upgrade_deadline_time|date:"l, F dS, Y" }} to get a shareable certificate! + Upgrade by {{ user_schedule_upgrade_deadline_time }} to get a shareable certificate! {% endblocktrans %} {% endif %} {% endblock %} @@ -22,16 +22,15 @@

{% trans "Upgrade now" %}

- {% if courses|length > 1 %} + {% if course_ids|length > 1 %} {% blocktrans trimmed %} We hope you are enjoying {{ course_name }}, and other courses on edX.org. - {{ user_schedule_upgrade_deadline_time|date }} - Upgrade by {{ user_schedule_upgrade_deadline_time|date:"l, F dS, Y" }} to get a shareable certificate! + Upgrade by {{ user_schedule_upgrade_deadline_time }} to get a shareable certificate! {% endblocktrans %} {% else %} {% blocktrans trimmed %} We hope you are enjoying {{ course_name }}. - Upgrade by {{ user_schedule_upgrade_deadline_time|date:"l, F dS, Y" }} to get a shareable certificate! + Upgrade by {{ user_schedule_upgrade_deadline_time }} to get a shareable certificate! {% endblocktrans %} {% endif %}

@@ -39,7 +38,7 @@

1 %} + {% if course_ids|length > 1 %} href="{{ dashboard_url }}" {% else %} href="{{ course_url }}" diff --git a/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.txt b/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.txt index 83d76022de..9eee300a2b 100644 --- a/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.txt +++ b/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.txt @@ -4,17 +4,17 @@ Dear {{ user_personal_address }}, {% endblocktrans %} -{% if courses|length > 1 %} +{% if course_ids|length > 1 %} {% blocktrans trimmed %} We hope you are enjoying {{ course_name }}, and other courses on edX.org. - Upgrade by {{ user_schedule_upgrade_deadline_time|date:"l, F dS, Y" }} to get a shareable certificate! + Upgrade by {{ user_schedule_upgrade_deadline_time }} to get a shareable certificate! {% endblocktrans %} {% trans "Upgrade now at" %} <{{ dashboard_url }}> {% else %} {% blocktrans trimmed %} We hope you are enjoying {{ course_name }}. - Upgrade by {{ user_schedule_upgrade_deadline_time|date:"l, F dS, Y" }} to get a shareable certificate! + Upgrade by {{ user_schedule_upgrade_deadline_time }} to get a shareable certificate! {% endblocktrans %} {% trans "Upgrade now at" %} <{{ course_url }}> diff --git a/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/from_name.txt b/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/from_name.txt index f07331f68a..d25f5d3e4a 100644 --- a/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/from_name.txt +++ b/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/from_name.txt @@ -1,4 +1,4 @@ -{% if courses|length > 1 %} +{% if course_ids|length > 1 %} {{ platform_name }} {% else %} {{ course_name }} diff --git a/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/subject.txt b/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/subject.txt index bcc0c6d217..1f1a6e4988 100644 --- a/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/subject.txt +++ b/openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/subject.txt @@ -1,6 +1,6 @@ {% load i18n %} -{% if courses|length > 1 %} +{% if course_ids|length > 1 %} {% blocktrans %}Only two days left to upgrade on {{ platform_name }}!{% endblocktrans %} {% else %} {% blocktrans %}Only two days left to upgrade in {{course_name}} !{% endblocktrans %} diff --git a/openedx/core/djangoapps/schedules/tests/factories.py b/openedx/core/djangoapps/schedules/tests/factories.py index 7c286afb7f..13c88e403d 100644 --- a/openedx/core/djangoapps/schedules/tests/factories.py +++ b/openedx/core/djangoapps/schedules/tests/factories.py @@ -23,3 +23,5 @@ class ScheduleConfigFactory(factory.DjangoModelFactory): create_schedules = True enqueue_recurring_nudge = True deliver_recurring_nudge = True + enqueue_upgrade_reminder = True + deliver_upgrade_reminder = True