moving milestones check to access.py and upgrading milestones version
This commit is contained in:
@@ -20,6 +20,7 @@ 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 (
|
||||
@@ -552,19 +553,18 @@ 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.
|
||||
"""
|
||||
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)
|
||||
)
|
||||
)
|
||||
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 (
|
||||
ACCESS_GRANTED if (response or _has_staff_access_to_descriptor(user, descriptor, course_key))
|
||||
else response
|
||||
_visible_to_nonstaff_users(descriptor) and
|
||||
_can_access_descriptor_with_milestones(user, descriptor, course_key) 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)
|
||||
)
|
||||
)
|
||||
|
||||
checkers = {
|
||||
@@ -801,6 +801,22 @@ 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, unicode(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.
|
||||
|
||||
@@ -9,7 +9,6 @@ from lettuce import world, step, before
|
||||
from lettuce.django import django_url
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from milestones.models import MilestoneRelationshipType
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
@@ -19,12 +18,6 @@ from logging import getLogger
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
@before.each_scenario # pylint: disable=no-member
|
||||
def setup_milestones_app(scenario): # pylint: disable=unused-argument
|
||||
MilestoneRelationshipType.objects.get_or_create(name='requires')
|
||||
MilestoneRelationshipType.objects.get_or_create(name='fulfills')
|
||||
|
||||
|
||||
@step('I (.*) capturing of screenshots before and after each step$')
|
||||
def configure_screenshots_for_all_steps(_step, action):
|
||||
"""
|
||||
|
||||
@@ -32,7 +32,6 @@ 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,
|
||||
@@ -164,9 +163,6 @@ 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]
|
||||
@@ -189,9 +185,7 @@ 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 gated/hidden from the user
|
||||
if gated_content and unicode(section.location) in gated_content:
|
||||
continue
|
||||
# skip the section if it is hidden from the user
|
||||
if section.hide_from_toc:
|
||||
continue
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ class AboutTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase, EventTra
|
||||
info_url = reverse('info', args=[self.course.id.to_deprecated_string()])
|
||||
self.assertTrue(target_url.endswith(info_url))
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True})
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True})
|
||||
def test_pre_requisite_course(self):
|
||||
pre_requisite_course = CourseFactory.create(org='edX', course='900', display_name='pre requisite course')
|
||||
course = CourseFactory.create(pre_requisite_courses=[unicode(pre_requisite_course.id)])
|
||||
@@ -154,7 +154,7 @@ class AboutTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase, EventTra
|
||||
.format(pre_requisite_course_about_url, pre_requisite_courses[0]['display']),
|
||||
resp.content.strip('\n'))
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True})
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True})
|
||||
def test_about_page_unfulfilled_prereqs(self):
|
||||
pre_requisite_course = CourseFactory.create(
|
||||
org='edX',
|
||||
|
||||
@@ -166,8 +166,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
|
||||
|
||||
def verify_access(self, mock_unit, student_should_have_access, expected_error_type=None):
|
||||
""" Verify the expected result from _has_access_descriptor """
|
||||
response = access._has_access_descriptor(self.anonymous_user, 'load',
|
||||
mock_unit, course_key=self.course.id)
|
||||
response = access._has_access_descriptor(self.anonymous_user, 'load', mock_unit, course_key=self.course.id)
|
||||
self.assertEqual(student_should_have_access, bool(response))
|
||||
|
||||
if expected_error_type is not None:
|
||||
@@ -383,7 +382,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
|
||||
Tests that "visible_to_staff_only" overrides start date.
|
||||
"""
|
||||
expected_access = expected_error_type is None
|
||||
mock_unit = Mock(user_partitions=[])
|
||||
mock_unit = Mock(location=self.course.location, user_partitions=[])
|
||||
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
|
||||
mock_unit.visible_to_staff_only = visible_to_staff_only
|
||||
mock_unit.start = start
|
||||
@@ -406,7 +405,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
|
||||
"""
|
||||
Tests that descriptor has access in preview mode.
|
||||
"""
|
||||
mock_unit = Mock(user_partitions=[])
|
||||
mock_unit = Mock(location=self.course.location, user_partitions=[])
|
||||
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
|
||||
mock_unit.visible_to_staff_only = False
|
||||
mock_unit.start = start
|
||||
@@ -425,7 +424,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
|
||||
Tests that descriptor has no access when start date in future & without preview.
|
||||
"""
|
||||
expected_access = expected_error_type is None
|
||||
mock_unit = Mock(user_partitions=[])
|
||||
mock_unit = Mock(location=self.course.location, user_partitions=[])
|
||||
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
|
||||
mock_unit.visible_to_staff_only = False
|
||||
mock_unit.start = start
|
||||
|
||||
@@ -15,7 +15,6 @@ import mock
|
||||
from django.core.urlresolvers import reverse
|
||||
from course_api.blocks.tests.helpers import deserialize_usage_key
|
||||
from courseware.module_render import get_module_for_descriptor_internal
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from xblock.field_data import DictFieldData
|
||||
from xblock.fragment import Fragment
|
||||
@@ -253,7 +252,7 @@ class TestTemplates(TestDiscussionXBlock):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestXBlockInCourse(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
class TestXBlockInCourse(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Test the discussion xblock as rendered in the course and course API.
|
||||
"""
|
||||
|
||||
@@ -39,7 +39,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
|
||||
|
||||
@attr(shard=2)
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True})
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
|
||||
class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
"""
|
||||
Check that content is properly gated.
|
||||
@@ -47,7 +47,7 @@ class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase, Milest
|
||||
Creates a test course from scratch. The tests below are designed to execute
|
||||
workflows regardless of the feature flag settings.
|
||||
"""
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True})
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
|
||||
def setUp(self):
|
||||
"""
|
||||
Test case scaffolding
|
||||
|
||||
@@ -22,7 +22,6 @@ from courseware.masquerade import (
|
||||
from courseware.tests.factories import StaffFactory
|
||||
from courseware.tests.helpers import LoginEnrollmentTestCase, get_request_for_user
|
||||
from courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from student.tests.factories import UserFactory
|
||||
from xblock.runtime import DictKeyValueStore
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -32,7 +31,7 @@ from xmodule.partitions.partitions import Group, UserPartition
|
||||
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
|
||||
|
||||
|
||||
class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase, MilestonesTestCaseMixin):
|
||||
class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
Base class for masquerade tests that sets up a test course and enrolls a user in the course.
|
||||
"""
|
||||
|
||||
@@ -407,7 +407,7 @@ class ModuleRenderTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
|
||||
|
||||
@attr(shard=1)
|
||||
class TestHandleXBlockCallback(SharedModuleStoreTestCase, LoginEnrollmentTestCase, MilestonesTestCaseMixin):
|
||||
class TestHandleXBlockCallback(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
Test the handle_xblock_callback function
|
||||
"""
|
||||
|
||||
@@ -11,7 +11,6 @@ from django.test.utils import override_settings
|
||||
|
||||
from courseware.tests.helpers import LoginEnrollmentTestCase
|
||||
from courseware.tests.factories import GlobalStaffFactory
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
@@ -19,7 +18,7 @@ from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
@attr(shard=1)
|
||||
class TestNavigation(SharedModuleStoreTestCase, LoginEnrollmentTestCase, MilestonesTestCaseMixin):
|
||||
class TestNavigation(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
Check that navigation state is saved properly.
|
||||
"""
|
||||
|
||||
@@ -7,7 +7,6 @@ from nose.plugins.attrib import attr
|
||||
|
||||
from courseware.module_render import get_module_for_descriptor
|
||||
from courseware.model_data import FieldDataCache
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
@@ -16,7 +15,7 @@ from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactor
|
||||
|
||||
|
||||
@attr(shard=1)
|
||||
class SplitTestBase(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
class SplitTestBase(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Sets up a basic course and user for split test testing.
|
||||
Also provides tests of rendered HTML for two user_tag conditions, 0 and 1.
|
||||
|
||||
@@ -330,14 +330,14 @@ class StaticTabDateTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
|
||||
|
||||
|
||||
@attr(shard=1)
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True})
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
|
||||
class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
"""
|
||||
Validate tab behavior when dealing with Entrance Exams
|
||||
"""
|
||||
MODULESTORE = TEST_DATA_MIXED_MODULESTORE
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True})
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
|
||||
def setUp(self):
|
||||
"""
|
||||
Test case scaffolding
|
||||
|
||||
@@ -191,7 +191,7 @@ class TestJumpTo(ModuleStoreTestCase):
|
||||
|
||||
@attr(shard=2)
|
||||
@ddt.ddt
|
||||
class ViewsTestCase(ModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
class ViewsTestCase(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for views.py methods.
|
||||
"""
|
||||
@@ -939,7 +939,7 @@ class ViewsTestCase(ModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
@attr(shard=1)
|
||||
# setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly
|
||||
@override_settings(TIME_ZONE_DISPLAYED_FOR_DEADLINES="UTC")
|
||||
class BaseDueDateTests(ModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
class BaseDueDateTests(ModuleStoreTestCase):
|
||||
"""
|
||||
Base class that verifies that due dates are rendered correctly on a page
|
||||
"""
|
||||
@@ -1829,7 +1829,7 @@ class ViewCheckerBlock(XBlock):
|
||||
|
||||
@attr(shard=1)
|
||||
@ddt.ddt
|
||||
class TestIndexView(ModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
class TestIndexView(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests of the courseware.views.index view.
|
||||
"""
|
||||
@@ -1901,7 +1901,7 @@ class TestIndexView(ModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestIndexViewWithVerticalPositions(ModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
class TestIndexViewWithVerticalPositions(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.
|
||||
|
||||
@@ -25,7 +25,6 @@ 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
|
||||
@@ -143,7 +142,6 @@ 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()
|
||||
|
||||
@@ -272,15 +270,6 @@ 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.
|
||||
|
||||
Reference in New Issue
Block a user