feat: Update the course end date block display logic

For self-paced courses, we have decided to switch to showing the end
date as long as it is within 365 days rather than the expected
duration of the course.
This commit is contained in:
Dillon Dumesnil
2021-05-18 16:31:05 -04:00
parent 7c211c108e
commit 8aeb460133
3 changed files with 48 additions and 43 deletions

View File

@@ -24,7 +24,6 @@ from lms.djangoapps.certificates.api import get_active_web_certificate
from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, can_show_verified_upgrade
from lms.djangoapps.verify_student.models import VerificationDeadline
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.catalog.utils import get_course_run_details
from openedx.core.djangoapps.certificates.api import can_show_certificate_available_date_field
from openedx.core.djangolib.markup import HTML, Text
from openedx.features.course_duration_limits.access import get_user_course_expiration_date
@@ -280,7 +279,7 @@ class CourseStartDate(DateSummary):
enrollment = CourseEnrollment.get_enrollment(self.user, self.course_id)
if enrollment and self.course.end and enrollment.created > self.course.end:
return ugettext_lazy('Enrollment Date')
return ugettext_lazy('Course Starts')
return ugettext_lazy('Course starts')
def register_alerts(self, request, course):
"""
@@ -317,28 +316,44 @@ class CourseEndDate(DateSummary):
Displays the end date of the course.
"""
css_class = 'end-date'
title = ugettext_lazy('Course End')
title = ugettext_lazy('Course ends')
is_enabled = True
@property
def description(self):
if self.current_time <= self.date:
"""
Returns a description for what experience changes a learner encounters when the course end date passes.
Note that this currently contains 4 scenarios:
1. End date is in the future and learner is enrolled in a certificate earning mode
2. End date is in the future and learner is not enrolled at all or not enrolled
in a certificate earning mode
3. End date is in the past
4. End date does not exist (and now neither does the description)
"""
if self.date and self.current_time <= self.date:
mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_id)
if is_active and CourseMode.is_eligible_for_certificate(mode):
return _('To earn a certificate, you must complete all requirements before this date.')
return _('This course will be archived, which means you can review the course content '
'but can no longer participate in graded assignments or earn a certificate.')
else:
return _('After this date, course content will be archived.')
return _('This course is archived, which means you can review course content but it is no longer active.')
return _('After the course ends, the course content will be archived and no longer active.')
elif self.date:
return _('This course is archived, which means you can review course content but it is no longer active.')
else:
return ''
@property
def date(self):
"""
Returns the course end date, if applicable.
For self-paced courses using Personalized Learner Schedules, the end date is only displayed
if it is within 365 days.
"""
if self.course.self_paced and RELATIVE_DATES_FLAG.is_enabled(self.course_id):
weeks_to_complete = get_course_run_details(self.course.id, ['weeks_to_complete']).get('weeks_to_complete')
if weeks_to_complete:
course_duration = datetime.timedelta(weeks=weeks_to_complete)
if self.course.end and self.course.end < (self.current_time + course_duration):
return self.course.end
return None
one_year = datetime.timedelta(days=365)
if self.course.end and self.course.end < (self.current_time + one_year):
return self.course.end
return None
return self.course.end

View File

@@ -4,7 +4,6 @@
from datetime import datetime, timedelta
from unittest.mock import patch
import crum
import ddt
import waffle # lint-amnesty, pylint: disable=invalid-django-waffle-import
@@ -12,11 +11,11 @@ from django.contrib.messages.middleware import MessageMiddleware
from django.test import RequestFactory
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_flag
from freezegun import freeze_time
from pytz import utc
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
from freezegun import freeze_time # lint-amnesty, pylint: disable=wrong-import-order
from lms.djangoapps.commerce.models import CommerceConfiguration
from lms.djangoapps.course_home_api.toggles import COURSE_HOME_MICROFRONTEND, COURSE_HOME_MICROFRONTEND_DATES_TAB
from lms.djangoapps.courseware.courses import get_course_date_blocks
@@ -42,7 +41,6 @@ from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVer
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory # pylint: disable=unused-import
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from openedx.features.course_experience import (
@@ -114,7 +112,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
{'verification_status': 'expired'},
(TodaysDate, CourseEndDate, VerificationDeadlineDate)),
# Verified enrollment with `approved` photo-verification during course run
({'days_till_start': -10, },
({'days_till_start': -10},
{'verification_status': 'approved'},
(TodaysDate, CourseEndDate)),
# Verified enrollment with *NO* course end date
@@ -522,15 +520,16 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
user = create_user()
CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED)
block = CourseEndDate(course, user)
assert block.description == 'To earn a certificate, you must complete all requirements before this date.'
assert block.description == ('This course will be archived, which means you can review the course content '
'but can no longer participate in graded assignments or earn a certificate.')
def test_course_end_date_for_non_certificate_eligible_mode(self):
course = create_course_run(days_till_start=-1)
user = create_user()
CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.AUDIT)
block = CourseEndDate(course, user)
assert block.description == 'After this date, course content will be archived.'
assert block.title == 'Course End'
assert block.description == 'After the course ends, the course content will be archived and no longer active.'
assert block.title == 'Course ends'
def test_course_end_date_after_course(self):
course = create_course_run(days_till_start=-2, days_till_end=-1)
@@ -539,35 +538,26 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
block = CourseEndDate(course, user)
assert block.description ==\
'This course is archived, which means you can review course content but it is no longer active.'
assert block.title == 'Course End'
assert block.title == 'Course ends'
@ddt.data(
{'weeks_to_complete': 7}, # Weeks to complete > time til end (end date shown)
{'weeks_to_complete': 4}, # Weeks to complete < time til end (end date not shown)
)
@ddt.data(300, 400)
@override_waffle_flag(RELATIVE_DATES_FLAG, active=True)
def test_course_end_date_self_paced(self, cr_details):
def test_course_end_date_self_paced(self, days_till_end):
"""
In self-paced courses, the end date will now only show up if the learner
views the course within the course's weeks to complete (as defined in
the course-discovery service). E.g. if the weeks to complete is 5 weeks
and the course doesn't end for 10 weeks, there will be no end date, but
if the course ends in 3 weeks, the end date will appear.
In self-paced courses, the end date will only show up if the learner
views the course within 365 days of the course end date.
"""
now = datetime.now(utc)
end_timedelta_number = 5
course = CourseFactory.create(
start=now + timedelta(days=-7), end=now + timedelta(weeks=end_timedelta_number), self_paced=True)
start=now + timedelta(days=-7), end=now + timedelta(days=days_till_end), self_paced=True)
user = create_user()
self.make_request(user)
with patch('lms.djangoapps.courseware.date_summary.get_course_run_details') as mock_get_cr_details:
mock_get_cr_details.return_value = cr_details
block = CourseEndDate(course, user)
assert block.title == 'Course End'
if cr_details['weeks_to_complete'] > end_timedelta_number:
assert block.date == course.end
else:
assert block.date is None
block = CourseEndDate(course, user)
assert block.title == 'Course ends'
if 365 > days_till_end:
assert block.date == course.end
else:
assert block.date is None
assert block.description == ''
def test_ecommerce_checkout_redirect(self):
"""Verify the block link redirects to ecommerce checkout if it's enabled."""

View File

@@ -41,7 +41,7 @@ class TestCourseDatesFragmentView(ModuleStoreTestCase):
def test_course_dates_fragment(self):
response = self.client.get(self.dates_fragment_url)
self.assertContains(response, 'Course End')
self.assertContains(response, 'Course ends')
self.client.logout()
response = self.client.get(self.dates_fragment_url)