diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py
index 3cf2d6609f..a9de6f056a 100644
--- a/lms/djangoapps/courseware/tests/helpers.py
+++ b/lms/djangoapps/courseware/tests/helpers.py
@@ -364,26 +364,35 @@ def get_expiration_banner_text(user, course):
upgrade_link = verified_upgrade_deadline_link(user=user, course=course)
enrollment = CourseEnrollment.get_enrollment(user, course.id)
upgrade_deadline = enrollment.upgrade_deadline
- if upgrade_deadline is None:
- return
- if now() < upgrade_deadline:
+ if upgrade_deadline is None or now() < upgrade_deadline:
upgrade_deadline = enrollment.course_upgrade_deadline
language = get_language()
- if language and language.split('-')[0].lower() == 'es':
+ 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()
- formatted_upgrade_deadline = strftime_localized(upgrade_deadline, '%-d de %b. de %Y').lower()
else:
formatted_expiration_date = strftime_localized(expiration_date, '%b. %-d, %Y')
- formatted_upgrade_deadline = strftime_localized(upgrade_deadline, '%b. %-d, %Y')
- bannerText = 'Audit Access Expires {expiration_date}
\
- You lose all access to this course, including your progress, on {expiration_date}.
\
- Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists on the site.\
- Upgrade now to retain access past {expiration_date}\
- '.format(
- expiration_date=formatted_expiration_date,
- upgrade_link=upgrade_link,
- upgrade_deadline=formatted_upgrade_deadline
- )
+ if upgrade_deadline:
+ 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')
+
+ bannerText = 'Audit Access Expires {expiration_date}
\
+ You lose all access to this course, including your progress, on {expiration_date}.\
+
Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists\
+ on the site. Upgrade now to retain access past\
+ {expiration_date}'.format(
+ expiration_date=formatted_expiration_date,
+ upgrade_link=upgrade_link,
+ upgrade_deadline=formatted_upgrade_deadline
+ )
+ else:
+ bannerText = 'Audit Access Expires {expiration_date}
\
+ You lose all access to this course, including your progress, on {expiration_date}.\
+ '.format(
+ expiration_date=formatted_expiration_date
+ )
return bannerText
diff --git a/openedx/features/content_type_gating/tests/test_access.py b/openedx/features/content_type_gating/tests/test_access.py
index 5012b0f1f4..166f94b006 100644
--- a/openedx/features/content_type_gating/tests/test_access.py
+++ b/openedx/features/content_type_gating/tests/test_access.py
@@ -35,7 +35,11 @@ from lms.djangoapps.courseware.tests.factories import (
from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory
from openedx.core.djangoapps.util.testing import TestConditionalContent
from openedx.core.lib.url_utils import quote_slashes
-from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS
+from openedx.features.content_type_gating.partitions import (
+ CONTENT_GATING_PARTITION_ID,
+ CONTENT_TYPE_GATE_GROUP_IDS,
+ ContentTypeGatingPartition
+)
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.config import (
EXPERIMENT_ID,
@@ -89,7 +93,7 @@ def _get_fragment_from_block(block, user_id, course, request_factory, mock_get_c
return frag
-def _assert_block_is_gated(block, is_gated, user_id, course, request_factory):
+def _assert_block_is_gated(block, is_gated, user_id, course, request_factory, has_upgrade_link=True):
"""
Asserts that a block in a specific course is gated for a specific user
Arguments:
@@ -98,9 +102,15 @@ def _assert_block_is_gated(block, is_gated, user_id, course, request_factory):
user_id (int): id of user
course_id (CourseLocator): id of course
"""
- frag = _get_fragment_from_block(block, user_id, course, request_factory)
+ checkout_link = '#' if has_upgrade_link else None
+ with patch.object(ContentTypeGatingPartition, '_get_checkout_link', return_value=checkout_link):
+ frag = _get_fragment_from_block(block, user_id, course, request_factory)
if is_gated:
assert 'content-paywall' in frag.content
+ if has_upgrade_link:
+ assert 'certA_1' in frag.content
+ else:
+ assert 'certA_1' not in frag.content
else:
assert 'content-paywall' not in frag.content
@@ -236,6 +246,18 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
component_types=['problem', 'html']
)
+ cls.courses['expired_upgrade_deadline'] = cls._create_course(
+ run='expired_upgrade_deadline_run_1',
+ display_name='Expired Upgrade Deadline Course Title',
+ modes=['audit'],
+ component_types=['problem', 'html']
+ )
+ CourseModeFactory.create(
+ course_id=cls.courses['expired_upgrade_deadline']['course'].scope_ids.usage_id.course_key,
+ mode_slug='verified',
+ expiration_datetime=datetime(2018, 1, 1)
+ )
+
def setUp(self):
super(TestProblemTypeAccess, self).setUp()
@@ -266,6 +288,12 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
course_id=self.courses['audit_only']['course'].id,
mode='audit'
)
+ # enroll audit user into the upgrade expired course
+ CourseEnrollmentFactory.create(
+ user=self.audit_user,
+ course_id=self.courses['expired_upgrade_deadline']['course'].id,
+ mode='audit'
+ )
ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1))
@classmethod
@@ -406,6 +434,20 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
request_factory=self.factory,
)
+ def test_access_expired_upgrade_deadline(self):
+ """
+ If a user is enrolled as an audit user and the upgrade deadline has passed
+ the user will continue to see gated content, but the upgrade messaging will be removed.
+ """
+ _assert_block_is_gated(
+ block=self.courses['default']['blocks']['problem'],
+ user_id=self.users['audit'].id,
+ course=self.courses['default']['course'],
+ is_gated=True,
+ request_factory=self.factory,
+ has_upgrade_link=False
+ )
+
@ddt.data(
('problem', 'graded_problem', 'audit', 404),
('problem', 'graded_problem', 'verified', 200),
diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py
index be08bf56eb..167ab24c66 100644
--- a/openedx/features/course_duration_limits/access.py
+++ b/openedx/features/course_duration_limits/access.py
@@ -66,7 +66,7 @@ def get_user_course_expiration_date(user, course):
access_duration = MIN_DURATION
- if not CourseMode.verified_mode_for_course(course.id):
+ if not CourseMode.verified_mode_for_course(course.id, include_expired=True):
return None
enrollment = CourseEnrollment.get_enrollment(user, course.id)
@@ -138,11 +138,9 @@ def register_course_expired_message(request, course):
return
upgrade_deadline = enrollment.upgrade_deadline
- if upgrade_deadline is None:
- return
now = timezone.now()
course_upgrade_deadline = enrollment.course_upgrade_deadline
- if now > upgrade_deadline:
+ if upgrade_deadline and now > upgrade_deadline:
upgrade_deadline = course_upgrade_deadline
expiration_message = _('{strong_open}Audit Access Expires {expiration_date}{strong_close}'
@@ -152,31 +150,47 @@ def register_course_expired_message(request, 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 now < course_upgrade_deadline:
+ if course_upgrade_deadline and now < course_upgrade_deadline:
full_message += upgrade_deadline_message
language = get_language()
- if language and language.split('-')[0].lower() == 'es':
+ 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()
- formatted_upgrade_deadline = strftime_localized(upgrade_deadline, '%-d de %b. de %Y').lower()
else:
formatted_expiration_date = strftime_localized(expiration_date, '%b. %-d, %Y')
- formatted_upgrade_deadline = strftime_localized(upgrade_deadline, '%b. %-d, %Y')
- PageLevelMessages.register_info_message(
- request,
- Text(full_message).format(
- a_open=HTML('').format(
- upgrade_link=verified_upgrade_deadline_link(user=request.user, course=course)
- ),
- sronly_span_open=HTML(''),
- sighted_only_span_open=HTML(''),
- span_close=HTML(''),
- a_close=HTML(''),
- expiration_date=formatted_expiration_date,
- strong_open=HTML(''),
- strong_close=HTML(''),
- line_break=HTML('
'),
- upgrade_deadline=formatted_upgrade_deadline
+ if upgrade_deadline:
+ 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')
+
+ if upgrade_deadline:
+ PageLevelMessages.register_info_message(
+ request,
+ Text(full_message).format(
+ a_open=HTML('').format(
+ upgrade_link=verified_upgrade_deadline_link(user=request.user, course=course)
+ ),
+ sronly_span_open=HTML(''),
+ span_close=HTML(''),
+ a_close=HTML(''),
+ expiration_date=formatted_expiration_date,
+ strong_open=HTML(''),
+ strong_close=HTML(''),
+ line_break=HTML('
'),
+ upgrade_deadline=formatted_upgrade_deadline
+ )
+ )
+ else:
+ PageLevelMessages.register_info_message(
+ request,
+ Text(full_message).format(
+ span_close=HTML(''),
+ expiration_date=formatted_expiration_date,
+ strong_open=HTML(''),
+ strong_close=HTML(''),
+ line_break=HTML('
'),
+ )
)
- )
diff --git a/openedx/features/course_duration_limits/tests/test_course_expiration.py b/openedx/features/course_duration_limits/tests/test_course_expiration.py
index c32227b0e5..77a3a79002 100644
--- a/openedx/features/course_duration_limits/tests/test_course_expiration.py
+++ b/openedx/features/course_duration_limits/tests/test_course_expiration.py
@@ -129,6 +129,22 @@ class CourseExpirationTestCase(ModuleStoreTestCase):
content_availability_date = start_date
self.assertEqual(result, content_availability_date + access_duration)
+ @mock.patch("openedx.features.course_duration_limits.access.get_course_run_details")
+ def test_expired_upgrade_deadline(self, mock_get_course_run_details):
+ """
+ The expiration date still exists if the upgrade deadline has passed
+ """
+ access_duration = timedelta(weeks=7)
+ mock_get_course_run_details.return_value = {'weeks_to_complete': 7}
+
+ start_date = now() - timedelta(weeks=10)
+ course = CourseFactory(start=start_date)
+ enrollment = CourseEnrollment.enroll(self.user, course.id, CourseMode.AUDIT)
+ add_course_mode(course, upgrade_deadline_expired=True)
+ result = get_user_course_expiration_date(self.user, course)
+ content_availability_date = enrollment.created
+ self.assertEqual(result, content_availability_date + access_duration)
+
@mock.patch("openedx.features.course_duration_limits.access.get_course_run_details")
@ddt.data(
({'user_partition_id': CONTENT_GATING_PARTITION_ID,
diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py
index 74bada9282..c17cb36311 100644
--- a/openedx/features/course_experience/tests/views/test_course_home.py
+++ b/openedx/features/course_experience/tests/views/test_course_home.py
@@ -204,7 +204,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
# Fetch the view and verify the query counts
# TODO: decrease query count as part of REVO-28
- with self.assertNumQueries(86, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
+ with self.assertNumQueries(87, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
with check_mongo_calls(4):
url = course_home_url(self.course)
self.client.get(url)
@@ -538,6 +538,27 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
)
self.assertRedirects(response, expected_url)
+ @mock.patch.dict(settings.FEATURES, {'DISABLE_START_DATES': False})
+ def test_expiration_banner_with_expired_upgrade_deadline(self):
+ """
+ Ensure that a user accessing a course with an expired upgrade deadline
+ will still see the course expiration banner without the upgrade related text.
+ """
+ past = datetime(2010, 1, 1)
+ CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=past)
+ course = CourseFactory.create(start=now() - timedelta(days=10))
+ CourseModeFactory.create(course_id=course.id, mode_slug=CourseMode.AUDIT)
+ CourseModeFactory.create(course_id=course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=past)
+ user = UserFactory(password=self.TEST_PASSWORD)
+ self.client.login(username=user.username, password=self.TEST_PASSWORD)
+ CourseEnrollment.enroll(user, course.id, mode=CourseMode.AUDIT)
+
+ url = course_home_url(course)
+ response = self.client.get(url)
+ bannerText = get_expiration_banner_text(user, course)
+ self.assertContains(response, bannerText, html=True)
+ self.assertContains(response, TEST_BANNER_CLASS)
+
def test_audit_only_not_expired(self):
"""
Verify that enrolled users are NOT shown the course expiration banner and can