Merge pull request #10308 from edx/ziafazal/SOL-1332
SOL-1332: use a generic function to check if entrance_exam feature is enabled or not
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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, {})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user