AA-67: Adds in new resolver logic for Weekly Highlights to learners
This commit is contained in:
@@ -6,6 +6,8 @@ schedule experience built on the Schedules app.
|
||||
|
||||
import logging
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_WAFFLE_FLAG
|
||||
from openedx.core.djangoapps.schedules.exceptions import CourseUpdateDoesNotExist
|
||||
from openedx.core.lib.request_utils import get_request_or_stub
|
||||
@@ -62,6 +64,24 @@ def get_week_highlights(user, course_key, week_num):
|
||||
return highlights
|
||||
|
||||
|
||||
def get_next_section_highlights(user, course_key, target_date):
|
||||
"""
|
||||
Get highlights (list of unicode strings) for a week, based upon the current date.
|
||||
|
||||
Raises:
|
||||
CourseUpdateDoeNotExist: if highlights do not exist for the requested date
|
||||
"""
|
||||
course_descriptor = _get_course_with_highlights(course_key)
|
||||
course_module = _get_course_module(course_descriptor, user)
|
||||
sections_with_highlights = _get_sections_with_highlights(course_module)
|
||||
highlights = _get_highlights_for_next_section(
|
||||
sections_with_highlights,
|
||||
course_key,
|
||||
target_date
|
||||
)
|
||||
return highlights
|
||||
|
||||
|
||||
def _get_course_with_highlights(course_key):
|
||||
# pylint: disable=missing-docstring
|
||||
if not COURSE_UPDATE_WAFFLE_FLAG.is_enabled(course_key):
|
||||
@@ -128,3 +148,17 @@ def _get_highlights_for_week(sections, week_num, course_key):
|
||||
|
||||
section = sections[week_num - 1]
|
||||
return section.highlights
|
||||
|
||||
|
||||
def _get_highlights_for_next_section(sections, course_key, target_date):
|
||||
sorted_sections = sorted(sections, key=lambda section: section.due)
|
||||
for index, sorted_section in enumerate(sorted_sections):
|
||||
if sorted_section.due.date() == target_date and index + 1 < len(sorted_sections):
|
||||
# Return index + 2 for "week_num", since weeks start at 1 as opposed to indexes,
|
||||
# and we want the next week, so +1 for index and +1 for next
|
||||
return sections[index + 1].highlights, index + 2
|
||||
raise CourseUpdateDoesNotExist(
|
||||
u"No section found ending on {} for {}".format(
|
||||
target_date, course_key
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
Management command to send Schedule course updates
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import pytz
|
||||
from textwrap import dedent
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from openedx.core.djangoapps.schedules.management.commands import SendEmailBaseCommand
|
||||
from openedx.core.djangoapps.schedules.tasks import ScheduleCourseNextSectionUpdate
|
||||
|
||||
|
||||
class Command(SendEmailBaseCommand):
|
||||
"""
|
||||
Command to send Schedule course updates
|
||||
"""
|
||||
help = dedent(__doc__).strip()
|
||||
async_send_task = ScheduleCourseNextSectionUpdate
|
||||
log_prefix = 'Course Update'
|
||||
|
||||
def handle(self, *args, ** options):
|
||||
current_date = datetime.datetime(
|
||||
*[int(x) for x in options['date'].split('-')],
|
||||
tzinfo=pytz.UTC
|
||||
)
|
||||
|
||||
site = Site.objects.get(domain__iexact=options['site_domain_name'])
|
||||
override_recipient_email = options.get('override_recipient_email')
|
||||
|
||||
# day_offset set to 1 as we'll always be looking for yesterday
|
||||
self.async_send_task.enqueue(site, current_date, 1, override_recipient_email)
|
||||
@@ -13,12 +13,13 @@ from django.urls import reverse
|
||||
from edx_ace.recipient import Recipient
|
||||
from edx_ace.recipient_resolver import RecipientResolver
|
||||
from edx_django_utils.monitoring import function_trace, set_custom_metric
|
||||
from edx_when.api import get_schedules_with_due_date
|
||||
|
||||
from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
|
||||
from lms.djangoapps.discussion.notification_prefs.views import UsernameCipher
|
||||
from openedx.core.djangoapps.ace_common.template_context import get_base_template_context
|
||||
from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH
|
||||
from openedx.core.djangoapps.schedules.content_highlights import get_week_highlights
|
||||
from openedx.core.djangoapps.schedules.content_highlights import get_week_highlights, get_next_section_highlights
|
||||
from openedx.core.djangoapps.schedules.exceptions import CourseUpdateDoesNotExist
|
||||
from openedx.core.djangoapps.schedules.message_types import CourseUpdate, InstructorLedCourseUpdate
|
||||
from openedx.core.djangoapps.schedules.models import Schedule, ScheduleExperience
|
||||
@@ -417,6 +418,93 @@ class CourseUpdateResolver(BinnedSchedulesBaseResolver):
|
||||
yield (user, schedule.enrollment.course.closest_released_language, template_context, course.self_paced)
|
||||
|
||||
|
||||
@attr.s
|
||||
class CourseNextSectionUpdate(PrefixedDebugLoggerMixin, RecipientResolver):
|
||||
"""
|
||||
Send a message to all users whose schedule gives them a due date of yesterday.
|
||||
"""
|
||||
async_send_task = attr.ib()
|
||||
site = attr.ib()
|
||||
target_datetime = attr.ib()
|
||||
course_key = attr.ib()
|
||||
override_recipient_email = attr.ib(default=None)
|
||||
|
||||
log_prefix = 'Next Section Course Update'
|
||||
experience_filter = Q(experience__experience_type=ScheduleExperience.EXPERIENCES.course_updates)
|
||||
|
||||
def send(self):
|
||||
schedules = self.get_schedules()
|
||||
LOG.info(
|
||||
u'Found {} emails to send for course-key: {}'.format(
|
||||
len(schedules), self.course_key
|
||||
)
|
||||
)
|
||||
for (user, language, context, is_self_paced) in schedules:
|
||||
msg_type = CourseUpdate() if is_self_paced else InstructorLedCourseUpdate()
|
||||
msg_type.personalize(
|
||||
Recipient(
|
||||
user.username,
|
||||
self.override_recipient_email or user.email,
|
||||
),
|
||||
language,
|
||||
context,
|
||||
)
|
||||
LOG.info(
|
||||
u'Sending email to user: {} for course-key: {}'.format(
|
||||
user.username,
|
||||
self.course_key
|
||||
)
|
||||
)
|
||||
# TODO: Uncomment below when going live
|
||||
# with function_trace('enqueue_send_task'):
|
||||
# self.async_send_task.apply_async((self.site.id, str(msg)), retry=False)
|
||||
|
||||
def get_schedules(self):
|
||||
target_date = self.target_datetime.date()
|
||||
schedules = get_schedules_with_due_date(self.course_key, target_date).filter(
|
||||
self.experience_filter,
|
||||
active=True,
|
||||
enrollment__user__is_active=True,
|
||||
)
|
||||
|
||||
template_context = get_base_template_context(self.site)
|
||||
for schedule in schedules:
|
||||
enrollment = schedule.enrollment
|
||||
course = schedule.enrollment.course
|
||||
user = enrollment.user
|
||||
|
||||
try:
|
||||
week_highlights, week_num = get_next_section_highlights(user, course.id, target_date)
|
||||
except CourseUpdateDoesNotExist:
|
||||
LOG.warning(
|
||||
u'Weekly highlights for user {} of course {} does not exist or is disabled'.format(
|
||||
user, course.id
|
||||
)
|
||||
)
|
||||
# continue to the next schedule, don't yield an email for this one
|
||||
continue
|
||||
unsubscribe_url = None
|
||||
if (COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH.is_enabled() and
|
||||
'bulk_email_optout' in settings.ACE_ENABLED_POLICIES):
|
||||
unsubscribe_url = reverse('bulk_email_opt_out', kwargs={
|
||||
'token': UsernameCipher.encrypt(user.username),
|
||||
'course_id': str(enrollment.course_id),
|
||||
})
|
||||
|
||||
template_context.update({
|
||||
'course_name': course.display_name,
|
||||
'course_url': _get_trackable_course_home_url(enrollment.course_id),
|
||||
'week_num': week_num,
|
||||
'week_highlights': week_highlights,
|
||||
# This is used by the bulk email optout policy
|
||||
'course_ids': [str(enrollment.course_id)],
|
||||
'unsubscribe_url': unsubscribe_url,
|
||||
})
|
||||
template_context.update(_get_upsell_information_for_schedule(user, schedule))
|
||||
|
||||
yield (user, enrollment.course.closest_released_language, template_context, course.self_paced)
|
||||
|
||||
|
||||
def _get_trackable_course_home_url(course_id):
|
||||
"""
|
||||
Get the home page URL for the course.
|
||||
|
||||
@@ -20,6 +20,7 @@ from edx_django_utils.monitoring import set_custom_metric
|
||||
from eventtracking import tracker
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.schedules import message_types, resolvers
|
||||
from openedx.core.djangoapps.schedules.models import Schedule, ScheduleConfig
|
||||
from openedx.core.lib.celery.task_utils import emulate_http_request
|
||||
@@ -38,6 +39,7 @@ KNOWN_RETRY_ERRORS = ( # Errors we expect occasionally that could resolve on re
|
||||
RECURRING_NUDGE_LOG_PREFIX = 'Recurring Nudge'
|
||||
UPGRADE_REMINDER_LOG_PREFIX = 'Upgrade Reminder'
|
||||
COURSE_UPDATE_LOG_PREFIX = 'Course Update'
|
||||
COURSE_NEXT_SECTION_UPDATE_LOG_PREFIX = 'Course Next Section Update'
|
||||
|
||||
|
||||
@task(base=LoggedPersistOnFailureTask, bind=True, default_retry_delay=30)
|
||||
@@ -59,12 +61,10 @@ def update_course_schedules(self, **kwargs):
|
||||
|
||||
class ScheduleMessageBaseTask(LoggedTask):
|
||||
"""
|
||||
Base class for top-level Schedule tasks that create subtasks
|
||||
for each Bin.
|
||||
Base class for top-level Schedule tasks that create subtasks.
|
||||
"""
|
||||
ignore_result = True
|
||||
routing_key = ROUTING_KEY
|
||||
num_bins = resolvers.DEFAULT_NUM_BINS
|
||||
enqueue_config_var = None # define in subclass
|
||||
log_prefix = None
|
||||
resolver = None # define in subclass
|
||||
@@ -84,6 +84,20 @@ class ScheduleMessageBaseTask(LoggedTask):
|
||||
"""
|
||||
LOG.info(cls.log_prefix + ': ' + message, *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def is_enqueue_enabled(cls, site):
|
||||
if cls.enqueue_config_var:
|
||||
return getattr(ScheduleConfig.current(site), cls.enqueue_config_var)
|
||||
return False
|
||||
|
||||
|
||||
class BinnedScheduleMessageBaseTask(ScheduleMessageBaseTask):
|
||||
"""
|
||||
Base class for top-level Schedule tasks that create subtasks
|
||||
for each Bin.
|
||||
"""
|
||||
num_bins = resolvers.DEFAULT_NUM_BINS
|
||||
|
||||
@classmethod
|
||||
def enqueue(cls, site, current_date, day_offset, override_recipient_email=None):
|
||||
current_date = resolvers._get_datetime_beginning_of_day(current_date)
|
||||
@@ -108,12 +122,6 @@ class ScheduleMessageBaseTask(LoggedTask):
|
||||
retry=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def is_enqueue_enabled(cls, site):
|
||||
if cls.enqueue_config_var:
|
||||
return getattr(ScheduleConfig.current(site), cls.enqueue_config_var)
|
||||
return False
|
||||
|
||||
def run(
|
||||
self, site_id, target_day_str, day_offset, bin_num, override_recipient_email=None,
|
||||
):
|
||||
@@ -164,7 +172,7 @@ def _course_update_schedule_send(site_id, msg_str):
|
||||
)
|
||||
|
||||
|
||||
class ScheduleRecurringNudge(ScheduleMessageBaseTask):
|
||||
class ScheduleRecurringNudge(BinnedScheduleMessageBaseTask):
|
||||
num_bins = resolvers.RECURRING_NUDGE_NUM_BINS
|
||||
enqueue_config_var = 'enqueue_recurring_nudge'
|
||||
log_prefix = RECURRING_NUDGE_LOG_PREFIX
|
||||
@@ -175,7 +183,7 @@ class ScheduleRecurringNudge(ScheduleMessageBaseTask):
|
||||
return message_types.RecurringNudge(abs(day_offset))
|
||||
|
||||
|
||||
class ScheduleUpgradeReminder(ScheduleMessageBaseTask):
|
||||
class ScheduleUpgradeReminder(BinnedScheduleMessageBaseTask):
|
||||
num_bins = resolvers.UPGRADE_REMINDER_NUM_BINS
|
||||
enqueue_config_var = 'enqueue_upgrade_reminder'
|
||||
log_prefix = UPGRADE_REMINDER_LOG_PREFIX
|
||||
@@ -186,7 +194,7 @@ class ScheduleUpgradeReminder(ScheduleMessageBaseTask):
|
||||
return message_types.UpgradeReminder()
|
||||
|
||||
|
||||
class ScheduleCourseUpdate(ScheduleMessageBaseTask):
|
||||
class ScheduleCourseUpdate(BinnedScheduleMessageBaseTask):
|
||||
num_bins = resolvers.COURSE_UPDATE_NUM_BINS
|
||||
enqueue_config_var = 'enqueue_course_update'
|
||||
log_prefix = COURSE_UPDATE_LOG_PREFIX
|
||||
@@ -197,6 +205,47 @@ class ScheduleCourseUpdate(ScheduleMessageBaseTask):
|
||||
return message_types.CourseUpdate()
|
||||
|
||||
|
||||
class ScheduleCourseNextSectionUpdate(ScheduleMessageBaseTask):
|
||||
enqueue_config_var = 'enqueue_course_update'
|
||||
log_prefix = COURSE_NEXT_SECTION_UPDATE_LOG_PREFIX
|
||||
resolver = resolvers.CourseNextSectionUpdate
|
||||
async_send_task = _course_update_schedule_send
|
||||
|
||||
@classmethod
|
||||
def enqueue(cls, site, current_date, day_offset, override_recipient_email=None):
|
||||
target_date = (current_date - datetime.timedelta(days=day_offset)).date()
|
||||
|
||||
if not cls.is_enqueue_enabled(site):
|
||||
cls.log_info(u'Message queuing disabled for site %s', site.domain)
|
||||
return
|
||||
|
||||
cls.log_info(u'Target date = %s', target_date.isoformat())
|
||||
for course_key in CourseOverview.get_all_course_keys():
|
||||
task_args = (
|
||||
site.id,
|
||||
serialize(target_date),
|
||||
course_key,
|
||||
override_recipient_email,
|
||||
)
|
||||
cls.log_info(u'Launching task with args = %r', task_args)
|
||||
cls().apply_async(
|
||||
task_args,
|
||||
retry=False,
|
||||
)
|
||||
|
||||
def run(self, site_id, target_day_str, course_key, override_recipient_email=None):
|
||||
site = Site.objects.select_related('configuration').get(id=site_id)
|
||||
with emulate_http_request(site=site):
|
||||
_annotate_for_monitoring(message_types.CourseUpdate(), site, 0, target_day_str, -1)
|
||||
return self.resolver(
|
||||
self.async_send_task,
|
||||
site,
|
||||
deserialize(target_day_str),
|
||||
course_key,
|
||||
override_recipient_email,
|
||||
).send()
|
||||
|
||||
|
||||
def _schedule_send(msg_str, site_id, delivery_config_var, log_prefix):
|
||||
site = Site.objects.select_related('configuration').get(pk=site_id)
|
||||
if _is_delivery_enabled(site, delivery_config_var, log_prefix):
|
||||
@@ -253,18 +302,24 @@ def _is_delivery_enabled(site, delivery_config_var, log_prefix):
|
||||
LOG.info(u'%s: Message delivery disabled for site %s', log_prefix, site.domain)
|
||||
|
||||
|
||||
def _annotate_for_monitoring(message_type, site, bin_num, target_day_str, day_offset):
|
||||
def _annotate_for_monitoring(message_type, site, bin_num=None, target_day_str=None, day_offset=None, course_key=None):
|
||||
# This identifies the type of message being sent, for example: schedules.recurring_nudge3.
|
||||
set_custom_metric('message_name', '{0}.{1}'.format(message_type.app_label, message_type.name))
|
||||
# The domain name of the site we are sending the message for.
|
||||
set_custom_metric('site', site.domain)
|
||||
# This is the "bin" of data being processed. We divide up the work into chunks so that we don't tie up celery
|
||||
# workers for too long. This could help us identify particular bins that are problematic.
|
||||
set_custom_metric('bin', bin_num)
|
||||
if bin_num:
|
||||
set_custom_metric('bin', bin_num)
|
||||
# The date we are processing data for.
|
||||
set_custom_metric('target_day', target_day_str)
|
||||
if target_day_str:
|
||||
set_custom_metric('target_day', target_day_str)
|
||||
# The number of days relative to the current date to process data for.
|
||||
set_custom_metric('day_offset', day_offset)
|
||||
if day_offset:
|
||||
set_custom_metric('day_offset', day_offset)
|
||||
# If we're processing these according to a course_key rather than bin we can use this to identify problematic keys.
|
||||
if course_key:
|
||||
set_custom_metric('course_key', course_key)
|
||||
# A unique identifier for this batch of messages being sent.
|
||||
set_custom_metric('send_uuid', message_type.uuid)
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import datetime
|
||||
|
||||
from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_WAFFLE_FLAG
|
||||
from openedx.core.djangoapps.schedules.content_highlights import course_has_highlights, get_week_highlights
|
||||
from openedx.core.djangoapps.schedules.content_highlights import (
|
||||
course_has_highlights, get_week_highlights, get_next_section_highlights,
|
||||
)
|
||||
from openedx.core.djangoapps.schedules.exceptions import CourseUpdateDoesNotExist
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
@@ -130,3 +133,25 @@ class TestContentHighlights(ModuleStoreTestCase):
|
||||
self.assertTrue(course_has_highlights(self.course_key))
|
||||
with self.assertRaises(CourseUpdateDoesNotExist):
|
||||
get_week_highlights(self.user, self.course_key, week_num=1)
|
||||
|
||||
@override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True)
|
||||
def test_get_next_section_highlights(self):
|
||||
yesterday = datetime.datetime.utcnow() - datetime.timedelta(days=1)
|
||||
today = datetime.datetime.utcnow()
|
||||
tomorrow = datetime.datetime.utcnow() + datetime.timedelta(days=1)
|
||||
with self.store.bulk_operations(self.course_key):
|
||||
self._create_chapter( # Week 1
|
||||
highlights=[u'a', u'b', u'á'],
|
||||
due=yesterday,
|
||||
)
|
||||
self._create_chapter( # Week 2
|
||||
highlights=[u'skipped a week'],
|
||||
due=today,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
get_next_section_highlights(self.user, self.course_key, yesterday.date()),
|
||||
([u'skipped a week'], 2),
|
||||
)
|
||||
with self.assertRaises(CourseUpdateDoesNotExist):
|
||||
get_next_section_highlights(self.user, self.course_key, tomorrow.date())
|
||||
|
||||
@@ -14,9 +14,11 @@ from mock import Mock, patch
|
||||
from waffle.testutils import override_switch
|
||||
|
||||
from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_WAFFLE_FLAG
|
||||
from openedx.core.djangoapps.schedules.models import Schedule
|
||||
from openedx.core.djangoapps.schedules.resolvers import (
|
||||
BinnedSchedulesBaseResolver,
|
||||
CourseUpdateResolver,
|
||||
CourseNextSectionUpdate,
|
||||
)
|
||||
from openedx.core.djangoapps.schedules.tests.factories import ScheduleConfigFactory
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory
|
||||
@@ -167,3 +169,75 @@ class TestCourseUpdateResolver(SchedulesResolverTestMixin, ModuleStoreTestCase):
|
||||
self.user.save()
|
||||
schedules = resolver.get_schedules_with_target_date_by_bin_and_orgs()
|
||||
self.assertEqual(schedules.count(), 0)
|
||||
|
||||
|
||||
@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 TestCourseNextSectionUpdateResolver(SchedulesResolverTestMixin, ModuleStoreTestCase):
|
||||
"""
|
||||
Tests the TestCourseNextSectionUpdateResolver.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestCourseNextSectionUpdateResolver, self).setUp()
|
||||
self.course = CourseFactory(highlights_enabled_for_messaging=True, self_paced=True)
|
||||
self.yesterday = datetime.datetime.utcnow() - datetime.timedelta(days=1)
|
||||
self.today = datetime.datetime.utcnow()
|
||||
self.tomorrow = datetime.datetime.utcnow() + datetime.timedelta(days=1)
|
||||
|
||||
with self.store.bulk_operations(self.course.id):
|
||||
ItemFactory.create(parent=self.course, category='chapter', highlights=[u'good stuff 1'], due=self.yesterday)
|
||||
ItemFactory.create(parent=self.course, category='chapter', highlights=[u'good stuff 2'], due=self.today)
|
||||
ItemFactory.create(parent=self.course, category='chapter', highlights=[u'good stuff 3'], due=self.tomorrow)
|
||||
|
||||
def create_resolver(self):
|
||||
"""
|
||||
Creates a CourseNextSectionUpdateResolver with an enrollment to schedule.
|
||||
"""
|
||||
with patch('openedx.core.djangoapps.schedules.signals.get_current_site') as mock_get_current_site:
|
||||
mock_get_current_site.return_value = self.site_config.site
|
||||
CourseEnrollmentFactory(course_id=self.course.id, user=self.user, mode=u'audit')
|
||||
|
||||
return CourseNextSectionUpdate(
|
||||
async_send_task=Mock(name='async_send_task'),
|
||||
site=self.site_config.site,
|
||||
target_datetime=self.yesterday,
|
||||
course_key=self.course.id,
|
||||
)
|
||||
|
||||
@override_settings(CONTACT_MAILING_ADDRESS='123 Sesame Street')
|
||||
@override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True)
|
||||
def test_schedule_context(self):
|
||||
resolver = self.create_resolver()
|
||||
# Mock the call to edx-when to just return all schedules
|
||||
with patch('openedx.core.djangoapps.schedules.resolvers.get_schedules_with_due_date') as mock_get_schedules:
|
||||
mock_get_schedules.return_value = Schedule.objects.all()
|
||||
schedules = list(resolver.get_schedules())
|
||||
expected_context = {
|
||||
'contact_email': 'info@example.com',
|
||||
'contact_mailing_address': '123 Sesame Street',
|
||||
'course_ids': [str(self.course.id)],
|
||||
'course_name': self.course.display_name,
|
||||
'course_url': '/courses/{}/course/'.format(self.course.id),
|
||||
'dashboard_url': '/dashboard',
|
||||
'homepage_url': '/',
|
||||
'mobile_store_urls': {},
|
||||
'platform_name': u'\xe9dX',
|
||||
'show_upsell': False,
|
||||
'social_media_urls': {},
|
||||
'template_revision': 'release',
|
||||
'unsubscribe_url': None,
|
||||
'week_highlights': ['good stuff 2'],
|
||||
'week_num': 2,
|
||||
}
|
||||
self.assertEqual(schedules, [(self.user, None, expected_context, True)])
|
||||
|
||||
@override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True)
|
||||
@override_switch('schedules.course_update_show_unsubscribe', True)
|
||||
def test_schedule_context_show_unsubscribe(self):
|
||||
resolver = self.create_resolver()
|
||||
# Mock the call to edx-when to just return all schedules
|
||||
with patch('openedx.core.djangoapps.schedules.resolvers.get_schedules_with_due_date') as mock_get_schedules:
|
||||
mock_get_schedules.return_value = Schedule.objects.all()
|
||||
schedules = list(resolver.get_schedules())
|
||||
self.assertIn('optout', schedules[0][2]['unsubscribe_url'])
|
||||
|
||||
@@ -11,7 +11,7 @@ from django.conf import settings
|
||||
from mock import DEFAULT, Mock, patch
|
||||
|
||||
from openedx.core.djangoapps.schedules.resolvers import DEFAULT_NUM_BINS
|
||||
from openedx.core.djangoapps.schedules.tasks import ScheduleMessageBaseTask
|
||||
from openedx.core.djangoapps.schedules.tasks import BinnedScheduleMessageBaseTask
|
||||
from openedx.core.djangoapps.schedules.tests.factories import ScheduleConfigFactory
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
|
||||
@@ -21,13 +21,13 @@ from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_un
|
||||
@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 TestScheduleMessageBaseTask(CacheIsolationTestCase):
|
||||
class TestBinnedScheduleMessageBaseTask(CacheIsolationTestCase):
|
||||
def setUp(self):
|
||||
super(TestScheduleMessageBaseTask, self).setUp()
|
||||
super(TestBinnedScheduleMessageBaseTask, self).setUp()
|
||||
|
||||
self.site = SiteFactory.create()
|
||||
self.schedule_config = ScheduleConfigFactory.create(site=self.site)
|
||||
self.basetask = ScheduleMessageBaseTask
|
||||
self.basetask = BinnedScheduleMessageBaseTask
|
||||
|
||||
def test_send_enqueue_disabled(self):
|
||||
send = Mock(name='async_send_task')
|
||||
|
||||
Reference in New Issue
Block a user