Powering courseware deadline with schedules
This commit is contained in:
committed by
Clinton Blackburn
parent
85e4274a05
commit
986afbfa38
@@ -0,0 +1 @@
|
||||
default_app_config = 'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
|
||||
|
||||
11
openedx/core/djangoapps/schedules/apps.py
Normal file
11
openedx/core/djangoapps/schedules/apps.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class SchedulesConfig(AppConfig):
|
||||
name = 'openedx.core.djangoapps.schedules'
|
||||
verbose_name = _('Schedules')
|
||||
|
||||
def ready(self):
|
||||
# noinspection PyUnresolvedReferences
|
||||
from . import signals # pylint: disable=unused-variable
|
||||
70
openedx/core/djangoapps/schedules/signals.py
Normal file
70
openedx/core/djangoapps/schedules/signals.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from courseware.models import DynamicUpgradeDeadlineConfiguration, CourseDynamicUpgradeDeadlineConfiguration
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
|
||||
from student.models import CourseEnrollment
|
||||
from .models import Schedule
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_upgrade_deadline(enrollment):
|
||||
""" Returns the upgrade deadline for the given enrollment.
|
||||
|
||||
The deadline is determined based on the following data (in priority order):
|
||||
1. Course run-specific deadline configuration (CourseDynamicUpgradeDeadlineConfiguration)
|
||||
2. Global deadline configuration (DynamicUpgradeDeadlineConfiguration)
|
||||
3. Verified course mode expiration
|
||||
"""
|
||||
course_key = enrollment.course_id
|
||||
upgrade_deadline = None
|
||||
|
||||
try:
|
||||
verified_mode = CourseMode.verified_mode_for_course(course_key)
|
||||
if verified_mode:
|
||||
upgrade_deadline = verified_mode.expiration_datetime
|
||||
except CourseMode.DoesNotExist:
|
||||
pass
|
||||
|
||||
global_config = DynamicUpgradeDeadlineConfiguration.current()
|
||||
if global_config.enabled:
|
||||
delta = global_config.deadline_days
|
||||
|
||||
# Check if the given course has opted out of the feature
|
||||
course_config = CourseDynamicUpgradeDeadlineConfiguration.current(course_key)
|
||||
if course_config.enabled:
|
||||
if course_config.opt_out:
|
||||
return upgrade_deadline
|
||||
|
||||
delta = course_config.deadline_days
|
||||
|
||||
course_overview = CourseOverview.get_from_id(course_key)
|
||||
|
||||
# This represents the first date at which the learner can access the content. This will be the latter of
|
||||
# either the enrollment date or the course's start date.
|
||||
content_availability_date = max(enrollment.created, course_overview.start)
|
||||
cav_based_deadline = content_availability_date + datetime.timedelta(days=delta)
|
||||
|
||||
# If the deadline from above is None, make sure we have a value for comparison
|
||||
upgrade_deadline = upgrade_deadline or datetime.date.max
|
||||
|
||||
# The content availability-based deadline should never occur after the verified mode's
|
||||
# expiration date, if one is set.
|
||||
upgrade_deadline = min(upgrade_deadline, cav_based_deadline)
|
||||
|
||||
return upgrade_deadline
|
||||
|
||||
|
||||
@receiver(post_save, sender=CourseEnrollment, dispatch_uid='create_schedule_for_enrollment')
|
||||
def create_schedule(sender, **kwargs):
|
||||
if WaffleSwitchNamespace('schedules').is_enabled('enable-create-schedule-receiver') and kwargs['created']:
|
||||
enrollment = kwargs['instance']
|
||||
upgrade_deadline = _get_upgrade_deadline(enrollment)
|
||||
Schedule.objects.create(enrollment=enrollment, start=timezone.now(), upgrade_deadline=upgrade_deadline)
|
||||
24
openedx/core/djangoapps/schedules/tests/test_signals.py
Normal file
24
openedx/core/djangoapps/schedules/tests/test_signals.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from student.tests.factories import CourseEnrollmentFactory
|
||||
from ..models import Schedule
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class CreateScheduleTests(TestCase):
|
||||
def test_create_schedule(self):
|
||||
""" A schedule should be created for every new enrollment if the switch is active. """
|
||||
|
||||
SWITCH_NAME = 'enable-create-schedule-receiver'
|
||||
switch_namesapce = WaffleSwitchNamespace('schedules')
|
||||
|
||||
with switch_namesapce.override(SWITCH_NAME, True):
|
||||
enrollment = CourseEnrollmentFactory()
|
||||
self.assertIsNotNone(enrollment.schedule)
|
||||
|
||||
with switch_namesapce.override(SWITCH_NAME, False):
|
||||
enrollment = CourseEnrollmentFactory()
|
||||
with self.assertRaises(Schedule.DoesNotExist):
|
||||
enrollment.schedule
|
||||
Reference in New Issue
Block a user