diff --git a/common/djangoapps/util/milestones_helpers.py b/common/djangoapps/util/milestones_helpers.py index b30c4a21d9..839b9519d9 100644 --- a/common/djangoapps/util/milestones_helpers.py +++ b/common/djangoapps/util/milestones_helpers.py @@ -345,19 +345,14 @@ def add_course_content_milestone(course_id, content_id, relationship, milestone) return milestones_api.add_course_content_milestone(course_id, content_id, relationship, milestone) -def get_course_content_milestones(course_id, content_id, relationship, user_id=None): +def get_course_content_milestones(course_id, content_id, relationship): """ Client API operation adapter/wrapper """ if not settings.FEATURES.get('MILESTONES_APP', False): return [] from milestones import api as milestones_api - return milestones_api.get_course_content_milestones( - course_id, - content_id, - relationship, - {'id': user_id} if user_id else None - ) + return milestones_api.get_course_content_milestones(course_id, content_id, relationship) def remove_course_content_user_milestones(course_key, content_key, user, relationship): diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index c6b75b4920..e6063dbdf5 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -20,7 +20,6 @@ from django.utils.timezone import UTC from opaque_keys.edx.keys import CourseKey, UsageKey -from util import milestones_helpers as milestones_helpers from xblock.core import XBlock from xmodule.course_module import ( @@ -553,20 +552,21 @@ def _has_access_descriptor(user, action, descriptor, course_key=None): students to see modules. If not, views should check the course, so we don't have to hit the enrollments table on every module load. """ - if _has_staff_access_to_descriptor(user, descriptor, course_key): - return ACCESS_GRANTED - - # if the user has staff access, they can load the module so this code doesn't need to run - return ( - _visible_to_nonstaff_users(descriptor) and - _can_access_descriptor_with_milestones(user, descriptor, course_key) and - _has_group_access(descriptor, user, course_key) and + response = ( + _visible_to_nonstaff_users(descriptor) + and _has_group_access(descriptor, user, course_key) + and ( - _has_detached_class_tag(descriptor) or - _can_access_descriptor_with_start_date(user, descriptor, course_key) + _has_detached_class_tag(descriptor) + or _can_access_descriptor_with_start_date(user, descriptor, course_key) ) ) + return ( + ACCESS_GRANTED if (response or _has_staff_access_to_descriptor(user, descriptor, course_key)) + else response + ) + checkers = { 'load': can_load, 'staff': lambda: _has_staff_access_to_descriptor(user, descriptor, course_key), @@ -801,22 +801,6 @@ def _visible_to_nonstaff_users(descriptor): return VisibilityError() if descriptor.visible_to_staff_only else ACCESS_GRANTED -def _can_access_descriptor_with_milestones(user, descriptor, course_key): - """ - Returns if the object is blocked by an unfulfilled milestone. - - Args: - user: the user trying to access this content - descriptor: the object being accessed - course_key: key for the course for this descriptor - """ - if milestones_helpers.get_course_content_milestones(course_key, descriptor.location, 'requires', user.id): - debug("Deny: user has not completed all milestones for content") - return ACCESS_DENIED - else: - return ACCESS_GRANTED - - def _has_detached_class_tag(descriptor): """ Returns if the given descriptor's type is marked as detached. diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py index c809e01fb8..4acf3e8fb1 100644 --- a/lms/djangoapps/courseware/grades.py +++ b/lms/djangoapps/courseware/grades.py @@ -17,6 +17,7 @@ from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import BlockUsageLocator from openedx.core.djangoapps.content.block_structure.api import get_course_in_cache from openedx.core.lib.cache_utils import memoized +from openedx.core.lib.gating import api as gating_api from courseware.model_data import FieldDataCache, ScoresClient from openedx.core.djangoapps.signals.signals import GRADES_UPDATED from student.models import anonymous_id_for_user @@ -512,6 +513,9 @@ def _progress_summary(student, course, course_structure=None): unicode(course.id), anonymous_id_for_user(student, course.id) ) + # Check for gated content + gated_content = gating_api.get_gated_content(course, student) + chapters = [] locations_to_weighted_scores = {} @@ -519,6 +523,9 @@ def _progress_summary(student, course, course_structure=None): chapter = course_structure[chapter_key] sections = [] for section_key in course_structure.get_children(chapter_key): + if unicode(section_key) in gated_content: + continue + section = course_structure[section_key] graded = getattr(section, 'graded', False) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 981edf0f59..adea910ae7 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -32,6 +32,7 @@ from xblock.exceptions import NoSuchHandlerError, NoSuchViewError from xblock.reference.plugins import FSService import static_replace +from openedx.core.lib.gating import api as gating_api from courseware.access import has_access, get_user_role from courseware.entrance_exams import ( get_entrance_exam_score, @@ -163,6 +164,9 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_ # before the rest of the content is made available required_content = milestones_helpers.get_required_content(course, user) + # Check for gated content + gated_content = gating_api.get_gated_content(course, user) + # The user may not actually have to complete the entrance exam, if one is required if not user_must_complete_entrance_exam(request, user, course): required_content = [content for content in required_content if not content == course.entrance_exam_id] @@ -185,7 +189,9 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_ sections = list() for section in chapter.get_display_items(): - # skip the section if it is hidden from the user + # skip the section if it is gated/hidden from the user + if gated_content and unicode(section.location) in gated_content: + continue if section.hide_from_toc: continue diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index baf933ebd3..552297aa1e 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -1112,7 +1112,6 @@ class TestGatedSubsectionRendering(SharedModuleStoreTestCase, MilestonesTestCase return None - @patch.dict(settings.FEATURES, {'MILESTONES_APP': True}) def test_toc_with_gated_sequential(self): """ Test generation of TOC for a course with a gated subsection diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 3ebac39977..ec0ca7cea6 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -1878,7 +1878,7 @@ class TestIndexView(ModuleStoreTestCase): @ddt.ddt -class TestIndexViewWithVerticalPositions(ModuleStoreTestCase): +class TestIndewViewWithVerticalPositions(ModuleStoreTestCase): """ Test the index view to handle vertical positions. Confirms that first position is loaded if input position is non-positive or greater than number of positions available. @@ -1888,7 +1888,7 @@ class TestIndexViewWithVerticalPositions(ModuleStoreTestCase): """ Set up initial test data """ - super(TestIndexViewWithVerticalPositions, self).setUp() + super(TestIndewViewWithVerticalPositions, self).setUp() self.user = UserFactory() @@ -1966,7 +1966,6 @@ class TestIndexViewWithGating(ModuleStoreTestCase, MilestonesTestCaseMixin): CourseEnrollmentFactory(user=self.user, course_id=self.course.id) - @patch.dict(settings.FEATURES, {'MILESTONES_APP': True}) def test_index_with_gated_sequential(self): """ Test index view with a gated sequential raises Http404 diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index a361898eff..e102eb27d4 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -25,6 +25,7 @@ import urllib from lang_pref import LANGUAGE_KEY from xblock.fragment import Fragment from opaque_keys.edx.keys import CourseKey +from openedx.core.lib.gating import api as gating_api from openedx.core.lib.time_zone_utils import get_user_time_zone from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from shoppingcart.models import CourseRegistrationCode @@ -142,6 +143,7 @@ class CoursewareIndex(View): if self.chapter and self.section: self._redirect_if_not_requested_section() + self._verify_section_not_gated() self._save_positions() self._prefetch_and_bind_section() @@ -270,6 +272,15 @@ class CoursewareIndex(View): self.chapter_url_name = exam_chapter.url_name self.section_url_name = exam_section.url_name + def _verify_section_not_gated(self): + """ + Verify whether the section is gated and accessible to the user. + """ + gated_content = gating_api.get_gated_content(self.course, self.effective_user) + if gated_content: + if unicode(self.section.location) in gated_content: + raise Http404 + def _get_language_preference(self): """ Returns the preferred language for the actual user making the request.