Unverified Commit f9623fc9 by Tyler Hallada Committed by GitHub

Merge pull request #16796 from edx/thallada/schedules-hold-back-fix

Schedules: Fix hold back error
parents 254aa84f 95bb6e3d
...@@ -27,59 +27,31 @@ log = logging.getLogger(__name__) ...@@ -27,59 +27,31 @@ log = logging.getLogger(__name__)
@receiver(post_save, sender=CourseEnrollment, dispatch_uid='create_schedule_for_enrollment') @receiver(post_save, sender=CourseEnrollment, dispatch_uid='create_schedule_for_enrollment')
def create_schedule(sender, **kwargs): def create_schedule(sender, **kwargs): # pylint: disable=unused-argument
if not kwargs['created']: """
# only create schedules when enrollment records are created When a CourseEnrollment is created, create a Schedule if configured.
return """
try:
current_site = get_current_site() enrollment = kwargs.get('instance')
if current_site is None: enrollment_created = kwargs.get('created')
log.debug('Schedules: No current site')
return schedule_details = _create_schedule(enrollment, enrollment_created)
enrollment = kwargs['instance'] if schedule_details:
schedule_config = ScheduleConfig.current(current_site) log.debug(
if ( 'Schedules: created a new schedule starting at ' +
not schedule_config.create_schedules '%s with an upgrade deadline of %s and experience type: %s',
and not CREATE_SCHEDULE_WAFFLE_FLAG.is_enabled(enrollment.course_id) schedule_details['content_availability_date'],
): schedule_details['upgrade_deadline'],
log.debug('Schedules: Creation not enabled for this course or for this site') ScheduleExperience.EXPERIENCES[schedule_details['experience_type']]
return )
except Exception: # pylint: disable=broad-except
if not enrollment.course_overview.self_paced: # We do not want to block the creation of a CourseEnrollment because of an error in creating a Schedule.
log.debug('Schedules: Creation only enabled for self-paced courses') # No Schedule is acceptable, but no CourseEnrollment is not.
return log.exception('Encountered error in creating a Schedule for CourseEnrollment for user {} in course {}'.format(
enrollment.user.id if (enrollment and enrollment.user) else None,
# This represents the first date at which the learner can access the content. This will be the latter of enrollment.course_id if enrollment else None
# either the enrollment date or the course's start date. ))
content_availability_date = max(enrollment.created, enrollment.course_overview.start)
upgrade_deadline = _calculate_upgrade_deadline(enrollment.course_id, content_availability_date)
if course_has_highlights(enrollment.course_id):
experience_type = ScheduleExperience.EXPERIENCES.course_updates
else:
experience_type = ScheduleExperience.EXPERIENCES.default
if _should_randomly_suppress_schedule_creation(
schedule_config,
enrollment,
upgrade_deadline,
experience_type,
content_availability_date,
):
return
schedule = Schedule.objects.create(
enrollment=enrollment,
start=content_availability_date,
upgrade_deadline=upgrade_deadline
)
ScheduleExperience(schedule=schedule, experience_type=experience_type).save()
log.debug('Schedules: created a new schedule starting at %s with an upgrade deadline of %s and experience type: %s',
content_availability_date, upgrade_deadline, ScheduleExperience.EXPERIENCES[experience_type])
@receiver(COURSE_START_DATE_CHANGED, dispatch_uid="update_schedules_on_course_start_changed") @receiver(COURSE_START_DATE_CHANGED, dispatch_uid="update_schedules_on_course_start_changed")
...@@ -167,9 +139,9 @@ def _should_randomly_suppress_schedule_creation( ...@@ -167,9 +139,9 @@ def _should_randomly_suppress_schedule_creation(
if upgrade_deadline: if upgrade_deadline:
upgrade_deadline_str = upgrade_deadline.isoformat() upgrade_deadline_str = upgrade_deadline.isoformat()
analytics.track( analytics.track(
'edx.bi.schedule.suppressed', user_id=enrollment.user.id,
{ event='edx.bi.schedule.suppressed',
'user_id': enrollment.user.id, properties={
'course_id': unicode(enrollment.course_id), 'course_id': unicode(enrollment.course_id),
'experience_type': experience_type, 'experience_type': experience_type,
'upgrade_deadline': upgrade_deadline_str, 'upgrade_deadline': upgrade_deadline_str,
...@@ -179,3 +151,70 @@ def _should_randomly_suppress_schedule_creation( ...@@ -179,3 +151,70 @@ def _should_randomly_suppress_schedule_creation(
return True return True
return False return False
def _create_schedule(enrollment, enrollment_created):
"""
Checks configuration and creates Schedule with ScheduleExperience for this enrollment.
"""
if not enrollment_created:
# only create schedules when enrollment records are created
return
current_site = get_current_site()
if current_site is None:
log.debug('Schedules: No current site')
return
schedule_config = ScheduleConfig.current(current_site)
if (
not schedule_config.create_schedules and
not CREATE_SCHEDULE_WAFFLE_FLAG.is_enabled(enrollment.course_id)
):
log.debug('Schedules: Creation not enabled for this course or for this site')
return
if not enrollment.course_overview.self_paced:
log.debug('Schedules: Creation only enabled for self-paced courses')
return
# 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, enrollment.course_overview.start)
upgrade_deadline = _calculate_upgrade_deadline(enrollment.course_id, content_availability_date)
experience_type = _get_experience_type(enrollment)
if _should_randomly_suppress_schedule_creation(
schedule_config,
enrollment,
upgrade_deadline,
experience_type,
content_availability_date,
):
return
schedule = Schedule.objects.create(
enrollment=enrollment,
start=content_availability_date,
upgrade_deadline=upgrade_deadline
)
ScheduleExperience(schedule=schedule, experience_type=experience_type).save()
return {
'content_availability_date': content_availability_date,
'upgrade_deadline': upgrade_deadline,
'experience_type': experience_type,
}
def _get_experience_type(enrollment):
"""
Returns the ScheduleExperience type for the given enrollment.
Schedules will receive the Course Updates experience if the course has any section highlights defined.
"""
if course_has_highlights(enrollment.course_id):
return ScheduleExperience.EXPERIENCES.course_updates
else:
return ScheduleExperience.EXPERIENCES.default
import datetime import datetime
import ddt import ddt
import pytest
from mock import patch from mock import patch
from pytz import utc from pytz import utc
...@@ -31,9 +32,9 @@ class CreateScheduleTests(SharedModuleStoreTestCase): ...@@ -31,9 +32,9 @@ class CreateScheduleTests(SharedModuleStoreTestCase):
course_id=course.id, course_id=course.id,
mode=CourseMode.AUDIT, mode=CourseMode.AUDIT,
) )
self.assertIsNotNone(enrollment.schedule) assert enrollment.schedule is not None
self.assertIsNone(enrollment.schedule.upgrade_deadline) assert enrollment.schedule.upgrade_deadline is None
self.assertEquals(enrollment.schedule.experience.experience_type, experience_type) assert enrollment.schedule.experience.experience_type == experience_type
def assert_schedule_not_created(self): def assert_schedule_not_created(self):
course = _create_course_run(self_paced=True) course = _create_course_run(self_paced=True)
...@@ -41,7 +42,7 @@ class CreateScheduleTests(SharedModuleStoreTestCase): ...@@ -41,7 +42,7 @@ class CreateScheduleTests(SharedModuleStoreTestCase):
course_id=course.id, course_id=course.id,
mode=CourseMode.AUDIT, mode=CourseMode.AUDIT,
) )
with self.assertRaises(Schedule.DoesNotExist): with pytest.raises(Schedule.DoesNotExist, message="Expecting Schedule to not exist"):
enrollment.schedule enrollment.schedule
@override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True)
...@@ -84,7 +85,7 @@ class CreateScheduleTests(SharedModuleStoreTestCase): ...@@ -84,7 +85,7 @@ class CreateScheduleTests(SharedModuleStoreTestCase):
ScheduleConfigFactory.create(site=site, enabled=True, create_schedules=True) ScheduleConfigFactory.create(site=site, enabled=True, create_schedules=True)
course = _create_course_run(self_paced=False) course = _create_course_run(self_paced=False)
enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT) enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT)
with self.assertRaises(Schedule.DoesNotExist): with pytest.raises(Schedule.DoesNotExist, message="Expecting Schedule to not exist"):
enrollment.schedule enrollment.schedule
@override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True)
...@@ -117,10 +118,22 @@ class CreateScheduleTests(SharedModuleStoreTestCase): ...@@ -117,10 +118,22 @@ class CreateScheduleTests(SharedModuleStoreTestCase):
mock_get_current_site.return_value = schedule_config.site mock_get_current_site.return_value = schedule_config.site
if expect_schedule_created: if expect_schedule_created:
self.assert_schedule_created() self.assert_schedule_created()
self.assertFalse(mock_track.called) assert not mock_track.called
else: else:
self.assert_schedule_not_created() self.assert_schedule_not_created()
mock_track.assert_called_once() mock_track.assert_called_once()
assert mock_track.call_args[1].get('event') == 'edx.bi.schedule.suppressed'
@patch('openedx.core.djangoapps.schedules.signals.log.exception')
@patch('openedx.core.djangoapps.schedules.signals.Schedule.objects.create')
def test_create_schedule_error(self, mock_create_schedule, mock_log, mock_get_current_site):
site = SiteFactory.create()
mock_get_current_site.return_value = site
ScheduleConfigFactory.create(site=site)
mock_create_schedule.side_effect = ValueError('Fake error')
self.assert_schedule_not_created()
mock_log.assert_called_once()
assert 'Encountered error in creating a Schedule for CourseEnrollment' in mock_log.call_args[0][0]
@ddt.ddt @ddt.ddt
...@@ -137,9 +150,9 @@ class UpdateScheduleTests(SharedModuleStoreTestCase): ...@@ -137,9 +150,9 @@ class UpdateScheduleTests(SharedModuleStoreTestCase):
DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True, deadline_days=self.VERIFICATION_DEADLINE_DAYS) DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True, deadline_days=self.VERIFICATION_DEADLINE_DAYS)
def assert_schedule_dates(self, schedule, expected_start): def assert_schedule_dates(self, schedule, expected_start):
self.assertEquals(_strip_secs(schedule.start), _strip_secs(expected_start)) assert _strip_secs(schedule.start) == _strip_secs(expected_start)
self.assertEquals( assert (
_strip_secs(schedule.upgrade_deadline), _strip_secs(schedule.upgrade_deadline) ==
_strip_secs(expected_start) + datetime.timedelta(days=self.VERIFICATION_DEADLINE_DAYS), _strip_secs(expected_start) + datetime.timedelta(days=self.VERIFICATION_DEADLINE_DAYS),
) )
......
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