Commit d38428ea by Tyler Hallada Committed by GitHub

Merge pull request #16129 from edx/thallada/refactor-verified-upgrade-reminder

Refactor recurring nudge and verified upgrade reminder emails
parents 3fcd7c69 d5b6b170
...@@ -30,4 +30,6 @@ class ScheduleAdmin(admin.ModelAdmin): ...@@ -30,4 +30,6 @@ class ScheduleAdmin(admin.ModelAdmin):
@admin.register(models.ScheduleConfig) @admin.register(models.ScheduleConfig)
class ScheduleConfigAdmin(admin.ModelAdmin): class ScheduleConfigAdmin(admin.ModelAdmin):
search_fields = ('site',) search_fields = ('site',)
list_display = ('site', 'create_schedules', 'enqueue_recurring_nudge', 'deliver_recurring_nudge') list_display = ('site', 'create_schedules', 'enqueue_recurring_nudge',
'deliver_recurring_nudge', 'enqueue_upgrade_reminder',
'deliver_upgrade_reminder')
import datetime
import pytz
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand
from openedx.core.djangoapps.schedules.utils import PrefixedDebugLoggerMixin
class SendEmailBaseCommand(PrefixedDebugLoggerMixin, BaseCommand):
resolver_class = None # define in subclass
def add_arguments(self, parser):
parser.add_argument(
'--date',
default=datetime.datetime.utcnow().date().isoformat(),
help='The date to compute weekly messages relative to, in YYYY-MM-DD format',
)
parser.add_argument(
'--override-recipient-email',
help='Send all emails to this address instead of the actual recipient'
)
parser.add_argument('site_domain_name')
def handle(self, *args, **options):
resolver = self.make_resolver(*args, **options)
self.send_emails(resolver, *args, **options)
def make_resolver(self, *args, **options):
current_date = datetime.datetime(
*[int(x) for x in options['date'].split('-')],
tzinfo=pytz.UTC
)
self.log_debug('Args = %r', options)
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)
return self.resolver_class(site, current_date)
def send_emails(self, resolver, *args, **options):
pass # define in subclass
from __future__ import print_function from openedx.core.djangoapps.schedules.management.commands import SendEmailBaseCommand
from openedx.core.djangoapps.schedules.resolvers import ScheduleStartResolver
import datetime
import logging
from django.contrib.sites.models import Site class Command(SendEmailBaseCommand):
from django.core.management.base import BaseCommand resolver_class = ScheduleStartResolver
import pytz
from edx_ace.utils.date import serialize def __init__(self, *args, **kwargs):
from openedx.core.djangoapps.schedules.models import ScheduleConfig super(Command, self).__init__(*args, **kwargs)
from openedx.core.djangoapps.schedules.tasks import recurring_nudge_schedule_hour self.log_prefix = 'Scheduled Nudge'
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
from edx_ace.recipient_resolver import RecipientResolver def send_emails(self, resolver, *args, **options):
for day_offset in (-3, -10):
LOG = logging.getLogger(__name__) resolver.send(day_offset, options.get('override_recipient_email'))
class ScheduleStartResolver(RecipientResolver):
def __init__(self, site, current_date):
self.site = site
self.current_date = current_date.replace(hour=0, minute=0, second=0)
def send(self, day, override_recipient_email=None):
"""
Send a message to all users whose schedule started at ``self.current_date`` - ``day``.
"""
if not ScheduleConfig.current(self.site).enqueue_recurring_nudge:
LOG.debug('Recurring Nudge: Message queuing disabled for site %s', self.site.domain)
return
exclude_orgs, org_list = self.get_org_filter()
target_date = self.current_date - datetime.timedelta(days=day)
LOG.debug('Scheduled Nudge: Target date = %s', target_date.isoformat())
for hour in range(24):
target_hour = target_date + datetime.timedelta(hours=hour)
task_args = (self.site.id, day, serialize(target_hour), org_list, exclude_orgs, override_recipient_email)
LOG.debug('Scheduled Nudge: Launching task with args = %r', task_args)
recurring_nudge_schedule_hour.apply_async(task_args, retry=False)
def get_org_filter(self):
"""
Given the configuration of sites, get the list of orgs that should be included or excluded from this send.
Returns:
tuple: Returns a tuple (exclude_orgs, org_list). If exclude_orgs is True, then org_list is a list of the
only orgs that should be included in this send. If exclude_orgs is False, then org_list is a list of
orgs that should be excluded from this send. All other orgs should be included.
"""
try:
site_config = SiteConfiguration.objects.get(site_id=self.site.id)
org_list = site_config.values.get('course_org_filter', None)
exclude_orgs = False
if not org_list:
not_orgs = set()
for other_site_config in SiteConfiguration.objects.all():
not_orgs.update(other_site_config.values.get('course_org_filter', []))
org_list = list(not_orgs)
exclude_orgs = True
elif not isinstance(org_list, list):
org_list = [org_list]
except SiteConfiguration.DoesNotExist:
org_list = None
exclude_orgs = False
return exclude_orgs, org_list
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
'--date',
default=datetime.datetime.utcnow().date().isoformat(),
help='The date to compute weekly messages relative to, in YYYY-MM-DD format',
)
parser.add_argument(
'--override-recipient-email',
help='Send all emails to this address instead of the actual recipient'
)
parser.add_argument('site_domain_name')
def handle(self, *args, **options):
current_date = datetime.datetime(
*[int(x) for x in options['date'].split('-')],
tzinfo=pytz.UTC
)
LOG.debug('Scheduled Nudge: Args = %r', options)
LOG.debug('Scheduled Nudge: Current date = %s', current_date.isoformat())
site = Site.objects.get(domain__iexact=options['site_domain_name'])
LOG.debug('Scheduled Nudge: Running for site %s', site.domain)
resolver = ScheduleStartResolver(site, current_date)
for day in (3, 10):
resolver.send(day, options.get('override_recipient_email'))
from openedx.core.djangoapps.schedules.management.commands import SendEmailBaseCommand
from openedx.core.djangoapps.schedules.resolvers import UpgradeReminderResolver
class Command(SendEmailBaseCommand):
resolver_class = UpgradeReminderResolver
def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
self.log_prefix = 'Upgrade Reminder'
def send_emails(self, resolver, *args, **options):
resolver.send(2, options.get('override_recipient_email'))
from __future__ import print_function
import datetime
from dateutil.tz import tzutc, gettz
from django.core.management.base import BaseCommand
from django.db.models import Prefetch
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.http import urlquote
from openedx.core.djangoapps.schedules.message_type import ScheduleMessageType
from openedx.core.djangoapps.schedules.models import Schedule
from openedx.core.djangoapps.user_api.models import UserPreference
from edx_ace.recipient_resolver import RecipientResolver
from edx_ace import ace
from edx_ace.recipient import Recipient
from course_modes.models import CourseMode, format_course_price
from lms.djangoapps.experiments.utils import check_and_get_upgrade_link
class VerifiedUpgradeDeadlineReminder(ScheduleMessageType):
pass
class VerifiedDeadlineResolver(RecipientResolver):
def __init__(self, target_deadline):
self.target_deadline = target_deadline
def send(self, msg_type):
for (user, language, context) in self.build_email_context():
msg = msg_type.personalize(
Recipient(
user.username,
user.email,
),
language,
context
)
ace.send(msg)
def build_email_context(self):
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'
),
).filter(
upgrade_deadline__year=self.schedule_deadline.year,
upgrade_deadline__month=self.schedule_deadline.month,
upgrade_deadline__day=self.schedule_deadline.day,
)
if "read_replica" in settings.DATABASES:
schedules = schedules.using("read_replica")
for schedule in schedules:
enrollment = schedule.enrollment
user = enrollment.user
user_time_zone = tzutc()
for preference in user.tzprefs:
user_time_zone = gettz(preference.value)
course_id_str = str(enrollment.course_id)
course = enrollment.course
course_root = reverse('course_root', kwargs={'course_id': urlquote(course_id_str)})
def absolute_url(relative_path):
return u'{}{}'.format(settings.LMS_ROOT_URL, relative_path)
template_context = {
'user_full_name': user.profile.name,
'user_personal_address': user.profile.name if user.profile.name else user.username,
'user_username': user.username,
'user_time_zone': user_time_zone,
'user_schedule_start_time': schedule.start,
'user_schedule_verified_upgrade_deadline_time': schedule.upgrade_deadline,
'course_id': course_id_str,
'course_title': course.display_name,
'course_url': absolute_url(course_root),
'course_image_url': absolute_url(course.course_image_url),
'course_end_time': course.end,
'course_verified_upgrade_url': check_and_get_upgrade_link(course, user),
'course_verified_upgrade_price': format_course_price(course.verified_modes[0].min_price),
}
yield (user, course.language, template_context)
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--date', default=datetime.datetime.utcnow().date().isoformat())
def handle(self, *args, **options):
current_date = datetime.date(*[int(x) for x in options['date'].split('-')])
msg_t = VerifiedUpgradeDeadlineReminder()
for offset in (2, 9, 16):
target_date = current_date + datetime.timedelta(days=offset)
VerifiedDeadlineResolver(target_date).send(msg_t)
import datetime
from unittest import skipUnless
import ddt
import pytz
from django.conf import settings
from mock import patch
from openedx.core.djangoapps.schedules.management.commands import SendEmailBaseCommand
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
@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 TestSendEmailBaseCommand(CacheIsolationTestCase):
def setUp(self):
self.command = SendEmailBaseCommand()
def test_init_resolver_class(self):
assert self.command.resolver_class is None
def test_make_resolver(self):
with patch.object(self.command, 'resolver_class') as resolver_class:
example_site = SiteFactory(domain='example.com')
self.command.make_resolver(site_domain_name='example.com', date='2017-09-29')
resolver_class.assert_called_once_with(
example_site,
datetime.datetime(2017, 9, 29, tzinfo=pytz.UTC)
)
def test_handle(self):
with patch.object(self.command, 'make_resolver') as make_resolver:
make_resolver.return_value = 'resolver'
with patch.object(self.command, 'send_emails') as send_emails:
self.command.handle(date='2017-09-29')
make_resolver.assert_called_once_with(date='2017-09-29')
send_emails.assert_called_once_with('resolver', date='2017-09-29')
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('schedules', '0003_scheduleconfig'),
]
operations = [
migrations.AddField(
model_name='scheduleconfig',
name='deliver_upgrade_reminder',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='scheduleconfig',
name='enqueue_upgrade_reminder',
field=models.BooleanField(default=False),
),
]
...@@ -35,3 +35,5 @@ class ScheduleConfig(ConfigurationModel): ...@@ -35,3 +35,5 @@ class ScheduleConfig(ConfigurationModel):
create_schedules = models.BooleanField(default=False) create_schedules = models.BooleanField(default=False)
enqueue_recurring_nudge = models.BooleanField(default=False) enqueue_recurring_nudge = models.BooleanField(default=False)
deliver_recurring_nudge = models.BooleanField(default=False) deliver_recurring_nudge = models.BooleanField(default=False)
enqueue_upgrade_reminder = models.BooleanField(default=False)
deliver_upgrade_reminder = models.BooleanField(default=False)
import datetime
from edx_ace.recipient_resolver import RecipientResolver
from edx_ace.utils.date import serialize
from openedx.core.djangoapps.schedules.models import ScheduleConfig
from openedx.core.djangoapps.schedules.tasks import (
DEFAULT_NUM_BINS,
RECURRING_NUDGE_NUM_BINS,
UPGRADE_REMINDER_NUM_BINS,
recurring_nudge_schedule_bin,
upgrade_reminder_schedule_bin
)
from openedx.core.djangoapps.schedules.utils import PrefixedDebugLoggerMixin
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
"""
Starts num_bins number of async tasks, each of which sends emails to an equal group of learners.
Arguments:
site -- Site object that filtered Schedules will be a part of
current_date -- datetime that will be used (with time zeroed-out) as the current date in the queries
Static attributes:
async_send_task -- celery task function which this resolver will call out to
num_bins -- the int number of bins to split the users into
enqueue_config_var -- the string field name of the config variable on ScheduleConfig to check before enqueuing
"""
async_send_task = None # define in subclass
num_bins = DEFAULT_NUM_BINS
enqueue_config_var = None # define in subclass
def __init__(self, site, current_date, *args, **kwargs):
super(BinnedSchedulesBaseResolver, self).__init__(*args, **kwargs)
self.site = site
self.current_date = current_date.replace(hour=0, minute=0, second=0)
def send(self, day_offset, override_recipient_email=None):
if not self.is_enqueue_enabled():
self.log_debug('Message queuing disabled for site %s', self.site.domain)
return
exclude_orgs, org_list = self.get_course_org_filter()
target_date = self.current_date + datetime.timedelta(days=day_offset)
self.log_debug('Target date = %s', target_date.isoformat())
for bin in range(self.num_bins):
task_args = (
self.site.id, serialize(target_date), day_offset, bin, org_list, exclude_orgs, override_recipient_email,
)
self.log_debug('Launching task with args = %r', task_args)
self.async_send_task.apply_async(
task_args,
retry=False,
)
def is_enqueue_enabled(self):
if self.enqueue_config_var:
return getattr(ScheduleConfig.current(self.site), self.enqueue_config_var)
return False
def get_course_org_filter(self):
"""
Given the configuration of sites, get the list of orgs that should be included or excluded from this send.
Returns:
tuple: Returns a tuple (exclude_orgs, org_list). If exclude_orgs is True, then org_list is a list of the
only orgs that should be included in this send. If exclude_orgs is False, then org_list is a list of
orgs that should be excluded from this send. All other orgs should be included.
"""
try:
site_config = SiteConfiguration.objects.get(site_id=self.site.id)
org_list = site_config.get_value('course_org_filter')
exclude_orgs = False
if not org_list:
not_orgs = set()
for other_site_config in SiteConfiguration.objects.all():
other = other_site_config.get_value('course_org_filter')
if not isinstance(other, list):
if other is not None:
not_orgs.add(other)
else:
not_orgs.update(other)
org_list = list(not_orgs)
exclude_orgs = True
elif not isinstance(org_list, list):
org_list = [org_list]
except SiteConfiguration.DoesNotExist:
org_list = None
exclude_orgs = False
finally:
return exclude_orgs, org_list
class ScheduleStartResolver(BinnedSchedulesBaseResolver):
"""
Send a message to all users whose schedule started at ``self.current_date`` + ``day_offset``.
"""
async_send_task = recurring_nudge_schedule_bin
num_bins = RECURRING_NUDGE_NUM_BINS
enqueue_config_var = 'enqueue_recurring_nudge'
def __init__(self, *args, **kwargs):
super(ScheduleStartResolver, self).__init__(*args, **kwargs)
self.log_prefix = 'Scheduled Nudge'
class UpgradeReminderResolver(BinnedSchedulesBaseResolver):
"""
Send a message to all users whose verified upgrade deadline is at ``self.current_date`` + ``day_offset``.
"""
async_send_task = upgrade_reminder_schedule_bin
num_bins = UPGRADE_REMINDER_NUM_BINS
enqueue_config_var = 'enqueue_upgrade_reminder'
def __init__(self, *args, **kwargs):
super(UpgradeReminderResolver, self).__init__(*args, **kwargs)
self.log_prefix = 'Upgrade Reminder'
from urlparse import urlparse
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.http import urlquote
from edxmako.shortcuts import marketing_link
def get_base_template_context(site):
"""Dict with entries needed for all templates that use the base template"""
return {
# Platform information
'homepage_url': encode_url(marketing_link('ROOT')),
'dashboard_url': absolute_url(site, reverse('dashboard')),
'template_revision': settings.EDX_PLATFORM_REVISION,
'platform_name': settings.PLATFORM_NAME,
'contact_mailing_address': settings.CONTACT_MAILING_ADDRESS,
'social_media_urls': encode_urls_in_dict(getattr(settings, 'SOCIAL_MEDIA_FOOTER_URLS', {})),
'mobile_store_urls': encode_urls_in_dict(getattr(settings, 'MOBILE_STORE_URLS', {})),
}
def encode_url(url):
# Sailthru has a bug where URLs that contain "+" characters in their path components are misinterpreted
# when GA instrumentation is enabled. We need to percent-encode the path segments of all URLs that are
# injected into our templates to work around this issue.
parsed_url = urlparse(url)
modified_url = parsed_url._replace(path=urlquote(parsed_url.path))
return modified_url.geturl()
def absolute_url(site, relative_path):
root = site.domain.rstrip('/')
relative_path = relative_path.lstrip('/')
return encode_url(u'https://{root}/{path}'.format(root=root, path=relative_path))
def encode_urls_in_dict(mapping):
urls = {}
for key, value in mapping.iteritems():
urls[key] = encode_url(value)
return urls
{% extends 'schedules/edx_ace/common/base_body.html' %}
{% load i18n %}
{% load static %}
{% block preview_text %}
{% blocktrans trimmed %}
We hope you are enjoying learning with us so far in {{ course_name }}! A verified certificate
will allow you to highlight your new knowledge and skills. It's official, and easily shareable.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
{% endblocktrans %}
{% endblock %}
{% block content %}
<table width="100%" align="left" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
<h1>{% trans "Upgrade now" %}</h1>
<p>
{% blocktrans trimmed %}
We hope you are enjoying learning with us so far in <strong>{{ course_name }}</strong>! A
verified certificate will allow you to highlight your new knowledge and skills. It's official,
and easily shareable.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed %}
Upgrade by <strong>{{ user_schedule_upgrade_deadline_time }}</strong>.
{% endblocktrans %}
</p>
<a href="{{ course_url }}">
<img
src="{{ cert_image }}"
alt="{% blocktrans %}Example print-out of a verified certificate{% endblocktrans %}"
style="
display: block;
margin-right: auto;
margin-left: auto;
margin-top: 50px;
margin-bottom: 50px;
border-top: 1px solid lightgray;
border-bottom: 3px solid lightgray;
border-right: 3px solid lightgray;
border-left: 1px solid lightgray;
" />
</a>
<p>
<!-- email client support for style sheets is pretty spotty, so we have to inline all of these styles -->
<a
href="{{ course_url }}"
style="
color: #ffffff;
text-decoration: none;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
background-color: #005686;
border-top: 10px solid #005686;
border-bottom: 10px solid #005686;
border-right: 16px solid #005686;
border-left: 16px solid #005686;
display: inline-block;
">
<!-- old email clients require the use of the font tag :( -->
<font color="#ffffff"><b>{% trans "Upgrade now" %}</b></font>
</a>
</p>
</td>
</tr>
</table>
{% endblock %}
{% load i18n %}
{% blocktrans trimmed %}
Dear {{ user_personal_address }},
{% endblocktrans %}
{% blocktrans trimmed %}
We hope you are enjoying learning with us so far in {{ course_name }}! A verified certificate
will allow you to highlight your new knowledge and skills. It's official, and easily shareable.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
{% endblocktrans %}
{% trans "Upgrade now at" %} <{{ course_url }}>
{% load i18n %}
{% blocktrans %}Upgrade to earn a verified certificate in {{ course_name }}{% endblocktrans %}
Dear {{ user_personal_address }},
<br/>
We hope you are enjoying {{ course_title }}.
Upgrade by {{ user_schedule_verified_upgrade_deadline_time|date:"l, F dS, Y" }}
to get a shareable certificate!
<br/>
<a href="{{course_verified_upgrade_url}}">Upgrade now</a>
Dear {{ user_personal_address }},
We hope you are enjoying {{ course_title }}.
Upgrade by {{ user_schedule_verified_upgrade_deadline_time|date:"l, F dS, Y" }} to get a shareable certificate!
Upgrade now at {{course_verified_upgrade_url}}
...@@ -23,3 +23,5 @@ class ScheduleConfigFactory(factory.DjangoModelFactory): ...@@ -23,3 +23,5 @@ class ScheduleConfigFactory(factory.DjangoModelFactory):
create_schedules = True create_schedules = True
enqueue_recurring_nudge = True enqueue_recurring_nudge = True
deliver_recurring_nudge = True deliver_recurring_nudge = True
enqueue_upgrade_reminder = True
deliver_upgrade_reminder = True
import datetime
from unittest import skipUnless
import ddt
from django.conf import settings
from mock import patch
from openedx.core.djangoapps.schedules.resolvers import BinnedSchedulesBaseResolver
from openedx.core.djangoapps.schedules.tasks import DEFAULT_NUM_BINS
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
@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 TestBinnedSchedulesBaseResolver(CacheIsolationTestCase):
def setUp(self):
super(TestBinnedSchedulesBaseResolver, self).setUp()
self.site = SiteFactory.create()
self.site_config = SiteConfigurationFactory.create(site=self.site)
self.schedule_config = ScheduleConfigFactory.create(site=self.site)
def setup_resolver(self, site=None, current_date=None):
if site is None:
site = self.site
if current_date is None:
current_date = datetime.datetime.now()
resolver = BinnedSchedulesBaseResolver(self.site, current_date)
return resolver
def test_init_site(self):
resolver = self.setup_resolver()
assert resolver.site == self.site
def test_init_current_date(self):
current_time = datetime.datetime.now()
resolver = self.setup_resolver(current_date=current_time)
current_date = current_time.replace(hour=0, minute=0, second=0)
assert resolver.current_date == current_date
def test_init_async_send_task(self):
resolver = self.setup_resolver()
assert resolver.async_send_task is None
def test_init_num_bins(self):
resolver = self.setup_resolver()
assert resolver.num_bins == DEFAULT_NUM_BINS
def test_send_enqueue_disabled(self):
resolver = self.setup_resolver()
resolver.is_enqueue_enabled = lambda: False
with patch.object(resolver, 'async_send_task') as send:
with patch.object(resolver, 'log_debug') as log_debug:
resolver.send(day_offset=2)
log_debug.assert_called_once_with('Message queuing disabled for site %s', self.site.domain)
send.apply_async.assert_not_called()
@ddt.data(0, 2, -3)
def test_send_enqueue_enabled(self, day_offset):
resolver = self.setup_resolver()
resolver.is_enqueue_enabled = lambda: True
resolver.get_course_org_filter = lambda: (False, None)
with patch.object(resolver, 'async_send_task') as send:
with patch.object(resolver, 'log_debug') as log_debug:
resolver.send(day_offset=day_offset)
target_date = resolver.current_date + datetime.timedelta(day_offset)
log_debug.assert_any_call('Target date = %s', target_date.isoformat())
assert send.apply_async.call_count == DEFAULT_NUM_BINS
@ddt.data(True, False)
def test_is_enqueue_enabled(self, enabled):
resolver = self.setup_resolver()
resolver.enqueue_config_var = 'enqueue_recurring_nudge'
self.schedule_config.enqueue_recurring_nudge = enabled
self.schedule_config.save()
assert resolver.is_enqueue_enabled() == enabled
@ddt.unpack
@ddt.data(
('course1', ['course1']),
(['course1', 'course2'], ['course1', 'course2'])
)
def test_get_course_org_filter_include(self, course_org_filter, expected_org_list):
resolver = self.setup_resolver()
self.site_config.values['course_org_filter'] = course_org_filter
self.site_config.save()
exclude_orgs, org_list = resolver.get_course_org_filter()
assert not exclude_orgs
assert org_list == expected_org_list
@ddt.unpack
@ddt.data(
(None, []),
('course1', [u'course1']),
(['course1', 'course2'], [u'course1', u'course2'])
)
def test_get_course_org_filter_exclude(self, course_org_filter, expected_org_list):
resolver = self.setup_resolver()
self.other_site = SiteFactory.create()
self.other_site_config = SiteConfigurationFactory.create(
site=self.other_site,
values={'course_org_filter': course_org_filter},
)
exclude_orgs, org_list = resolver.get_course_org_filter()
assert exclude_orgs
self.assertItemsEqual(org_list, expected_org_list)
import logging
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):
super(PrefixedDebugLoggerMixin, self).__init__(*args, **kwargs)
self.log_prefix = self.__class__.__name__
def log_debug(self, message, *args, **kwargs):
LOG.debug(self.log_prefix + ': ' + message, *args, **kwargs)
...@@ -3,7 +3,7 @@ Model factories for unit testing views or models. ...@@ -3,7 +3,7 @@ Model factories for unit testing views or models.
""" """
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from factory.django import DjangoModelFactory from factory.django import DjangoModelFactory
from factory import SubFactory, Sequence, SelfAttribute from factory import SubFactory, Sequence, SelfAttribute, lazy_attribute
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
...@@ -27,6 +27,9 @@ class SiteConfigurationFactory(DjangoModelFactory): ...@@ -27,6 +27,9 @@ class SiteConfigurationFactory(DjangoModelFactory):
class Meta(object): class Meta(object):
model = SiteConfiguration model = SiteConfiguration
values = {}
enabled = True enabled = True
site = SubFactory(SiteFactory) site = SubFactory(SiteFactory)
@lazy_attribute
def values(self):
return {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment