${_("Course Progress for Student '{username}' ({email})").format(username=username, email=student.email)}
-
+ % if course_expiration_fragment:
+ ${HTML(course_expiration_fragment.content)}
+ % endif
<%block name="certificate_block">
diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py
index 64a9e65108..5bee1a6929 100644
--- a/openedx/features/course_duration_limits/access.py
+++ b/openedx/features/course_duration_limits/access.py
@@ -9,7 +9,7 @@ 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 util.date_utils import strftime_localized
from course_modes.models import CourseMode
from lms.djangoapps.courseware.access_response import AccessError
@@ -18,9 +18,9 @@ from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_lin
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
+from web_fragments.fragment import Fragment
MIN_DURATION = timedelta(weeks=4)
MAX_DURATION = timedelta(weeks=12)
@@ -112,28 +112,25 @@ def check_course_expired(user, course):
return ACCESS_GRANTED
-def register_course_expired_message(request, course):
+def generate_course_expired_message(user, course):
"""
- Add a banner notifying the user of the user course expiration date if it exists.
+ Generate the message for the user course expiration date if it exists.
"""
- if not CourseDurationLimitConfig.enabled_for_enrollment(user=request.user, course_key=course.id):
+ if not CourseDurationLimitConfig.enabled_for_enrollment(user=user, course_key=course.id):
return
- expiration_date = get_user_course_expiration_date(request.user, course)
+ expiration_date = get_user_course_expiration_date(user, course)
if not expiration_date:
return
- if is_masquerading_as_specific_student(request.user, course.id) and timezone.now() > expiration_date:
+ if is_masquerading_as_specific_student(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')
- )
+ return HTML(upgrade_message).format(
+ expiration_date=strftime_localized(expiration_date, '%b. %-d, %Y')
)
else:
- enrollment = CourseEnrollment.get_enrollment(request.user, course.id)
+ enrollment = CourseEnrollment.get_enrollment(user, course.id)
if enrollment is None:
return
@@ -169,30 +166,58 @@ def register_course_expired_message(request, course):
else:
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(''),
- 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
- )
+ return HTML(full_message).format(
+ a_open=HTML('
').format(
+ upgrade_link=verified_upgrade_deadline_link(user=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('
'),
- )
+ return HTML(full_message).format(
+ span_close=HTML(''),
+ expiration_date=formatted_expiration_date,
+ strong_open=HTML('
'),
+ strong_close=HTML(''),
+ line_break=HTML('
'),
)
+
+
+def generate_course_expired_fragment(user, course):
+ message = generate_course_expired_message(user, course)
+ if message:
+ return Fragment(HTML(u"""\
+
{}
+ """).format(message))
+
+
+def course_expiration_wrapper(user, block, view, frag, context): # pylint: disable=W0613
+ """
+ An XBlock wrapper that prepends a message to the beginning of a vertical if
+ a user's course is about to expire.
+ """
+ if block.category != "vertical":
+ return frag
+
+ course = CourseOverview.get_from_id(block.course_id)
+ course_expiration_fragment = generate_course_expired_fragment(user, course)
+
+ if not course_expiration_fragment:
+ return frag
+
+ # Course content must be escaped to render correctly due to the way the
+ # way the XBlock rendering works. Transforming the safe markup to unicode
+ # escapes correctly.
+ course_expiration_fragment.content = unicode(course_expiration_fragment.content)
+
+ course_expiration_fragment.add_content(frag.content)
+ course_expiration_fragment.add_fragment_resources(frag)
+
+ return course_expiration_fragment
diff --git a/openedx/features/course_duration_limits/tests/test_access.py b/openedx/features/course_duration_limits/tests/test_access.py
index a57833ce71..5ae99f30d4 100644
--- a/openedx/features/course_duration_limits/tests/test_access.py
+++ b/openedx/features/course_duration_limits/tests/test_access.py
@@ -12,7 +12,7 @@ from mock import patch
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from openedx.features.course_duration_limits.access import (
- register_course_expired_message,
+ generate_course_expired_message,
get_user_course_expiration_date,
)
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
@@ -31,7 +31,6 @@ class TestAccess(CacheIsolationTestCase):
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=UTC))
DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
- @patch('openedx.features.course_duration_limits.access.PageLevelMessages')
@ddt.data(
*itertools.product(
['en-us', 'es-419'],
@@ -39,7 +38,7 @@ class TestAccess(CacheIsolationTestCase):
)
)
@ddt.unpack
- def test_register_course_expired_message(self, language, offsets, mock_messages):
+ def test_generate_course_expired_message(self, language, offsets):
now = timezone.now()
schedule_offset, course_offset = offsets
@@ -78,19 +77,11 @@ class TestAccess(CacheIsolationTestCase):
enrollment=enrollment,
upgrade_deadline=schedule_upgrade_deadline,
)
- request = RequestFactory().get('/courseware')
- request.user = enrollment.user
duration_limit_upgrade_deadline = get_user_course_expiration_date(enrollment.user, enrollment.course)
self.assertIsNotNone(duration_limit_upgrade_deadline)
- register_course_expired_message(request, enrollment.course)
- self.assertEqual(
- mock_messages.register_info_message.call_count,
- 1
- )
-
- message = str(mock_messages.register_info_message.call_args[0][1])
+ message = generate_course_expired_message(enrollment.user, enrollment.course)
self.assertIn(format_date(duration_limit_upgrade_deadline), message)
diff --git a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html
index e99d9f7d78..c6dee98b27 100644
--- a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html
+++ b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html
@@ -81,6 +81,9 @@ from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL
+ % if course_expiration_fragment:
+ ${HTML(course_expiration_fragment.content)}
+ % endif
% if course_home_message_fragment:
${HTML(course_home_message_fragment.body_html())}
% endif
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 c17cb36311..048ebcf8c4 100644
--- a/openedx/features/course_experience/tests/views/test_course_home.py
+++ b/openedx/features/course_experience/tests/views/test_course_home.py
@@ -64,7 +64,7 @@ TEST_PASSWORD = 'test'
TEST_CHAPTER_NAME = 'Test Chapter'
TEST_COURSE_TOOLS = 'Course Tools'
TEST_COURSE_TODAY = 'Today is'
-TEST_BANNER_CLASS = '
'
+TEST_BANNER_CLASS = '
'
TEST_WELCOME_MESSAGE = '
Welcome!
'
TEST_UPDATE_MESSAGE = 'Test Update!
'
TEST_COURSE_UPDATES_TOOL = '/course/updates">'
@@ -688,7 +688,6 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
response = self.client.get(url)
bannerText = get_expiration_banner_text(user, self.course)
self.assertContains(response, bannerText, html=True)
- self.assertContains(response, TEST_BANNER_CLASS)
# Verify that enrolled users are not shown the course expiration banner if content gating is disabled
config.enabled = False
diff --git a/openedx/features/course_experience/tests/views/test_course_updates.py b/openedx/features/course_experience/tests/views/test_course_updates.py
index 51defb96ef..e48984e821 100644
--- a/openedx/features/course_experience/tests/views/test_course_updates.py
+++ b/openedx/features/course_experience/tests/views/test_course_updates.py
@@ -130,7 +130,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
# Fetch the view and verify that the query counts haven't changed
# TODO: decrease query count as part of REVO-28
- with self.assertNumQueries(56, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
+ with self.assertNumQueries(53, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
with check_mongo_calls(4):
url = course_updates_url(self.course)
self.client.get(url)
diff --git a/openedx/features/course_experience/views/course_home.py b/openedx/features/course_experience/views/course_home.py
index a56679de8d..5c1449c3f8 100644
--- a/openedx/features/course_experience/views/course_home.py
+++ b/openedx/features/course_experience/views/course_home.py
@@ -26,6 +26,7 @@ from lms.djangoapps.courseware.views.views import CourseTabView
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.core.djangoapps.util.maintenance_banner import add_maintenance_banner
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
+from openedx.features.course_duration_limits.access import generate_course_expired_fragment
from student.models import CourseEnrollment
from util.views import ensure_valid_course_key
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC_OUTLINE, COURSE_VISIBILITY_PUBLIC
@@ -132,6 +133,7 @@ class CourseHomeFragmentView(EdxFragmentView):
outline_fragment = None
update_message_fragment = None
course_sock_fragment = None
+ course_expiration_fragment = None
has_visited_course = None
resume_course_url = None
handouts_html = None
@@ -151,6 +153,7 @@ class CourseHomeFragmentView(EdxFragmentView):
course_sock_fragment = CourseSockFragmentView().render_to_fragment(request, course=course, **kwargs)
has_visited_course, resume_course_url = self._get_resume_course_info(request, course_id)
handouts_html = self._get_course_handouts(request, course)
+ course_expiration_fragment = generate_course_expired_fragment(request.user, course)
elif allow_public_outline or allow_public:
outline_fragment = CourseOutlineFragmentView().render_to_fragment(
request, course_id=course_id, user_is_enrolled=False, **kwargs
@@ -198,6 +201,7 @@ class CourseHomeFragmentView(EdxFragmentView):
'outline_fragment': outline_fragment,
'handouts_html': handouts_html,
'course_home_message_fragment': course_home_message_fragment,
+ 'course_expiration_fragment': course_expiration_fragment,
'has_visited_course': has_visited_course,
'resume_course_url': resume_course_url,
'course_tools': course_tools,