-
Gabe Mulley authoredbe3dd7e4
access.py 8.69 KiB
# -*- coding: utf-8 -*-
"""
Contains code related to computing content gating course duration limits
and course access based on these limits.
"""
from datetime import timedelta
from django.utils import timezone
from django.utils.translation import get_language, ugettext as _
from student.models import CourseEnrollment
from util.date_utils import DEFAULT_SHORT_DATE_FORMAT, strftime_localized
from course_modes.models import CourseMode
from lms.djangoapps.courseware.access_response import AccessError
from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_specific_student
from openedx.core.djangoapps.catalog.utils import get_course_run_details
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
from openedx.core.djangolib.markup import HTML, Text
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
MIN_DURATION = timedelta(weeks=4)
MAX_DURATION = timedelta(weeks=12)
class AuditExpiredError(AccessError):
"""
Access denied because the user's audit timespan has expired
"""
def __init__(self, user, course, expiration_date):
error_code = "audit_expired"
developer_message = "User {} had access to {} until {}".format(user, course, expiration_date)
language = get_language()
if language and language.split('-')[0].lower() == 'es':
expiration_date = strftime_localized(expiration_date, '%-d de %b. de %Y').lower()
else:
expiration_date = strftime_localized(expiration_date, '%b. %-d, %Y')
user_message = _("Access expired on {expiration_date}").format(expiration_date=expiration_date)
try:
course_name = CourseOverview.get_from_id(course.id).display_name_with_default
additional_context_user_message = _("Access to {course_name} expired on {expiration_date}").format(
course_name=course_name,
expiration_date=expiration_date
)
except CourseOverview.DoesNotExist:
additional_context_user_message = _("Access to the course you were looking"
" for expired on {expiration_date}").format(
expiration_date=expiration_date
)
super(AuditExpiredError, self).__init__(error_code, developer_message, user_message,
additional_context_user_message)
def get_user_course_expiration_date(user, course):
"""
Return expiration date for given user course pair.
Return None if the course does not expire.
Business Logic:
- Course access duration is bounded by the min and max duration.
- If course fields are missing, default course access duration to MIN_DURATION.
"""
access_duration = MIN_DURATION
if not CourseMode.verified_mode_for_course(course.id, include_expired=True):
return None
enrollment = CourseEnrollment.get_enrollment(user, course.id)
if enrollment is None or enrollment.mode != 'audit':
return None
try:
# Content availability date is equivalent to max(enrollment date, course start date)
# for most people. Using the schedule date will provide flexibility to deal with
# more complex business rules in the future.
content_availability_date = enrollment.schedule.start
except CourseEnrollment.schedule.RelatedObjectDoesNotExist:
content_availability_date = max(enrollment.created, course.start)
# The user course expiration date is the content availability date
# plus the weeks_to_complete field from course-discovery.
discovery_course_details = get_course_run_details(course.id, ['weeks_to_complete'])
expected_weeks = discovery_course_details.get('weeks_to_complete')
if expected_weeks:
access_duration = timedelta(weeks=expected_weeks)
# Course access duration is bounded by the min and max duration.
access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))
return content_availability_date + access_duration
def check_course_expired(user, course):
"""
Check if the course expired for the user.
"""
# masquerading course staff should always have access
if get_course_masquerade(user, course.id):
return ACCESS_GRANTED
if not CourseDurationLimitConfig.enabled_for_enrollment(user=user, course_key=course.id):
return ACCESS_GRANTED
expiration_date = get_user_course_expiration_date(user, course)
if expiration_date and timezone.now() > expiration_date:
return AuditExpiredError(user, course, expiration_date)
return ACCESS_GRANTED
def register_course_expired_message(request, course):
"""
Add a banner notifying the user of the user course expiration date if it exists.
"""
if not CourseDurationLimitConfig.enabled_for_enrollment(user=request.user, course_key=course.id):
return
expiration_date = get_user_course_expiration_date(request.user, course)
if not expiration_date:
return
if is_masquerading_as_specific_student(request.user, course.id) and timezone.now() > expiration_date:
upgrade_message = _('This learner does not have access to this course. '
'Their access expired on {expiration_date}.')
PageLevelMessages.register_warning_message(
request,
HTML(upgrade_message).format(
expiration_date=strftime_localized(expiration_date, '%b. %-d, %Y')
)
)
else:
enrollment = CourseEnrollment.get_enrollment(request.user, course.id)
if enrollment is None:
return
upgrade_deadline = enrollment.upgrade_deadline
now = timezone.now()
course_upgrade_deadline = enrollment.course_upgrade_deadline
if (not upgrade_deadline) or (now > upgrade_deadline):
upgrade_deadline = course_upgrade_deadline
expiration_message = _('{strong_open}Audit Access Expires {expiration_date}{strong_close}'
'{line_break}You lose all access to this course, including your progress, on '
'{expiration_date}.')
upgrade_deadline_message = _('{line_break}Upgrade by {upgrade_deadline} to get unlimited access to the course '
'as long as it exists on the site. {a_open}Upgrade now{sronly_span_open} to '
'retain access past {expiration_date}{span_close}{a_close}')
full_message = expiration_message
if upgrade_deadline and now < upgrade_deadline:
full_message += upgrade_deadline_message
using_upgrade_messaging = True
else:
using_upgrade_messaging = False
language = get_language()
language_is_es = language and language.split('-')[0].lower() == 'es'
if language_is_es:
formatted_expiration_date = strftime_localized(expiration_date, '%-d de %b. de %Y').lower()
else:
formatted_expiration_date = strftime_localized(expiration_date, '%b. %-d, %Y')
if using_upgrade_messaging:
if language_is_es:
formatted_upgrade_deadline = strftime_localized(upgrade_deadline, '%-d de %b. de %Y').lower()
else:
formatted_upgrade_deadline = strftime_localized(upgrade_deadline, '%b. %-d, %Y')
PageLevelMessages.register_info_message(
request,
Text(full_message).format(
a_open=HTML('<a href="{upgrade_link}">').format(
upgrade_link=verified_upgrade_deadline_link(user=request.user, course=course)
),
sronly_span_open=HTML('<span class="sr-only">'),
span_close=HTML('</span>'),
a_close=HTML('</a>'),
expiration_date=formatted_expiration_date,
strong_open=HTML('<strong>'),
strong_close=HTML('</strong>'),
line_break=HTML('<br>'),
upgrade_deadline=formatted_upgrade_deadline
)
)
else:
PageLevelMessages.register_info_message(
request,
Text(full_message).format(
span_close=HTML('</span>'),
expiration_date=formatted_expiration_date,
strong_open=HTML('<strong>'),
strong_close=HTML('</strong>'),
line_break=HTML('<br>'),
)
)