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:
@@ -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
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user