diff --git a/common/lib/xmodule/xmodule/tests/test_vertical.py b/common/lib/xmodule/xmodule/tests/test_vertical.py index 8f05484552..cc1d22b8c2 100644 --- a/common/lib/xmodule/xmodule/tests/test_vertical.py +++ b/common/lib/xmodule/xmodule/tests/test_vertical.py @@ -196,6 +196,22 @@ class VerticalBlockTestCase(BaseVerticalBlockTest): self.assertIn("'completed': None", html) self.assertIn("'past_due': False", html) + @ddt.data(True, False) + def test_render_access_denied_blocks(self, has_access_error): + """ Tests access denied blocks are not rendered when hide_access_error_blocks is True """ + self.module_system._services['bookmarks'] = Mock() + self.module_system._services['user'] = StubUserService() + self.vertical.due = datetime.now(pytz.UTC) + timedelta(days=-1) + self.problem_block.has_access_error = has_access_error + + context = {'username': self.username, 'hide_access_error_blocks': True} + html = self.module_system.render(self.vertical, STUDENT_VIEW, context).content + + if has_access_error: + self.assertNotIn(self.test_problem, html) + else: + self.assertIn(self.test_problem, html) + @ddt.unpack @ddt.data( (True, 0.9, True), diff --git a/common/lib/xmodule/xmodule/vertical_block.py b/common/lib/xmodule/xmodule/vertical_block.py index 17a302c869..c5dff07c0d 100644 --- a/common/lib/xmodule/xmodule/vertical_block.py +++ b/common/lib/xmodule/xmodule/vertical_block.py @@ -81,6 +81,8 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse # pylint: disable=no-member for child in child_blocks: + if context.get('hide_access_error_blocks') and getattr(child, 'has_access_error', False): + continue child_block_context = copy(child_context) if child in list(child_blocks_to_complete_on_view): child_block_context['wrap_xblock_data'] = { diff --git a/lms/djangoapps/commerce/api/v1/tests/test_views.py b/lms/djangoapps/commerce/api/v1/tests/test_views.py index 78c1f3dc93..a380d4f7de 100644 --- a/lms/djangoapps/commerce/api/v1/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v1/tests/test_views.py @@ -175,8 +175,9 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase) self.assertIsNone(VerificationDeadline.deadline_for_course(self.course.id)) # Generate the expected data - verification_deadline = datetime(year=2030, month=12, day=31, tzinfo=pytz.utc) - expiration_datetime = datetime.now(pytz.utc) + now = datetime.now(pytz.utc) + verification_deadline = now + timedelta(days=1) + expiration_datetime = now response, expected = self._get_update_response_and_expected_data(expiration_datetime, verification_deadline) # Sanity check: The API should return HTTP status 200 for updates diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index f364d1ac61..a645e514ef 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -3,7 +3,6 @@ Module rendering """ -import hashlib import json import logging import textwrap @@ -917,6 +916,7 @@ def get_module_for_descriptor_internal(user, descriptor, student_data, course_id and (access.user_message or access.user_fragment) ) if access or caller_will_handle_access_error: + descriptor.has_access_error = bool(caller_will_handle_access_error) return descriptor return None return descriptor @@ -1074,7 +1074,8 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None): return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course=course) -def get_module_by_usage_id(request, course_id, usage_id, disable_staff_debug_info=False, course=None): +def get_module_by_usage_id(request, course_id, usage_id, disable_staff_debug_info=False, course=None, + will_recheck_access=False): """ Gets a module instance based on its `usage_id` in a course, for a given request/user @@ -1126,7 +1127,8 @@ def get_module_by_usage_id(request, course_id, usage_id, disable_staff_debug_inf field_data_cache, usage_key.course_key, disable_staff_debug_info=disable_staff_debug_info, - course=course + course=course, + will_recheck_access=will_recheck_access, ) if instance is None: # Either permissions just changed, or someone is trying to be clever diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 33d13c5355..23d2fe071c 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -1647,14 +1647,23 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True): raise Http404("Course not found.") # get the block, which verifies whether the user has access to the block. + recheck_access = request.GET.get('recheck_access') == '1' block, _ = get_module_by_usage_id( - request, text_type(course_key), text_type(usage_key), disable_staff_debug_info=True, course=course + request, str(course_key), str(usage_key), disable_staff_debug_info=True, course=course, + will_recheck_access=recheck_access ) student_view_context = request.GET.dict() student_view_context['show_bookmark_button'] = request.GET.get('show_bookmark_button', '0') == '1' student_view_context['show_title'] = request.GET.get('show_title', '1') == '1' + is_learning_mfe = is_request_from_learning_mfe(request) + # Right now, we only care about this in regards to the Learning MFE because it results + # in a bad UX if we display blocks with access errors (repeated upgrade messaging). + # If other use cases appear, consider removing the is_learning_mfe check or switching this + # to be its own query parameter that can toggle the behavior. + student_view_context['hide_access_error_blocks'] = is_learning_mfe and recheck_access + enable_completion_on_view_service = False completion_service = block.runtime.service(block, 'completion') if completion_service and completion_service.completion_tracking_enabled(): @@ -1684,7 +1693,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True): 'web_app_course_url': reverse(COURSE_HOME_VIEW_NAME, args=[course.id]), 'on_courseware_page': True, 'verified_upgrade_link': verified_upgrade_deadline_link(request.user, course=course), - 'is_learning_mfe': is_request_from_learning_mfe(request), + 'is_learning_mfe': is_learning_mfe, 'is_mobile_app': is_request_from_mobile_app(request), 'reset_deadlines_url': reverse(RESET_COURSE_DEADLINES_NAME), }