diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index e381c73aad..1f81fb29e2 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -67,8 +67,7 @@ from contentstore.tasks import rerun_course from contentstore.views.entrance_exam import ( create_entrance_exam, update_entrance_exam, - delete_entrance_exam, - is_entrance_exams_enabled + delete_entrance_exam ) from .library import LIBRARIES_ENABLED @@ -89,7 +88,8 @@ from student.auth import has_course_author_access from util.milestones_helpers import ( set_prerequisite_courses, is_valid_course_key, - is_prerequisite_courses_enabled + is_prerequisite_courses_enabled, + is_entrance_exams_enabled ) log = logging.getLogger(__name__) @@ -983,7 +983,7 @@ def settings_handler(request, course_key_string): # feature-specific settings and handle them accordingly # We have to be careful that we're only executing the following logic if we actually # need to create or delete an entrance exam from the specified course - if settings.FEATURES.get('ENTRANCE_EXAMS', False): + if is_entrance_exams_enabled(): course_entrance_exam_present = course_module.entrance_exam_enabled entrance_exam_enabled = request.json.get('entrance_exam_enabled', '') == 'true' ee_min_score_pct = request.json.get('entrance_exam_minimum_score_pct', None) diff --git a/cms/djangoapps/contentstore/views/entrance_exam.py b/cms/djangoapps/contentstore/views/entrance_exam.py index 1e297101f0..320ce3df3a 100644 --- a/cms/djangoapps/contentstore/views/entrance_exam.py +++ b/cms/djangoapps/contentstore/views/entrance_exam.py @@ -54,13 +54,6 @@ def check_feature_enabled(feature_name): return _check_feature_enabled -def is_entrance_exams_enabled(): - """ - Returns a boolean indicating entrance exam feature is enable or not. - """ - return settings.FEATURES.get('ENTRANCE_EXAMS', False) - - @login_required @ensure_csrf_cookie @check_feature_enabled(feature_name='ENTRANCE_EXAMS') diff --git a/cms/djangoapps/contentstore/views/helpers.py b/cms/djangoapps/contentstore/views/helpers.py index 59dc39cc30..60683b040c 100644 --- a/cms/djangoapps/contentstore/views/helpers.py +++ b/cms/djangoapps/contentstore/views/helpers.py @@ -22,6 +22,7 @@ from xmodule.tabs import StaticTab from contentstore.utils import reverse_course_url, reverse_library_url, reverse_usage_url from models.settings.course_grading import CourseGradingModel +from util.milestones_helpers import is_entrance_exams_enabled __all__ = ['edge', 'event', 'landing'] @@ -226,7 +227,7 @@ def create_xblock(parent_locator, user, category, display_name, boilerplate=None # Entrance Exams: Chapter module positioning child_position = None - if settings.FEATURES.get('ENTRANCE_EXAMS', False): + if is_entrance_exams_enabled(): if category == 'chapter' and is_entrance_exam: fields['is_entrance_exam'] = is_entrance_exam fields['in_entrance_exam'] = True # Inherited metadata, all children will have it @@ -250,7 +251,7 @@ def create_xblock(parent_locator, user, category, display_name, boilerplate=None ) # Entrance Exams: Grader assignment - if settings.FEATURES.get('ENTRANCE_EXAMS', False): + if is_entrance_exams_enabled(): course_key = usage_key.course_key course = store.get_course(course_key) if hasattr(course, 'entrance_exam_enabled') and course.entrance_exam_enabled: diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index d1283ece1b..055fa4afd7 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -38,6 +38,7 @@ from django.contrib.auth.models import User from util.date_utils import get_default_time_display from util.json_request import expect_json, JsonResponse +from util.milestones_helpers import is_entrance_exams_enabled from student.auth import has_studio_write_access, has_studio_read_access from contentstore.utils import ( @@ -89,7 +90,7 @@ def _filter_entrance_exam_grader(graders): views/controls like the 'Grade as' dropdown that allows a course author to select the grader type for a given section of a course """ - if settings.FEATURES.get('ENTRANCE_EXAMS', False): + if is_entrance_exams_enabled(): graders = [grader for grader in graders if grader.get('type') != u'Entrance Exam'] return graders diff --git a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py index 3f07a1bcba..dfcd1de091 100644 --- a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py +++ b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py @@ -20,257 +20,257 @@ from util import milestones_helpers from xmodule.modulestore.django import modulestore +@patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True}) class EntranceExamHandlerTests(CourseTestCase): """ Base test class for create, save, and delete """ - if settings.FEATURES.get('ENTRANCE_EXAMS', False): - def setUp(self): - """ - Shared scaffolding for individual test runs - """ - super(EntranceExamHandlerTests, self).setUp() - self.course_key = self.course.id - self.usage_key = self.course.location - self.course_url = '/course/{}'.format(unicode(self.course.id)) - self.exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id)) - milestones_helpers.seed_milestone_relationship_types() - self.milestone_relationship_types = milestones_helpers.get_milestone_relationship_types() + def setUp(self): + """ + Shared scaffolding for individual test runs + """ + super(EntranceExamHandlerTests, self).setUp() + self.course_key = self.course.id + self.usage_key = self.course.location + self.course_url = '/course/{}'.format(unicode(self.course.id)) + self.exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id)) + milestones_helpers.seed_milestone_relationship_types() + self.milestone_relationship_types = milestones_helpers.get_milestone_relationship_types() - def test_contentstore_views_entrance_exam_post(self): - """ - Unit Test: test_contentstore_views_entrance_exam_post - """ - resp = self.client.post(self.exam_url, {}, http_accept='application/json') - self.assertEqual(resp.status_code, 201) - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 200) + def test_contentstore_views_entrance_exam_post(self): + """ + Unit Test: test_contentstore_views_entrance_exam_post + """ + resp = self.client.post(self.exam_url, {}, http_accept='application/json') + self.assertEqual(resp.status_code, 201) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 200) - # Reload the test course now that the exam module has been added - self.course = modulestore().get_course(self.course.id) - metadata = CourseMetadata.fetch_all(self.course) - self.assertTrue(metadata['entrance_exam_enabled']) - self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct']) - self.assertIsNotNone(metadata['entrance_exam_id']['value']) - self.assertTrue(len(milestones_helpers.get_course_milestones(unicode(self.course.id)))) - content_milestones = milestones_helpers.get_course_content_milestones( - unicode(self.course.id), - metadata['entrance_exam_id']['value'], - self.milestone_relationship_types['FULFILLS'] - ) - self.assertTrue(len(content_milestones)) + # Reload the test course now that the exam module has been added + self.course = modulestore().get_course(self.course.id) + metadata = CourseMetadata.fetch_all(self.course) + self.assertTrue(metadata['entrance_exam_enabled']) + self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct']) + self.assertIsNotNone(metadata['entrance_exam_id']['value']) + self.assertTrue(len(milestones_helpers.get_course_milestones(unicode(self.course.id)))) + content_milestones = milestones_helpers.get_course_content_milestones( + unicode(self.course.id), + metadata['entrance_exam_id']['value'], + self.milestone_relationship_types['FULFILLS'] + ) + self.assertTrue(len(content_milestones)) - def test_contentstore_views_entrance_exam_post_new_sequential_confirm_grader(self): - """ - Unit Test: test_contentstore_views_entrance_exam_post - """ - resp = self.client.post(self.exam_url, {}, http_accept='application/json') - self.assertEqual(resp.status_code, 201) - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 200) + def test_contentstore_views_entrance_exam_post_new_sequential_confirm_grader(self): + """ + Unit Test: test_contentstore_views_entrance_exam_post + """ + resp = self.client.post(self.exam_url, {}, http_accept='application/json') + self.assertEqual(resp.status_code, 201) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 200) - # Reload the test course now that the exam module has been added - self.course = modulestore().get_course(self.course.id) + # Reload the test course now that the exam module has been added + self.course = modulestore().get_course(self.course.id) - # Add a new child sequential to the exam module - # Confirm that the grader type is 'Entrance Exam' - chapter_locator_string = json.loads(resp.content).get('locator') - # chapter_locator = UsageKey.from_string(chapter_locator_string) - seq_data = { - 'category': "sequential", - 'display_name': "Entrance Exam Subsection", - 'parent_locator': chapter_locator_string, - } - resp = self.client.ajax_post(reverse_url('xblock_handler'), seq_data) - seq_locator_string = json.loads(resp.content).get('locator') - seq_locator = UsageKey.from_string(seq_locator_string) - section_grader_type = CourseGradingModel.get_section_grader_type(seq_locator) - self.assertEqual(GRADER_TYPES['ENTRANCE_EXAM'], section_grader_type['graderType']) + # Add a new child sequential to the exam module + # Confirm that the grader type is 'Entrance Exam' + chapter_locator_string = json.loads(resp.content).get('locator') + # chapter_locator = UsageKey.from_string(chapter_locator_string) + seq_data = { + 'category': "sequential", + 'display_name': "Entrance Exam Subsection", + 'parent_locator': chapter_locator_string, + } + resp = self.client.ajax_post(reverse_url('xblock_handler'), seq_data) + seq_locator_string = json.loads(resp.content).get('locator') + seq_locator = UsageKey.from_string(seq_locator_string) + section_grader_type = CourseGradingModel.get_section_grader_type(seq_locator) + self.assertEqual(GRADER_TYPES['ENTRANCE_EXAM'], section_grader_type['graderType']) - def test_contentstore_views_entrance_exam_get(self): - """ - Unit Test: test_contentstore_views_entrance_exam_get - """ - resp = self.client.post( - self.exam_url, - {'entrance_exam_minimum_score_pct': settings.ENTRANCE_EXAM_MIN_SCORE_PCT}, - http_accept='application/json' - ) - self.assertEqual(resp.status_code, 201) - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 200) + def test_contentstore_views_entrance_exam_get(self): + """ + Unit Test: test_contentstore_views_entrance_exam_get + """ + resp = self.client.post( + self.exam_url, + {'entrance_exam_minimum_score_pct': settings.ENTRANCE_EXAM_MIN_SCORE_PCT}, + http_accept='application/json' + ) + self.assertEqual(resp.status_code, 201) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 200) - def test_contentstore_views_entrance_exam_delete(self): - """ - Unit Test: test_contentstore_views_entrance_exam_delete - """ - resp = self.client.post(self.exam_url, {}, http_accept='application/json') - self.assertEqual(resp.status_code, 201) - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 200) - resp = self.client.delete(self.exam_url) - self.assertEqual(resp.status_code, 204) - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 404) + def test_contentstore_views_entrance_exam_delete(self): + """ + Unit Test: test_contentstore_views_entrance_exam_delete + """ + resp = self.client.post(self.exam_url, {}, http_accept='application/json') + self.assertEqual(resp.status_code, 201) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 200) + resp = self.client.delete(self.exam_url) + self.assertEqual(resp.status_code, 204) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 404) - user = User.objects.create( - username='test_user', - email='test_user@edx.org', - is_active=True, - ) - user.set_password('test') - user.save() - milestones = milestones_helpers.get_course_milestones(unicode(self.course_key)) - self.assertEqual(len(milestones), 1) - milestone_key = '{}.{}'.format(milestones[0]['namespace'], milestones[0]['name']) - paths = milestones_helpers.get_course_milestones_fulfillment_paths( - unicode(self.course_key), - milestones_helpers.serialize_user(user) - ) + user = User.objects.create( + username='test_user', + email='test_user@edx.org', + is_active=True, + ) + user.set_password('test') + user.save() + milestones = milestones_helpers.get_course_milestones(unicode(self.course_key)) + self.assertEqual(len(milestones), 1) + milestone_key = '{}.{}'.format(milestones[0]['namespace'], milestones[0]['name']) + paths = milestones_helpers.get_course_milestones_fulfillment_paths( + unicode(self.course_key), + milestones_helpers.serialize_user(user) + ) - # What we have now is a course milestone requirement and no valid fulfillment - # paths for the specified user. The LMS is going to have to ignore this situation, - # because we can't confidently prevent it from occuring at some point in the future. - # milestone_key_1 = - self.assertEqual(len(paths[milestone_key]), 0) + # What we have now is a course milestone requirement and no valid fulfillment + # paths for the specified user. The LMS is going to have to ignore this situation, + # because we can't confidently prevent it from occuring at some point in the future. + # milestone_key_1 = + self.assertEqual(len(paths[milestone_key]), 0) - # Re-adding an entrance exam to the course should fix the missing link - # It wipes out any old entrance exam artifacts and inserts a new exam course chapter/module - resp = self.client.post(self.exam_url, {}, http_accept='application/json') - self.assertEqual(resp.status_code, 201) - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 200) + # Re-adding an entrance exam to the course should fix the missing link + # It wipes out any old entrance exam artifacts and inserts a new exam course chapter/module + resp = self.client.post(self.exam_url, {}, http_accept='application/json') + self.assertEqual(resp.status_code, 201) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 200) - # Confirm that we have only one Entrance Exam grader after re-adding the exam (validates SOL-475) - graders = CourseGradingModel.fetch(self.course_key).graders - count = 0 - for grader in graders: - if grader['type'] == GRADER_TYPES['ENTRANCE_EXAM']: - count += 1 - self.assertEqual(count, 1) + # Confirm that we have only one Entrance Exam grader after re-adding the exam (validates SOL-475) + graders = CourseGradingModel.fetch(self.course_key).graders + count = 0 + for grader in graders: + if grader['type'] == GRADER_TYPES['ENTRANCE_EXAM']: + count += 1 + self.assertEqual(count, 1) - def test_contentstore_views_entrance_exam_delete_bogus_course(self): - """ - Unit Test: test_contentstore_views_entrance_exam_delete_bogus_course - """ - resp = self.client.delete('/course/bad/course/key/entrance_exam') - self.assertEqual(resp.status_code, 400) + def test_contentstore_views_entrance_exam_delete_bogus_course(self): + """ + Unit Test: test_contentstore_views_entrance_exam_delete_bogus_course + """ + resp = self.client.delete('/course/bad/course/key/entrance_exam') + self.assertEqual(resp.status_code, 400) - def test_contentstore_views_entrance_exam_get_bogus_course(self): - """ - Unit Test: test_contentstore_views_entrance_exam_get_bogus_course - """ - resp = self.client.get('/course/bad/course/key/entrance_exam') - self.assertEqual(resp.status_code, 400) + def test_contentstore_views_entrance_exam_get_bogus_course(self): + """ + Unit Test: test_contentstore_views_entrance_exam_get_bogus_course + """ + resp = self.client.get('/course/bad/course/key/entrance_exam') + self.assertEqual(resp.status_code, 400) - def test_contentstore_views_entrance_exam_get_bogus_exam(self): - """ - Unit Test: test_contentstore_views_entrance_exam_get_bogus_exam - """ - resp = self.client.post( - self.exam_url, - {'entrance_exam_minimum_score_pct': '50'}, - http_accept='application/json' - ) - self.assertEqual(resp.status_code, 201) - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 200) - self.course = modulestore().get_course(self.course.id) + def test_contentstore_views_entrance_exam_get_bogus_exam(self): + """ + Unit Test: test_contentstore_views_entrance_exam_get_bogus_exam + """ + resp = self.client.post( + self.exam_url, + {'entrance_exam_minimum_score_pct': '50'}, + http_accept='application/json' + ) + self.assertEqual(resp.status_code, 201) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 200) + self.course = modulestore().get_course(self.course.id) - # Should raise an ItemNotFoundError and return a 404 - updated_metadata = {'entrance_exam_id': 'i4x://org.4/course_4/chapter/ed7c4c6a4d68409998e2c8554c4629d1'} - CourseMetadata.update_from_dict( - updated_metadata, - self.course, - self.user, - ) - self.course = modulestore().get_course(self.course.id) - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 404) + # Should raise an ItemNotFoundError and return a 404 + updated_metadata = {'entrance_exam_id': 'i4x://org.4/course_4/chapter/ed7c4c6a4d68409998e2c8554c4629d1'} + CourseMetadata.update_from_dict( + updated_metadata, + self.course, + self.user, + ) + self.course = modulestore().get_course(self.course.id) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 404) - # Should raise an InvalidKeyError and return a 404 - updated_metadata = {'entrance_exam_id': '123afsdfsad90f87'} - CourseMetadata.update_from_dict( - updated_metadata, - self.course, - self.user, - ) - self.course = modulestore().get_course(self.course.id) - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 404) + # Should raise an InvalidKeyError and return a 404 + updated_metadata = {'entrance_exam_id': '123afsdfsad90f87'} + CourseMetadata.update_from_dict( + updated_metadata, + self.course, + self.user, + ) + self.course = modulestore().get_course(self.course.id) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 404) - def test_contentstore_views_entrance_exam_post_bogus_course(self): - """ - Unit Test: test_contentstore_views_entrance_exam_post_bogus_course - """ - resp = self.client.post( - '/course/bad/course/key/entrance_exam', - {}, - http_accept='application/json' - ) - self.assertEqual(resp.status_code, 400) + def test_contentstore_views_entrance_exam_post_bogus_course(self): + """ + Unit Test: test_contentstore_views_entrance_exam_post_bogus_course + """ + resp = self.client.post( + '/course/bad/course/key/entrance_exam', + {}, + http_accept='application/json' + ) + self.assertEqual(resp.status_code, 400) - def test_contentstore_views_entrance_exam_post_invalid_http_accept(self): - """ - Unit Test: test_contentstore_views_entrance_exam_post_invalid_http_accept - """ - resp = self.client.post( - '/course/bad/course/key/entrance_exam', - {}, - http_accept='text/html' - ) - self.assertEqual(resp.status_code, 400) + def test_contentstore_views_entrance_exam_post_invalid_http_accept(self): + """ + Unit Test: test_contentstore_views_entrance_exam_post_invalid_http_accept + """ + resp = self.client.post( + '/course/bad/course/key/entrance_exam', + {}, + http_accept='text/html' + ) + self.assertEqual(resp.status_code, 400) - def test_contentstore_views_entrance_exam_get_invalid_user(self): - """ - Unit Test: test_contentstore_views_entrance_exam_get_invalid_user - """ - user = User.objects.create( - username='test_user', - email='test_user@edx.org', - is_active=True, - ) - user.set_password('test') - user.save() - self.client = AjaxEnabledTestClient() - self.client.login(username='test_user', password='test') - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 403) + def test_contentstore_views_entrance_exam_get_invalid_user(self): + """ + Unit Test: test_contentstore_views_entrance_exam_get_invalid_user + """ + user = User.objects.create( + username='test_user', + email='test_user@edx.org', + is_active=True, + ) + user.set_password('test') + user.save() + self.client = AjaxEnabledTestClient() + self.client.login(username='test_user', password='test') + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 403) - def test_contentstore_views_entrance_exam_unsupported_method(self): - """ - Unit Test: test_contentstore_views_entrance_exam_unsupported_method - """ - resp = self.client.put(self.exam_url) - self.assertEqual(resp.status_code, 405) + def test_contentstore_views_entrance_exam_unsupported_method(self): + """ + Unit Test: test_contentstore_views_entrance_exam_unsupported_method + """ + resp = self.client.put(self.exam_url) + self.assertEqual(resp.status_code, 405) - def test_entrance_exam_view_direct_missing_score_setting(self): - """ - Unit Test: test_entrance_exam_view_direct_missing_score_setting - """ - user = UserFactory() - user.is_staff = True - request = RequestFactory() - request.user = user + def test_entrance_exam_view_direct_missing_score_setting(self): + """ + Unit Test: test_entrance_exam_view_direct_missing_score_setting + """ + user = UserFactory() + user.is_staff = True + request = RequestFactory() + request.user = user - resp = create_entrance_exam(request, self.course.id, None) - self.assertEqual(resp.status_code, 201) + resp = create_entrance_exam(request, self.course.id, None) + self.assertEqual(resp.status_code, 201) - @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': False}) - def test_entrance_exam_feature_flag_gating(self): - user = UserFactory() - user.is_staff = True - request = RequestFactory() - request.user = user + @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': False}) + def test_entrance_exam_feature_flag_gating(self): + user = UserFactory() + user.is_staff = True + request = RequestFactory() + request.user = user - resp = self.client.get(self.exam_url) - self.assertEqual(resp.status_code, 400) + resp = self.client.get(self.exam_url) + self.assertEqual(resp.status_code, 400) - resp = create_entrance_exam(request, self.course.id, None) - self.assertEqual(resp.status_code, 400) + resp = create_entrance_exam(request, self.course.id, None) + self.assertEqual(resp.status_code, 400) - resp = delete_entrance_exam(request, self.course.id) - self.assertEqual(resp.status_code, 400) + resp = delete_entrance_exam(request, self.course.id) + self.assertEqual(resp.status_code, 400) - # No return, so we'll just ensure no exception is thrown - update_entrance_exam(request, self.course.id, {}) + # No return, so we'll just ensure no exception is thrown + update_entrance_exam(request, self.course.id, {}) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index d573220dc0..8cc1c8a56e 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -52,6 +52,7 @@ import lms.lib.comment_client as cc from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from util.model_utils import emit_field_changed_events, get_changed_fields_dict from util.query import use_read_replica_if_available +from util.milestones_helpers import is_entrance_exams_enabled UNENROLL_DONE = Signal(providing_args=["course_enrollment", "skip_refund"]) @@ -1896,7 +1897,7 @@ class EntranceExamConfiguration(models.Model): Return True if given user can skip entrance exam for given course otherwise False. """ can_skip = False - if settings.FEATURES.get('ENTRANCE_EXAMS', False): + if is_entrance_exams_enabled(): try: record = EntranceExamConfiguration.objects.get(user=user, course_id=course_key) can_skip = record.skip_entrance_exam diff --git a/common/djangoapps/util/milestones_helpers.py b/common/djangoapps/util/milestones_helpers.py index 9652206790..8c627b5a79 100644 --- a/common/djangoapps/util/milestones_helpers.py +++ b/common/djangoapps/util/milestones_helpers.py @@ -24,6 +24,14 @@ def get_namespace_choices(): return NAMESPACE_CHOICES +def is_entrance_exams_enabled(): + """ + Checks to see if the Entrance Exams feature is enabled + Use this operation instead of checking the feature flag all over the place + """ + return settings.FEATURES.get('ENTRANCE_EXAMS', False) + + def is_prerequisite_courses_enabled(): """ Returns boolean indicating prerequisite courses enabled system wide or not. diff --git a/lms/djangoapps/courseware/entrance_exams.py b/lms/djangoapps/courseware/entrance_exams.py index 8c6f73c7e1..51429df539 100644 --- a/lms/djangoapps/courseware/entrance_exams.py +++ b/lms/djangoapps/courseware/entrance_exams.py @@ -8,24 +8,16 @@ from courseware.model_data import FieldDataCache, ScoresClient from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.locator import BlockUsageLocator from student.models import EntranceExamConfiguration -from util.milestones_helpers import get_required_content +from util.milestones_helpers import get_required_content, is_entrance_exams_enabled from util.module_utils import yield_dynamic_descriptor_descendants from xmodule.modulestore.django import modulestore -def feature_is_enabled(): - """ - Checks to see if the Entrance Exams feature is enabled - Use this operation instead of checking the feature flag all over the place - """ - return settings.FEATURES.get('ENTRANCE_EXAMS', False) - - def course_has_entrance_exam(course): """ Checks to see if a course is properly configured for an entrance exam """ - if not feature_is_enabled(): + if not is_entrance_exams_enabled(): return False if not course.entrance_exam_enabled: return False diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 39b20449f4..4ccea3061c 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -469,7 +469,7 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to # Fulfillment Use Case: Entrance Exam # If this module is part of an entrance exam, we'll need to see if the student # has reached the point at which they can collect the associated milestone - if settings.FEATURES.get('ENTRANCE_EXAMS', False): + if milestones_helpers.is_entrance_exams_enabled(): course = modulestore().get_course(course_key) content = modulestore().get_item(content_key) entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False) diff --git a/lms/djangoapps/courseware/tests/test_tabs.py b/lms/djangoapps/courseware/tests/test_tabs.py index b50d4d793a..79d120e2d2 100644 --- a/lms/djangoapps/courseware/tests/test_tabs.py +++ b/lms/djangoapps/courseware/tests/test_tabs.py @@ -19,7 +19,13 @@ from courseware.tests.factories import InstructorFactory, StaffFactory from courseware.views import get_static_tab_contents, static_tab from student.models import CourseEnrollment from student.tests.factories import UserFactory -from util import milestones_helpers +from util.milestones_helpers import ( + seed_milestone_relationship_types, + get_milestone_relationship_types, + add_milestone, + add_course_milestone, + add_course_content_milestone +) from xmodule import tabs as xmodule_tabs from xmodule.modulestore.tests.django_utils import ( TEST_DATA_MIXED_TOY_MODULESTORE, TEST_DATA_MIXED_CLOSED_MODULESTORE @@ -303,112 +309,112 @@ class StaticTabDateTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase): @attr('shard_1') +@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True}) class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): """ Validate tab behavior when dealing with Entrance Exams """ MODULESTORE = TEST_DATA_MIXED_CLOSED_MODULESTORE - if settings.FEATURES.get('ENTRANCE_EXAMS', False): + @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True}) + def setUp(self): + """ + Test case scaffolding + """ + super(EntranceExamsTabsTestCase, self).setUp() - def setUp(self): - """ - Test case scaffolding - """ - super(EntranceExamsTabsTestCase, self).setUp() + self.course = CourseFactory.create() + self.instructor_tab = ItemFactory.create( + category="instructor", parent_location=self.course.location, + data="Instructor Tab", display_name="Instructor" + ) + self.extra_tab_2 = ItemFactory.create( + category="static_tab", parent_location=self.course.location, + data="Extra Tab", display_name="Extra Tab 2" + ) + self.extra_tab_3 = ItemFactory.create( + category="static_tab", parent_location=self.course.location, + data="Extra Tab", display_name="Extra Tab 3" + ) + self.setup_user() + self.enroll(self.course) + self.user.is_staff = True + seed_milestone_relationship_types() + self.relationship_types = get_milestone_relationship_types() - self.course = CourseFactory.create() - self.instructor_tab = ItemFactory.create( - category="instructor", parent_location=self.course.location, - data="Instructor Tab", display_name="Instructor" - ) - self.extra_tab_2 = ItemFactory.create( - category="static_tab", parent_location=self.course.location, - data="Extra Tab", display_name="Extra Tab 2" - ) - self.extra_tab_3 = ItemFactory.create( - category="static_tab", parent_location=self.course.location, - data="Extra Tab", display_name="Extra Tab 3" - ) - self.setup_user() - self.enroll(self.course) - self.user.is_staff = True - self.relationship_types = milestones_helpers.get_milestone_relationship_types() - milestones_helpers.seed_milestone_relationship_types() + def test_get_course_tabs_list_entrance_exam_enabled(self): + """ + Unit Test: test_get_course_tabs_list_entrance_exam_enabled + """ + entrance_exam = ItemFactory.create( + category="chapter", + parent_location=self.course.location, + data="Exam Data", + display_name="Entrance Exam", + is_entrance_exam=True + ) + milestone = { + 'name': 'Test Milestone', + 'namespace': '{}.entrance_exams'.format(unicode(self.course.id)), + 'description': 'Testing Courseware Tabs' + } + self.user.is_staff = False + request = get_request_for_user(self.user) + self.course.entrance_exam_enabled = True + self.course.entrance_exam_id = unicode(entrance_exam.location) + milestone = add_milestone(milestone) + add_course_milestone( + unicode(self.course.id), + self.relationship_types['REQUIRES'], + milestone + ) + add_course_content_milestone( + unicode(self.course.id), + unicode(entrance_exam.location), + self.relationship_types['FULFILLS'], + milestone + ) + course_tab_list = get_course_tab_list(request, self.course) + self.assertEqual(len(course_tab_list), 1) + self.assertEqual(course_tab_list[0]['tab_id'], 'courseware') + self.assertEqual(course_tab_list[0]['name'], 'Entrance Exam') - def test_get_course_tabs_list_entrance_exam_enabled(self): - """ - Unit Test: test_get_course_tabs_list_entrance_exam_enabled - """ - entrance_exam = ItemFactory.create( - category="chapter", - parent_location=self.course.location, - data="Exam Data", - display_name="Entrance Exam", - is_entrance_exam=True - ) - milestone = { - 'name': 'Test Milestone', - 'namespace': '{}.entrance_exams'.format(unicode(self.course.id)), - 'description': 'Testing Courseware Tabs' - } - self.user.is_staff = False - request = get_request_for_user(self.user) - self.course.entrance_exam_enabled = True - self.course.entrance_exam_id = unicode(entrance_exam.location) - milestone = milestones_helpers.add_milestone(milestone) - milestones_helpers.add_course_milestone( - unicode(self.course.id), - self.relationship_types['REQUIRES'], - milestone - ) - milestones_helpers.add_course_content_milestone( - unicode(self.course.id), - unicode(entrance_exam.location), - self.relationship_types['FULFILLS'], - milestone - ) - course_tab_list = get_course_tab_list(request, self.course) - self.assertEqual(len(course_tab_list), 1) - self.assertEqual(course_tab_list[0]['tab_id'], 'courseware') - self.assertEqual(course_tab_list[0]['name'], 'Entrance Exam') + def test_get_course_tabs_list_skipped_entrance_exam(self): + """ + Tests tab list is not limited if user is allowed to skip entrance exam. + """ + #create a user + student = UserFactory() + # login as instructor hit skip entrance exam api in instructor app + instructor = InstructorFactory(course_key=self.course.id) + self.client.logout() + self.client.login(username=instructor.username, password='test') - def test_get_course_tabs_list_skipped_entrance_exam(self): - """ - Tests tab list is not limited if user is allowed to skip entrance exam. - """ - #create a user - student = UserFactory() - # login as instructor hit skip entrance exam api in instructor app - instructor = InstructorFactory(course_key=self.course.id) - self.client.logout() - self.client.login(username=instructor.username, password='test') + url = reverse('mark_student_can_skip_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) + response = self.client.post(url, { + 'unique_student_identifier': student.email, + }) + self.assertEqual(response.status_code, 200) - url = reverse('mark_student_can_skip_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.post(url, { - 'unique_student_identifier': student.email, - }) - self.assertEqual(response.status_code, 200) + # log in again as student + self.client.logout() + self.login(self.email, self.password) + request = get_request_for_user(self.user) + course_tab_list = get_course_tab_list(request, self.course) + self.assertEqual(len(course_tab_list), 5) - # log in again as student - self.client.logout() - self.login(self.email, self.password) - request = get_request_for_user(self.user) - course_tab_list = get_course_tab_list(request, self.course) - self.assertEqual(len(course_tab_list), 5) - - def test_course_tabs_list_for_staff_members(self): - """ - Tests tab list is not limited if user is member of staff - and has not passed entrance exam. - """ - # Login as member of staff - self.client.logout() - staff_user = StaffFactory(course_key=self.course.id) - self.client.login(username=staff_user.username, password='test') - request = get_request_for_user(staff_user) - course_tab_list = get_course_tab_list(request, self.course) - self.assertEqual(len(course_tab_list), 5) + def test_course_tabs_list_for_staff_members(self): + """ + Tests tab list is not limited if user is member of staff + and has not passed entrance exam. + """ + # Login as member of staff + self.client.logout() + staff_user = StaffFactory(course_key=self.course.id) + self.client.login(username=staff_user.username, password='test') + request = get_request_for_user(staff_user) + course_tab_list = get_course_tab_list(request, self.course) + self.assertEqual(len(course_tab_list), 5) @attr('shard_1')