Removed unused imports from openedx/core/djangoapps/{dark_lang, django_comment_common, embargo, lang_pref, oauth_dispatch, plugins, programs, schedules}
179 lines
7.4 KiB
Python
179 lines
7.4 KiB
Python
"""
|
|
CourseEnrollment related signal handlers.
|
|
"""
|
|
|
|
import datetime
|
|
import logging
|
|
|
|
from django.db.models.signals import post_save
|
|
from django.dispatch import receiver
|
|
from edx_ace.utils import date
|
|
|
|
from common.djangoapps.course_modes.models import CourseMode
|
|
from lms.djangoapps.courseware.models import (
|
|
CourseDynamicUpgradeDeadlineConfiguration,
|
|
DynamicUpgradeDeadlineConfiguration,
|
|
OrgDynamicUpgradeDeadlineConfiguration
|
|
)
|
|
from openedx.core.djangoapps.content.course_overviews.signals import COURSE_START_DATE_CHANGED
|
|
from openedx.core.djangoapps.schedules.content_highlights import course_has_highlights_from_store
|
|
from openedx.core.djangoapps.schedules.models import ScheduleExperience
|
|
from openedx.core.djangoapps.schedules.utils import reset_self_paced_schedule
|
|
from common.djangoapps.student.models import CourseEnrollment
|
|
from common.djangoapps.student.signals import ENROLLMENT_TRACK_UPDATED # lint-amnesty, pylint: disable=unused-import
|
|
from .models import Schedule
|
|
from .tasks import update_course_schedules
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@receiver(post_save, sender=CourseEnrollment, dispatch_uid='create_schedule_for_enrollment')
|
|
def create_schedule(sender, **kwargs): # pylint: disable=unused-argument
|
|
"""
|
|
When a CourseEnrollment is created, create a Schedule if configured.
|
|
"""
|
|
try:
|
|
enrollment = kwargs.get('instance')
|
|
enrollment_created = kwargs.get('created')
|
|
|
|
schedule_details = _create_schedule(enrollment, enrollment_created)
|
|
|
|
if schedule_details:
|
|
log.debug( # lint-amnesty, pylint: disable=logging-not-lazy
|
|
'Schedules: created a new schedule starting at ' +
|
|
'%s with an upgrade deadline of %s and experience type: %s',
|
|
schedule_details['content_availability_date'],
|
|
schedule_details['upgrade_deadline'],
|
|
ScheduleExperience.EXPERIENCES[schedule_details['experience_type']]
|
|
)
|
|
except Exception: # pylint: disable=broad-except
|
|
# We do not want to block the creation of a CourseEnrollment because of an error in creating a Schedule.
|
|
# No Schedule is acceptable, but no CourseEnrollment is not.
|
|
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,
|
|
enrollment.course_id if enrollment else None
|
|
))
|
|
|
|
|
|
@receiver(COURSE_START_DATE_CHANGED)
|
|
def update_schedules_on_course_start_changed(sender, updated_course_overview, previous_start_date, **kwargs): # pylint: disable=unused-argument
|
|
"""
|
|
Updates all course schedules start and upgrade_deadline dates based off of
|
|
the new course overview start date.
|
|
"""
|
|
upgrade_deadline = _calculate_upgrade_deadline(
|
|
updated_course_overview.id,
|
|
content_availability_date=updated_course_overview.start,
|
|
)
|
|
update_course_schedules.apply_async(
|
|
kwargs=dict(
|
|
course_id=str(updated_course_overview.id),
|
|
new_start_date_str=date.serialize(updated_course_overview.start),
|
|
new_upgrade_deadline_str=date.serialize(upgrade_deadline),
|
|
),
|
|
)
|
|
|
|
|
|
@receiver(ENROLLMENT_TRACK_UPDATED)
|
|
def reset_schedule_on_mode_change(sender, user, course_key, mode, **kwargs): # pylint: disable=unused-argument
|
|
"""
|
|
When a CourseEnrollment's mode is changed, reset the user's schedule if self-paced.
|
|
"""
|
|
# If switching to audit, reset to when the user got access to course. This is for the case where a user
|
|
# upgrades to verified (resetting their date), then later refunds the order and goes back to audit. We want
|
|
# to make sure that audit users go back to their normal audit schedule access.
|
|
use_availability_date = mode in CourseMode.AUDIT_MODES
|
|
reset_self_paced_schedule(user, course_key, use_availability_date=use_availability_date)
|
|
|
|
|
|
def _calculate_upgrade_deadline(course_id, content_availability_date): # lint-amnesty, pylint: disable=missing-function-docstring
|
|
upgrade_deadline = None
|
|
|
|
delta = _get_upgrade_deadline_delta_setting(course_id)
|
|
if delta is not None:
|
|
upgrade_deadline = content_availability_date + datetime.timedelta(days=delta)
|
|
if upgrade_deadline is not None:
|
|
# The content availability-based deadline should never occur
|
|
# after the verified mode's expiration date, if one is set.
|
|
try:
|
|
verified_mode = CourseMode.verified_mode_for_course(course_id)
|
|
except CourseMode.DoesNotExist:
|
|
pass
|
|
else:
|
|
if verified_mode:
|
|
course_mode_upgrade_deadline = verified_mode.expiration_datetime
|
|
if course_mode_upgrade_deadline is not None:
|
|
upgrade_deadline = min(upgrade_deadline, course_mode_upgrade_deadline)
|
|
|
|
return upgrade_deadline
|
|
|
|
|
|
def _get_upgrade_deadline_delta_setting(course_id): # lint-amnesty, pylint: disable=missing-function-docstring
|
|
delta = None
|
|
|
|
global_config = DynamicUpgradeDeadlineConfiguration.current()
|
|
if global_config.enabled:
|
|
# Use the default from this model whether or not the feature is enabled
|
|
delta = global_config.deadline_days
|
|
|
|
# Check if the org has a deadline
|
|
org_config = OrgDynamicUpgradeDeadlineConfiguration.current(course_id.org)
|
|
if org_config.opted_in():
|
|
delta = org_config.deadline_days
|
|
elif org_config.opted_out():
|
|
delta = None
|
|
|
|
# Check if the course has a deadline
|
|
course_config = CourseDynamicUpgradeDeadlineConfiguration.current(course_id)
|
|
if course_config.opted_in():
|
|
delta = course_config.deadline_days
|
|
elif course_config.opted_out():
|
|
delta = None
|
|
|
|
return delta
|
|
|
|
|
|
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
|
|
|
|
# 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)
|
|
|
|
schedule = Schedule.objects.create(
|
|
enrollment=enrollment,
|
|
start_date=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.
|
|
"""
|
|
has_highlights = enrollment.course_overview.has_highlights
|
|
if has_highlights is None: # old course that doesn't have this info cached in the overview
|
|
has_highlights = course_has_highlights_from_store(enrollment.course_id)
|
|
|
|
if has_highlights:
|
|
return ScheduleExperience.EXPERIENCES.course_updates
|
|
else:
|
|
return ScheduleExperience.EXPERIENCES.default
|