def calendar_settings_handler(request, package_id=None, branch=None, version_guid=None, block=None, tag=None): locator, course_module = _get_locator_and_course( package_id, branch, version_guid, block, request.user ) if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': return render_to_response('settings_calendar.html', { 'package_id': package_id, 'context_course': course_module, 'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)), 'advanced_settings_url': locator.url_reverse('settings/calendar') }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): if request.method == 'GET': return JsonResponse(CourseMetadata.fetch(course_module)) else: # Whether or not to filter the tabs key out of the settings metadata filter_tabs = _config_course_advanced_components(request, course_module) try: return JsonResponse(CourseMetadata.update_from_json( course_module, request.json, filter_tabs=filter_tabs, user=request.user, )) except (TypeError, ValueError) as err: return HttpResponseBadRequest( "Incorrect setting format. {}".format(err), content_type="text/plain" )
def _delete_entrance_exam(request, course_key): """ Internal workflow operation to remove an entrance exam """ store = modulestore() course = store.get_course(course_key) if course is None: return HttpResponse(status=400) remove_entrance_exam_milestone_reference(request, course_key) # Reset the entrance exam flags on the course # Reload the course so we have the latest state course = store.get_course(course_key) if course.entrance_exam_id: metadata = { 'entrance_exam_enabled': False, 'entrance_exam_minimum_score_pct': None, 'entrance_exam_id': None, } CourseMetadata.update_from_dict(metadata, course, request.user) # Clean up any pre-existing entrance exam graders remove_entrance_exam_graders(course_key, request.user) return HttpResponse(status=204)
def test_update_from_json(self): test_model = CourseMetadata.update_from_json(self.course, { "advertised_start": "start A", "days_early_for_beta": 2, }, user=self.user) self.update_check(test_model) # try fresh fetch to ensure persistence fresh = modulestore().get_item(self.course_location) test_model = CourseMetadata.fetch(fresh) self.update_check(test_model) # now change some of the existing metadata test_model = CourseMetadata.update_from_json( fresh, { "advertised_start": "start B", "display_name": "jolly roger", }, user=self.user) self.assertIn('display_name', test_model, 'Missing editable metadata field') self.assertEqual(test_model['display_name'], 'jolly roger', "not expected value") self.assertIn('advertised_start', test_model, 'Missing revised advertised_start metadata field') self.assertEqual(test_model['advertised_start'], 'start B', "advertised_start not expected value")
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 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_validate_and_update_from_json_wrong_inputs(self): # input incorrectly formatted data is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json( self.course, { "advertised_start": {"value": 1, "display_name": "Course Advertised Start Date", }, "days_early_for_beta": {"value": "supposed to be an integer", "display_name": "Days Early for Beta Users", }, "advanced_modules": {"value": 1, "display_name": "Advanced Module List", }, }, user=self.user ) # Check valid results from validate_and_update_from_json self.assertFalse(is_valid) self.assertEqual(len(errors), 3) self.assertFalse(test_model) error_keys = set([error_obj['model']['display_name'] for error_obj in errors]) test_keys = set(['Advanced Module List', 'Course Advertised Start Date', 'Days Early for Beta Users']) self.assertEqual(error_keys, test_keys) # try fresh fetch to ensure no update happened fresh = modulestore().get_course(self.course.id) test_model = CourseMetadata.fetch(fresh) self.assertNotEqual(test_model['advertised_start']['value'], 1, 'advertised_start should not be updated to a wrong value') self.assertNotEqual(test_model['days_early_for_beta']['value'], "supposed to be an integer", 'days_early_for beta should not be updated to a wrong value')
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 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 _delete_entrance_exam(request, course_key): """ Internal workflow operation to remove an entrance exam """ store = modulestore() course = store.get_course(course_key) if course is None: return HttpResponse(status=400) remove_entrance_exam_milestone_reference(request, course_key) # Reset the entrance exam flags on the course # Reload the course so we have the latest state course = store.get_course(course_key) if course.entrance_exam_id: metadata = { 'entrance_exam_enabled': False, 'entrance_exam_minimum_score_pct': None, 'entrance_exam_id': None, } CourseMetadata.update_from_dict(metadata, course, request.user) # Clean up any pre-existing entrance exam graders remove_entrance_exam_graders(course_key, request.user) return HttpResponse(status=204)
def test_validate_and_update_from_json_correct_inputs(self): is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json( self.course, { "advertised_start": { "value": "start A" }, "days_early_for_beta": { "value": 2 }, "advanced_modules": { "value": ['combinedopenended'] }, }, user=self.user) self.assertTrue(is_valid) self.assertTrue(len(errors) == 0) self.update_check(test_model) # fresh fetch to ensure persistence fresh = modulestore().get_course(self.course.id) test_model = CourseMetadata.fetch(fresh) self.update_check(test_model) # Tab gets tested in test_advanced_settings_munge_tabs self.assertIn('advanced_modules', test_model, 'Missing advanced_modules') self.assertEqual(test_model['advanced_modules']['value'], ['combinedopenended'], 'advanced_module is not updated')
def test_update_from_json(self): test_model = CourseMetadata.update_from_json( self.course, { "advertised_start": {"value": "start A"}, "days_early_for_beta": {"value": 2}, }, user=self.user ) self.update_check(test_model) # try fresh fetch to ensure persistence fresh = modulestore().get_course(self.course.id) test_model = CourseMetadata.fetch(fresh) self.update_check(test_model) # now change some of the existing metadata test_model = CourseMetadata.update_from_json( fresh, { "advertised_start": {"value": "start B"}, "display_name": {"value": "jolly roger"}, }, user=self.user ) self.assertIn('display_name', test_model, 'Missing editable metadata field') self.assertEqual(test_model['display_name']['value'], 'jolly roger', "not expected value") self.assertIn('advertised_start', test_model, 'Missing revised advertised_start metadata field') self.assertEqual(test_model['advertised_start']['value'], 'start B', "advertised_start not expected value")
def course_audit_api(request, course_id, operation): re_json = {"success": False} request_method = request.method if request_method != "POST": return JsonResponse(re_json) # get course location and module infomation try: course_location_info = course_id.split('.') locator = BlockUsageLocator(package_id=course_id, branch='draft', version_guid=None, block_id=course_location_info[-1]) course_location = loc_mapper().translate_locator_to_location(locator) course_module = get_modulestore(course_location).get_item(course_location) instructors = CourseInstructorRole(locator).users_with_role() if len(instructors) <= 0: return JsonResponse(re_json) user = instructors[0] meta_json = {} if operation == "pass": meta_json["course_audit"] = 1 elif operation == "offline": meta_json["course_audit"] = 0 else: return JsonResponse(re_json) re_json["success"] = True CourseMetadata.update_from_json(course_module, meta_json, True, user) return JsonResponse(re_json) except: return JsonResponse(re_json)
def _delete_entrance_exam(request, course_key): """ Internal workflow operation to remove an entrance exam """ store = modulestore() course = store.get_course(course_key) if course is None: return HttpResponse(status=400) course_children = store.get_items( course_key, qualifiers={'category': 'chapter'} ) for course_child in course_children: if course_child.is_entrance_exam: delete_item(request, course_child.scope_ids.usage_id) milestones_helpers.remove_content_references(unicode(course_child.scope_ids.usage_id)) # Reset the entrance exam flags on the course # Reload the course so we have the latest state course = store.get_course(course_key) if getattr(course, 'entrance_exam_id'): metadata = { 'entrance_exam_enabled': False, 'entrance_exam_minimum_score_pct': None, 'entrance_exam_id': None, } CourseMetadata.update_from_dict(metadata, course, request.user) # Clean up any pre-existing entrance exam graders remove_entrance_exam_graders(course_key, request.user) return HttpResponse(status=204)
def test_update_from_json(self): test_model = CourseMetadata.update_from_json( self.course_location, { "advertised_start": "start A", "testcenter_info": { "c": "test" }, "days_early_for_beta": 2 }) self.update_check(test_model) # try fresh fetch to ensure persistence test_model = CourseMetadata.fetch(self.course_location) self.update_check(test_model) # now change some of the existing metadata test_model = CourseMetadata.update_from_json( self.course_location, { "advertised_start": "start B", "display_name": "jolly roger" }) self.assertIn('display_name', test_model, 'Missing editable metadata field') self.assertEqual(test_model['display_name'], 'jolly roger', "not expected value") self.assertIn('advertised_start', test_model, 'Missing revised advertised_start metadata field') self.assertEqual(test_model['advertised_start'], 'start B', "advertised_start not expected value")
def advanced_settings_handler(request, package_id=None, branch=None, version_guid=None, block=None, tag=None): """ Course settings configuration GET html: get the page json: get the model PUT, POST json: update the Course's settings. The payload is a json rep of the metadata dicts. The dict can include a "unsetKeys" entry which is a list of keys whose values to unset: i.e., revert to default """ locator, course_module = _get_locator_and_course(package_id, branch, version_guid, block, request.user) if "text/html" in request.META.get("HTTP_ACCEPT", "") and request.method == "GET": return render_to_response( "settings_advanced.html", { "context_course": course_module, "advanced_dict": json.dumps(CourseMetadata.fetch(course_module)), "advanced_settings_url": locator.url_reverse("settings/advanced"), }, ) elif "application/json" in request.META.get("HTTP_ACCEPT", ""): if request.method == "GET": return JsonResponse(CourseMetadata.fetch(course_module)) else: # Whether or not to filter the tabs key out of the settings metadata filter_tabs = _config_course_advanced_components(request, course_module) try: return JsonResponse( CourseMetadata.update_from_json(course_module, request.json, filter_tabs=filter_tabs) ) except (TypeError, ValueError) as err: return HttpResponseBadRequest("Incorrect setting format. {}".format(err), content_type="text/plain")
def test_validate_and_update_from_json_wrong_inputs(self): # input incorrectly formatted data is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json( self.course, { "advertised_start": {"value": 1, "display_name": "Course Advertised Start Date", }, "days_early_for_beta": {"value": "supposed to be an integer", "display_name": "Days Early for Beta Users", }, "advanced_modules": {"value": 1, "display_name": "Advanced Module List", }, }, user=self.user ) # Check valid results from validate_and_update_from_json self.assertFalse(is_valid) self.assertEqual(len(errors), 3) self.assertFalse(test_model) error_keys = set([error_obj['model']['display_name'] for error_obj in errors]) test_keys = set(['Advanced Module List', 'Course Advertised Start Date', 'Days Early for Beta Users']) self.assertEqual(error_keys, test_keys) # try fresh fetch to ensure no update happened fresh = modulestore().get_course(self.course.id) test_model = CourseMetadata.fetch(fresh) self.assertNotEqual(test_model['advertised_start']['value'], 1, 'advertised_start should not be updated to a wrong value') self.assertNotEqual(test_model['days_early_for_beta']['value'], "supposed to be an integer", 'days_early_for beta should not be updated to a wrong value')
def _delete_entrance_exam(request, course_key): """ Internal workflow operation to remove an entrance exam """ store = modulestore() course = store.get_course(course_key) if course is None: return HttpResponse(status=400) course_children = store.get_items(course_key, qualifiers={'category': 'chapter'}) for course_child in course_children: if course_child.is_entrance_exam: delete_item(request, course_child.scope_ids.usage_id) milestones_helpers.remove_content_references( unicode(course_child.scope_ids.usage_id)) # Reset the entrance exam flags on the course # Reload the course so we have the latest state course = store.get_course(course_key) if getattr(course, 'entrance_exam_id'): metadata = { 'entrance_exam_enabled': False, 'entrance_exam_minimum_score_pct': None, 'entrance_exam_id': None, } CourseMetadata.update_from_dict(metadata, course, request.user) # Clean up any pre-existing entrance exam graders remove_entrance_exam_graders(course_key, request.user) return HttpResponse(status=204)
def update_entrance_exam(request, course_key, exam_data): """ Operation to update course fields pertaining to entrance exams The update operation is not currently exposed directly via the API Because the operation is not exposed directly, we do not return a 200 response But we do return a 400 in the error case because the workflow is executed in a request context """ course = modulestore().get_course(course_key) if course: metadata = exam_data CourseMetadata.update_from_dict(metadata, course, request.user)
def update_entrance_exam(request, course_key, exam_data): """ Operation to update course fields pertaining to entrance exams The update operation is not currently exposed directly via the API Because the operation is not exposed directly, we do not return a 200 response But we do return a 400 in the error case because the workflow is executed in a request context """ course = modulestore().get_course(course_key) if course: metadata = exam_data CourseMetadata.update_from_dict(metadata, course, request.user)
def test_fetch_initial_fields(self): test_model = CourseMetadata.fetch(self.course.location) self.assertIn("display_name", test_model, "Missing editable metadata field") self.assertEqual(test_model["display_name"], "Robot Super Course", "not expected value") test_model = CourseMetadata.fetch(self.fullcourse_location) self.assertNotIn("graceperiod", test_model, "blacklisted field leaked in") self.assertIn("display_name", test_model, "full missing editable metadata field") self.assertEqual(test_model["display_name"], "Robot Super Course", "not expected value") self.assertIn("rerandomize", test_model, "Missing rerandomize metadata field") self.assertIn("showanswer", test_model, "showanswer field ") self.assertIn("xqa_key", test_model, "xqa_key field ")
def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=None): """ Internal workflow operation to create an entrance exam """ # Provide a default value for the minimum score percent if nothing specified if entrance_exam_minimum_score_pct is None: entrance_exam_minimum_score_pct = float(settings.ENTRANCE_EXAM_MIN_SCORE_PCT) # Confirm the course exists course = modulestore().get_course(course_key) if course is None: return HttpResponse(status=400) # Create the entrance exam item (currently it's just a chapter) payload = { "category": "chapter", "display_name": "Entrance Exam", "parent_locator": unicode(course.location), "is_entrance_exam": True, "in_entrance_exam": True, } factory = RequestFactory() internal_request = factory.post("/", json.dumps(payload), content_type="application/json") internal_request.user = request.user created_item = json.loads(create_item(internal_request).content) # Set the entrance exam metadata flags for this course # Reload the course so we don't overwrite the new child reference course = modulestore().get_course(course_key) metadata = { "entrance_exam_enabled": True, "entrance_exam_minimum_score_pct": entrance_exam_minimum_score_pct / 100, "entrance_exam_id": created_item["locator"], } CourseMetadata.update_from_dict(metadata, course, request.user) # Add an entrance exam milestone if one does not already exist milestone_namespace = generate_milestone_namespace(NAMESPACE_CHOICES["ENTRANCE_EXAM"], course_key) milestones = milestones_api.get_milestones(milestone_namespace) if len(milestones): milestone = milestones[0] else: description = "Autogenerated during {} entrance exam creation.".format(unicode(course.id)) milestone = milestones_api.add_milestone( {"name": "Completed Course Entrance Exam", "namespace": milestone_namespace, "description": description} ) relationship_types = milestones_api.get_milestone_relationship_types() milestones_api.add_course_milestone(unicode(course.id), relationship_types["REQUIRES"], milestone) milestones_api.add_course_content_milestone( unicode(course.id), created_item["locator"], relationship_types["FULFILLS"], milestone ) return HttpResponse(status=201)
def test_fetch_initial_fields(self): test_model = CourseMetadata.fetch(self.course.location) self.assertIn('display_name', test_model, 'Missing editable metadata field') self.assertEqual(test_model['display_name'], 'Robot Super Course', "not expected value") test_model = CourseMetadata.fetch(self.fullcourse_location) self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in') self.assertIn('display_name', test_model, 'full missing editable metadata field') self.assertEqual(test_model['display_name'], 'Testing', "not expected value") self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field') self.assertIn('showanswer', test_model, 'showanswer field ') self.assertIn('xqa_key', test_model, 'xqa_key field ')
def test_fetch_initial_fields(self): test_model = CourseMetadata.fetch(self.course) self.assertIn('display_name', test_model, 'Missing editable metadata field') self.assertEqual(test_model['display_name']['value'], self.course.display_name) test_model = CourseMetadata.fetch(self.fullcourse) self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in') self.assertIn('display_name', test_model, 'full missing editable metadata field') self.assertEqual(test_model['display_name']['value'], self.fullcourse.display_name) self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field') self.assertIn('showanswer', test_model, 'showanswer field ') self.assertIn('xqa_key', test_model, 'xqa_key field ')
def test_fetch_initial_fields(self): test_model = CourseMetadata.fetch(self.course) self.assertIn('display_name', test_model, 'Missing editable metadata field') self.assertEqual(test_model['display_name']['value'], self.course.display_name) test_model = CourseMetadata.fetch(self.fullcourse) self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in') self.assertIn('display_name', test_model, 'full missing editable metadata field') self.assertEqual(test_model['display_name']['value'], self.fullcourse.display_name) self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field') self.assertIn('showanswer', test_model, 'showanswer field ') self.assertIn('xqa_key', test_model, 'xqa_key field ')
def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=None): """ Internal workflow operation to create an entrance exam """ # Provide a default value for the minimum score percent if nothing specified if entrance_exam_minimum_score_pct is None: entrance_exam_minimum_score_pct = _get_default_entrance_exam_minimum_pct() # Confirm the course exists course = modulestore().get_course(course_key) if course is None: return HttpResponse(status=400) # Create the entrance exam item (currently it's just a chapter) payload = { 'category': "chapter", 'display_name': _("Entrance Exam"), 'parent_locator': unicode(course.location), 'is_entrance_exam': True, 'in_entrance_exam': True, } parent_locator = unicode(course.location) created_block = create_xblock( parent_locator=parent_locator, user=request.user, category='chapter', display_name=_('Entrance Exam'), is_entrance_exam=True ) # Set the entrance exam metadata flags for this course # Reload the course so we don't overwrite the new child reference course = modulestore().get_course(course_key) metadata = { 'entrance_exam_enabled': True, 'entrance_exam_minimum_score_pct': unicode(entrance_exam_minimum_score_pct), 'entrance_exam_id': unicode(created_block.location), } CourseMetadata.update_from_dict(metadata, course, request.user) # Create the entrance exam section item. create_xblock( parent_locator=unicode(created_block.location), user=request.user, category='sequential', display_name=_('Entrance Exam - Subsection') ) add_entrance_exam_milestone(course.id, created_block) return HttpResponse(status=201)
def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=None): """ Internal workflow operation to create an entrance exam """ # Provide a default value for the minimum score percent if nothing specified if entrance_exam_minimum_score_pct is None: entrance_exam_minimum_score_pct = _get_default_entrance_exam_minimum_pct( ) # Confirm the course exists course = modulestore().get_course(course_key) if course is None: return HttpResponse(status=400) # Create the entrance exam item (currently it's just a chapter) payload = { 'category': "chapter", 'display_name': _("Entrance Exam"), 'parent_locator': unicode(course.location), 'is_entrance_exam': True, 'in_entrance_exam': True, } parent_locator = unicode(course.location) created_block = create_xblock(parent_locator=parent_locator, user=request.user, category='chapter', display_name=_('Entrance Exam'), is_entrance_exam=True) # Set the entrance exam metadata flags for this course # Reload the course so we don't overwrite the new child reference course = modulestore().get_course(course_key) metadata = { 'entrance_exam_enabled': True, 'entrance_exam_minimum_score_pct': unicode(entrance_exam_minimum_score_pct), 'entrance_exam_id': unicode(created_block.location), } CourseMetadata.update_from_dict(metadata, course, request.user) # Create the entrance exam section item. create_xblock(parent_locator=unicode(created_block.location), user=request.user, category='sequential', display_name=_('Entrance Exam - Subsection')) add_entrance_exam_milestone(course.id, created_block) return HttpResponse(status=201)
def customize_settings(request, course_key_string): course_key = SlashSeparatedCourseKey.from_deprecated_string( course_key_string) with modulestore().bulk_operations(course_key): course_module = get_course_and_check_access(course_key, request.user) additional_info = { 'is_new': request.POST.get('is_new', False), 'invitation_only': request.POST.get('invitation_only', False), 'manager_only': request.POST.get('manager_only', False) } CourseMetadata.update_from_dict(additional_info, course_module, request.user) return JsonResponse({'data': 'data'})
def advanced_settings_handler(request, package_id=None, branch=None, version_guid=None, block=None, tag=None): """ Course settings configuration GET html: get the page json: get the model PUT, POST json: update the Course's settings. The payload is a json rep of the metadata dicts. The dict can include a "unsetKeys" entry which is a list of keys whose values to unset: i.e., revert to default """ locator, course_module = _get_locator_and_course(package_id, branch, version_guid, block, request.user) if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': return render_to_response( 'settings_advanced.html', { 'context_course': course_module, 'advanced_dict': json.dumps( CourseMetadata.fetch(course_module)), 'advanced_settings_url': locator.url_reverse('settings/advanced') }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): if request.method == 'GET': return JsonResponse(CourseMetadata.fetch(course_module)) else: # Whether or not to filter the tabs key out of the settings metadata filter_tabs = _config_course_advanced_components( request, course_module) try: return JsonResponse( CourseMetadata.update_from_json( course_module, request.json, filter_tabs=filter_tabs, user=request.user, )) except (TypeError, ValueError) as err: return HttpResponseBadRequest( "Incorrect setting format. {}".format(err), content_type="text/plain")
def test_import_delete_pre_exiting_entrance_exam(self): """ Check that pre existed entrance exam content should be overwrite with the imported course. """ exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id)) resp = self.client.post(exam_url, {'entrance_exam_minimum_score_pct': 0.5}, http_accept='application/json') self.assertEqual(resp.status_code, 201) # 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.assertEqual(metadata['entrance_exam_minimum_score_pct']['value'], 0.5) 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'], milestones_helpers.get_milestone_relationship_types()['FULFILLS'] ) self.assertTrue(len(content_milestones)) # Now import entrance exam course with open(self.entrance_exam_tar) as gtar: args = {"name": self.entrance_exam_tar, "course-data": [gtar]} resp = self.client.post(self.url, args) self.assertEquals(resp.status_code, 200) course = self.store.get_course(self.course.id) self.assertIsNotNone(course) self.assertEquals(course.entrance_exam_enabled, True) self.assertEquals(course.entrance_exam_minimum_score_pct, 0.7)
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( six.text_type(self.course.id)))) content_milestones = milestones_helpers.get_course_content_milestones( six.text_type(self.course.id), metadata['entrance_exam_id']['value'], self.milestone_relationship_types['FULFILLS']) self.assertTrue(len(content_milestones))
def test_import_delete_pre_exiting_entrance_exam(self): """ Check that pre existed entrance exam content should be overwrite with the imported course. """ exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id)) resp = self.client.post(exam_url, {'entrance_exam_minimum_score_pct': 0.5}, http_accept='application/json') self.assertEqual(resp.status_code, 201) # 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.assertEqual(metadata['entrance_exam_minimum_score_pct']['value'], 0.5) 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'], milestones_helpers.get_milestone_relationship_types()['FULFILLS']) self.assertTrue(len(content_milestones)) # Now import entrance exam course with open(self.entrance_exam_tar) as gtar: args = {"name": self.entrance_exam_tar, "course-data": [gtar]} resp = self.client.post(self.url, args) self.assertEquals(resp.status_code, 200) course = self.store.get_course(self.course.id) self.assertIsNotNone(course) self.assertEquals(course.entrance_exam_enabled, True) self.assertEquals(course.entrance_exam_minimum_score_pct, 0.7)
def post(self, request, course_id): """ POST handler """ serializer = ProctoredExamSettingsSerializer if request.user.is_staff else LimitedProctoredExamSettingsSerializer exam_config = serializer( data=request.data.get('proctored_exam_settings', {})) valid_request = exam_config.is_valid() if not request.user.is_staff and valid_request and ProctoredExamSettingsSerializer( data=request.data.get('proctored_exam_settings', {})).is_valid(): return Response(status=status.HTTP_403_FORBIDDEN) with modulestore().bulk_operations(CourseKey.from_string(course_id)): course_module = self._get_and_validate_course_access( request.user, course_id) course_metadata = CourseMetadata().fetch_all(course_module) models_to_update = {} for setting_key, value in exam_config.data.items(): model = course_metadata.get(setting_key) if model: models_to_update[setting_key] = copy.deepcopy(model) models_to_update[setting_key]['value'] = value # validate data formats and update the course module object is_valid, errors, updated_data = CourseMetadata.validate_and_update_from_json( course_module, models_to_update, user=request.user, ) if not is_valid: error_messages = [{ error.get('key'): error.get('message') } for error in errors] return Response({'detail': error_messages}, status=status.HTTP_400_BAD_REQUEST) # save to mongo modulestore().update_item(course_module, request.user.id) # merge updated settings with all existing settings. # do this because fields that could not be modified are excluded from the result course_metadata = {**course_metadata, **updated_data} updated_setttings = self._get_proctored_exam_setting_values( course_metadata) serializer = ProctoredExamSettingsSerializer(updated_setttings) return Response({'proctored_exam_settings': serializer.data})
def test_update_from_json(self): test_model = CourseMetadata.update_from_json( self.course.location, {"advertised_start": "start A", "testcenter_info": {"c": "test"}, "days_early_for_beta": 2}, ) self.update_check(test_model) # try fresh fetch to ensure persistence test_model = CourseMetadata.fetch(self.course.location) self.update_check(test_model) # now change some of the existing metadata test_model = CourseMetadata.update_from_json( self.course.location, {"advertised_start": "start B", "display_name": "jolly roger"} ) self.assertIn("display_name", test_model, "Missing editable metadata field") self.assertEqual(test_model["display_name"], "jolly roger", "not expected value") self.assertIn("advertised_start", test_model, "Missing revised advertised_start metadata field") self.assertEqual(test_model["advertised_start"], "start B", "advertised_start not expected value")
def test_fetch_initial_fields(self): test_model = CourseMetadata.fetch(self.course_location) self.assertIn('display_name', test_model, 'Missing editable metadata field') self.assertEqual(test_model['display_name'], 'Robot Super Course', "not expected value") test_model = CourseMetadata.fetch(self.fullcourse_location) self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in') self.assertIn('display_name', test_model, 'full missing editable metadata field') self.assertEqual(test_model['display_name'], 'Testing', "not expected value") self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field') self.assertIn('showanswer', test_model, 'showanswer field ') self.assertIn('xqa_key', test_model, 'xqa_key field ')
def advanced_settings_handler(request, course_key_string): """ Course settings configuration GET html: get the page json: get the model PUT, POST json: update the Course's settings. The payload is a json rep of the metadata dicts. """ course_key = CourseKey.from_string(course_key_string) course_module = _get_course_module(course_key, request.user) if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': return render_to_response( 'settings_advanced.html', { 'context_course': course_module, 'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)), 'advanced_settings_url': reverse_course_url('advanced_settings_handler', course_key) }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): if request.method == 'GET': return JsonResponse(CourseMetadata.fetch(course_module)) else: try: # Whether or not to filter the tabs key out of the settings metadata filter_tabs = _config_course_advanced_components( request, course_module) return JsonResponse( CourseMetadata.update_from_json( course_module, request.json, filter_tabs=filter_tabs, user=request.user, )) except (TypeError, ValueError) as err: return HttpResponseBadRequest(django.utils.html.escape( err.message), content_type="text/plain")
def test_delete_key(self): test_model = CourseMetadata.update_from_json(self.fullcourse, {"unsetKeys": ["showanswer", "xqa_key"]}) # ensure no harm self.assertNotIn("graceperiod", test_model, "blacklisted field leaked in") self.assertIn("display_name", test_model, "full missing editable metadata field") self.assertEqual(test_model["display_name"], "Robot Super Course", "not expected value") self.assertIn("rerandomize", test_model, "Missing rerandomize metadata field") # check for deletion effectiveness self.assertEqual("finished", test_model["showanswer"], "showanswer field still in") self.assertEqual(None, test_model["xqa_key"], "xqa_key field still in")
def test_delete_key(self): test_model = CourseMetadata.delete_key(self.fullcourse_location, {'deleteKeys': ['doesnt_exist', 'showanswer', 'xqa_key']}) # ensure no harm self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in') self.assertIn('display_name', test_model, 'full missing editable metadata field') self.assertEqual(test_model['display_name'], 'Robot Super Course', "not expected value") self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field') # check for deletion effectiveness self.assertEqual('finished', test_model['showanswer'], 'showanswer field still in') self.assertEqual(None, test_model['xqa_key'], 'xqa_key field still in')
def advanced_settings_handler(request, course_id=None, branch=None, version_guid=None, block=None, tag=None): """ Course settings configuration GET html: get the page json: get the model PUT, POST json: update the Course's settings. The payload is a json rep of the metadata dicts. The dict can include a "unsetKeys" entry which is a list of keys whose values to unset: i.e., revert to default """ locator = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block) if not has_access(request.user, locator): raise PermissionDenied() course_old_location = loc_mapper().translate_locator_to_location(locator) course_module = modulestore().get_item(course_old_location) if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': return render_to_response('settings_advanced.html', { 'context_course': course_module, 'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)), 'advanced_settings_url': locator.url_reverse('settings/advanced') }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): if request.method == 'GET': return JsonResponse(CourseMetadata.fetch(course_module)) else: # Whether or not to filter the tabs key out of the settings metadata filter_tabs = _config_course_advanced_components(request, course_module) try: return JsonResponse(CourseMetadata.update_from_json( course_module, request.json, filter_tabs=filter_tabs )) except (TypeError, ValueError) as err: return HttpResponseBadRequest( "Incorrect setting format. {}".format(err), content_type="text/plain" )
def test_update_from_json_filtered_off(self): """ If feature flag is on, then giturl must not be updated. """ test_model = CourseMetadata.update_from_json(self.course, { "giturl": { "value": "http://example.com" }, }, user=self.user) self.assertNotIn('giturl', test_model)
def test_update_from_json_filtered_edxnotes_off(self): """ If feature flag is off, then edxnotes must not be updated. """ test_model = CourseMetadata.update_from_json( self.course, { "edxnotes": {"value": "true"}, }, user=self.user ) self.assertNotIn('edxnotes', test_model)
def test_delete_key(self): test_model = CourseMetadata.delete_key( self.fullcourse_location, {"deleteKeys": ["doesnt_exist", "showanswer", "xqa_key"]} ) # ensure no harm self.assertNotIn("graceperiod", test_model, "blacklisted field leaked in") self.assertIn("display_name", test_model, "full missing editable metadata field") self.assertEqual(test_model["display_name"], "Testing", "not expected value") self.assertIn("rerandomize", test_model, "Missing rerandomize metadata field") # check for deletion effectiveness self.assertEqual("closed", test_model["showanswer"], "showanswer field still in") self.assertEqual(None, test_model["xqa_key"], "xqa_key field still in")
def test_update_from_json_filtered_off(self): """ If feature flag is on, then giturl must not be updated. """ test_model = CourseMetadata.update_from_json( self.course, { "giturl": {"value": "http://example.com"}, }, user=self.user ) self.assertNotIn('giturl', test_model)
def test_update_from_json_filtered_edxnotes_off(self): """ If feature flag is off, then edxnotes must not be updated. """ test_model = CourseMetadata.update_from_json( self.course, { "edxnotes": {"value": "true"}, }, user=self.user ) self.assertNotIn('edxnotes', test_model)
def test_validate_update_filtered_on(self): """ If feature flag is on, then giturl must not be filtered. """ # pylint: disable=unused-variable is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json( self.course, { "giturl": {"value": "http://example.com"}, }, user=self.user ) self.assertIn('giturl', test_model)
def test_validate_update_filtered_edxnotes_on(self): """ If feature flag is on, then edxnotes must not be filtered. """ # pylint: disable=unused-variable is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json( self.course, { "edxnotes": {"value": "true"}, }, user=self.user ) self.assertIn('edxnotes', test_model)
def test_validate_update_filtered_edxnotes_on(self): """ If feature flag is on, then edxnotes must not be filtered. """ # pylint: disable=unused-variable is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json( self.course, { "edxnotes": {"value": "true"}, }, user=self.user ) self.assertIn('edxnotes', test_model)
def test_validate_and_update_from_json_correct_inputs(self): is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json( self.course, { "advertised_start": {"value": "start A"}, "days_early_for_beta": {"value": 2}, "advanced_modules": {"value": ['combinedopenended']}, }, user=self.user ) self.assertTrue(is_valid) self.assertTrue(len(errors) == 0) self.update_check(test_model) # fresh fetch to ensure persistence fresh = modulestore().get_course(self.course.id) test_model = CourseMetadata.fetch(fresh) self.update_check(test_model) # Tab gets tested in test_advanced_settings_munge_tabs self.assertIn('advanced_modules', test_model, 'Missing advanced_modules') self.assertEqual(test_model['advanced_modules']['value'], ['combinedopenended'], 'advanced_module is not updated')
def test_validate_update_filtered_on(self): """ If feature flag is on, then giturl must not be filtered. """ # pylint: disable=unused-variable is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json( self.course, { "giturl": {"value": "http://example.com"}, }, user=self.user ) self.assertIn('giturl', test_model)
def advanced_settings_handler(request, course_key_string): """ Course settings configuration GET html: get the page json: get the model PUT, POST json: update the Course's settings. The payload is a json rep of the metadata dicts. The dict can include a "unsetKeys" entry which is a list of keys whose values to unset: i.e., revert to default """ course_key = CourseKey.from_string(course_key_string) course_module = _get_course_module(course_key, request.user) if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': return render_to_response('settings_advanced.html', { 'context_course': course_module, 'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)), 'advanced_settings_url': reverse_course_url('advanced_settings_handler', course_key) }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): if request.method == 'GET': return JsonResponse(CourseMetadata.fetch(course_module)) else: # Whether or not to filter the tabs key out of the settings metadata filter_tabs = _config_course_advanced_components(request, course_module) try: return JsonResponse(CourseMetadata.update_from_json( course_module, request.json, filter_tabs=filter_tabs, user=request.user, )) except (TypeError, ValueError) as err: return HttpResponseBadRequest( "Incorrect setting format. {}".format(err), content_type="text/plain" )
def _delete_entrance_exam(request, course_key): """ Internal workflow operation to remove an entrance exam """ store = modulestore() course = store.get_course(course_key) if course is None: return HttpResponse(status=400) course_children = store.get_items(course_key, qualifiers={"category": "chapter"}) for course_child in course_children: if course_child.is_entrance_exam: delete_item(request, course_child.scope_ids.usage_id) milestones_api.remove_content_references(unicode(course_child.scope_ids.usage_id)) # Reset the entrance exam flags on the course # Reload the course so we have the latest state course = store.get_course(course_key) if getattr(course, "entrance_exam_id"): metadata = {"entrance_exam_enabled": False, "entrance_exam_minimum_score_pct": None, "entrance_exam_id": None} CourseMetadata.update_from_dict(metadata, course, request.user) return HttpResponse(status=204)
def advanced_settings_handler(request, course_key_string): """ Course settings configuration GET html: get the page json: get the model PUT, POST json: update the Course's settings. The payload is a json rep of the metadata dicts. """ course_key = CourseKey.from_string(course_key_string) course_module = _get_course_module(course_key, request.user) if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': return render_to_response('settings_advanced.html', { 'context_course': course_module, 'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)), 'advanced_settings_url': reverse_course_url('advanced_settings_handler', course_key) }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): if request.method == 'GET': return JsonResponse(CourseMetadata.fetch(course_module)) else: try: # Whether or not to filter the tabs key out of the settings metadata filter_tabs = _config_course_advanced_components(request, course_module) return JsonResponse(CourseMetadata.update_from_json( course_module, request.json, filter_tabs=filter_tabs, user=request.user, )) except (TypeError, ValueError) as err: return HttpResponseBadRequest( django.utils.html.escape(err.message), content_type="text/plain" )
def course_config_advanced_page(request, org, course, name): """ Send models and views as well as html for editing the advanced course settings to the client. org, course, name: Attributes of the Location for the item to edit """ location = get_location_and_verify_access(request, org, course, name) course_module = modulestore().get_item(location) return render_to_response('settings_advanced.html', { 'context_course': course_module, 'course_location': location, 'advanced_dict': json.dumps(CourseMetadata.fetch(location)), })
def course_config_advanced_page(request, org, course, name): """ Send models and views as well as html for editing the advanced course settings to the client. org, course, name: Attributes of the Location for the item to edit """ location = get_location_and_verify_access(request, org, course, name) course_module = modulestore().get_item(location) return render_to_response('settings_advanced.html', { 'context_course': course_module, 'course_location': location, 'advanced_dict': json.dumps(CourseMetadata.fetch(location)), })
def course_audit_api(request, course_id, operation): re_json = {"success": False} request_method = request.method if request_method != "POST": return JsonResponse(re_json) # get course location and module infomation try: course_location_info = course_id.split('.') locator = BlockUsageLocator(package_id=course_id, branch='draft', version_guid=None, block_id=course_location_info[-1]) course_location = loc_mapper().translate_locator_to_location(locator) course_module = get_modulestore(course_location).get_item( course_location) instructors = CourseInstructorRole(locator).users_with_role() if len(instructors) <= 0: return JsonResponse(re_json) user = instructors[0] meta_json = {} if operation == "pass": meta_json["course_audit"] = 1 elif operation == "offline": meta_json["course_audit"] = 0 else: return JsonResponse(re_json) re_json["success"] = True CourseMetadata.update_from_json(course_module, meta_json, True, user) return JsonResponse(re_json) except: return JsonResponse(re_json)
def mobi_get_topics(request, course_id): """ Return course topics """ course_id = course_id.replace('.', '/') nr_transaction = newrelic.agent.current_transaction() try: course = get_course_with_access(request.user, course_id, "load_forum") except: return JsonResponse({"success": False, 'errmsg': "can not find a course with " + course_id.replace('/', '.') + " id"}) return JsonResponse({ "topic-list": CourseMetadata.fetch(course).pop("discussion_topics"), "success": True })
def get(self, request, course_id): """ GET handler """ with modulestore().bulk_operations(CourseKey.from_string(course_id)): course_module = self._get_and_validate_course_access( request.user, course_id) course_metadata = CourseMetadata().fetch_all(course_module) proctored_exam_settings = self._get_proctored_exam_setting_values( course_metadata) data = {} data['proctored_exam_settings'] = proctored_exam_settings data['available_proctoring_providers'] = get_available_providers() data['course_start_date'] = course_metadata['start'].get('value') serializer = ProctoredExamConfigurationSerializer(data) return Response(serializer.data)
def mobi_get_topics(request, course_id): """ Return course topics """ course_id = course_id.replace('.', '/') nr_transaction = newrelic.agent.current_transaction() try: course = get_course_with_access(request.user, course_id, "load_forum") except: return JsonResponse({ "success": False, 'errmsg': "can not find a course with " + course_id.replace('/', '.') + " id" }) return JsonResponse({ "topic-list": CourseMetadata.fetch(course).pop("discussion_topics"), "success": True })
def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=None): """ Internal workflow operation to create an entrance exam """ # Provide a default value for the minimum score percent if nothing specified if entrance_exam_minimum_score_pct is None: entrance_exam_minimum_score_pct = _get_default_entrance_exam_minimum_pct( ) # Confirm the course exists course = modulestore().get_course(course_key) if course is None: return HttpResponse(status=400) # Create the entrance exam item (currently it's just a chapter) payload = { 'category': "chapter", 'display_name': _("Entrance Exam"), 'parent_locator': unicode(course.location), 'is_entrance_exam': True, 'in_entrance_exam': True, } parent_locator = unicode(course.location) created_block = create_xblock(parent_locator=parent_locator, user=request.user, category='chapter', display_name=_('Entrance Exam'), is_entrance_exam=True) # Set the entrance exam metadata flags for this course # Reload the course so we don't overwrite the new child reference course = modulestore().get_course(course_key) metadata = { 'entrance_exam_enabled': True, 'entrance_exam_minimum_score_pct': unicode(entrance_exam_minimum_score_pct), 'entrance_exam_id': unicode(created_block.location), } CourseMetadata.update_from_dict(metadata, course, request.user) # Create the entrance exam section item. create_xblock(parent_locator=unicode(created_block.location), user=request.user, category='sequential', display_name=_('Entrance Exam - Subsection')) # Add an entrance exam milestone if one does not already exist namespace_choices = milestones_helpers.get_namespace_choices() milestone_namespace = milestones_helpers.generate_milestone_namespace( namespace_choices.get('ENTRANCE_EXAM'), course_key) milestones = milestones_helpers.get_milestones(milestone_namespace) if len(milestones): milestone = milestones[0] else: description = 'Autogenerated during {} entrance exam creation.'.format( unicode(course.id)) milestone = milestones_helpers.add_milestone({ 'name': _('Completed Course Entrance Exam'), 'namespace': milestone_namespace, 'description': description }) relationship_types = milestones_helpers.get_milestone_relationship_types() milestones_helpers.add_course_milestone(unicode(course.id), relationship_types['REQUIRES'], milestone) milestones_helpers.add_course_content_milestone( unicode(course.id), unicode(created_block.location), relationship_types['FULFILLS'], milestone) return HttpResponse(status=201)
def course_advanced_updates(request, org, course, name): """ restful CRUD operations on metadata. The payload is a json rep of the metadata dicts. For delete, otoh, the payload is either a key or a list of keys to delete. org, course: Attributes of the Location for the item to edit """ location = get_location_and_verify_access(request, org, course, name) real_method = get_request_method(request) if real_method == 'GET': return HttpResponse(json.dumps(CourseMetadata.fetch(location)), mimetype="application/json") elif real_method == 'DELETE': return HttpResponse(json.dumps( CourseMetadata.delete_key(location, json.loads(request.body))), mimetype="application/json") elif real_method == 'POST' or real_method == 'PUT': # NOTE: request.POST is messed up because expect_json # cloned_request.POST.copy() is creating a defective entry w/ the whole payload as the key request_body = json.loads(request.body) # Whether or not to filter the tabs key out of the settings metadata filter_tabs = True # Check to see if the user instantiated any advanced components. This is a hack # that does the following : # 1) adds/removes the open ended panel tab to a course automatically if the user # has indicated that they want to edit the combinedopendended or peergrading module # 2) adds/removes the notes panel tab to a course automatically if the user has # indicated that they want the notes module enabled in their course # TODO refactor the above into distinct advanced policy settings if ADVANCED_COMPONENT_POLICY_KEY in request_body: # Get the course so that we can scrape current tabs course_module = modulestore().get_item(location) # Maps tab types to components tab_component_map = { 'open_ended': OPEN_ENDED_COMPONENT_TYPES, 'notes': NOTE_COMPONENT_TYPES, } # Check to see if the user instantiated any notes or open ended components for tab_type in tab_component_map.keys(): component_types = tab_component_map.get(tab_type) found_ac_type = False for ac_type in component_types: if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]: # Add tab to the course if needed changed, new_tabs = add_extra_panel_tab( tab_type, course_module) # If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json if changed: course_module.tabs = new_tabs request_body.update({'tabs': new_tabs}) # Indicate that tabs should not be filtered out of the metadata filter_tabs = False # Set this flag to avoid the tab removal code below. found_ac_type = True break # If we did not find a module type in the advanced settings, # we may need to remove the tab from the course. if not found_ac_type: # Remove tab from the course if needed changed, new_tabs = remove_extra_panel_tab( tab_type, course_module) if changed: course_module.tabs = new_tabs request_body.update({'tabs': new_tabs}) # Indicate that tabs should *not* be filtered out of the metadata filter_tabs = False try: response_json = json.dumps( CourseMetadata.update_from_json(location, request_body, filter_tabs=filter_tabs)) except (TypeError, ValueError), e: return HttpResponseBadRequest("Incorrect setting format. " + str(e), content_type="text/plain") return HttpResponse(response_json, mimetype="application/json")
def proctored_exam_settings(request, course_id): """ A view for retrieving information about proctored exam settings for a course. Path: ``/api/contentstore/v1/proctored_exam_settings/{course_id}`` Accepts: [GET] ------------------------------------------------------------------------------------ GET ------------------------------------------------------------------------------------ **Returns** * 200: OK - Contains a set of course proctored exam settings. * 401: The requesting user is not authenticated. * 403: The requesting user lacks access to the course. * 404: The requested course does not exist. **Response** In the case of a 200 response code, the response will proctored exam settings data as well as other metadata about the course or the requesting user that are necessary for rendering the settings page. **Example** { "proctored_exam_settings": { "enable_proctored_exams": true, "allow_proctoring_opt_out": true, "proctoring_provider": "mockprock", "proctoring_escalation_email": null, "create_zendesk_tickets": true }, "available_proctoring_providers": [ "mockprock", "proctortrack" ], "course_start_date": "2013-02-05T05:00:00Z", "is_staff": true } """ course_key = CourseKey.from_string(course_id) with modulestore().bulk_operations(course_key): if request.method == 'GET': course_module = get_course_and_check_access( course_key, request.user) if not course_module: return Response( 'Course with course_id {} does not exist.'.format( course_id), status=status.HTTP_404_NOT_FOUND) course_metadata = CourseMetadata().fetch_all(course_module) data = {} # specify only the advanced settings we want to return proctored_exam_settings_advanced_settings_keys = [ 'enable_proctored_exams', 'allow_proctoring_opt_out', 'proctoring_provider', 'proctoring_escalation_email', 'create_zendesk_tickets', 'start' ] proctored_exam_settings_data = { setting_key: setting_value.get('value') for (setting_key, setting_value) in course_metadata.items() if setting_key in proctored_exam_settings_advanced_settings_keys } data['proctored_exam_settings'] = proctored_exam_settings_data data['available_proctoring_providers'] = get_available_providers() # move start key:value out of proctored_exam_settings dictionary and change key data['course_start_date'] = proctored_exam_settings_data['start'] del data['proctored_exam_settings']['start'] data['is_staff'] = request.user.is_staff serializer = ProctoredExamConfigurationSerializer(data) return Response(serializer.data)
def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=None): """ Internal workflow operation to create an entrance exam """ # Provide a default value for the minimum score percent if nothing specified if entrance_exam_minimum_score_pct is None: entrance_exam_minimum_score_pct = float( settings.ENTRANCE_EXAM_MIN_SCORE_PCT) # Confirm the course exists course = modulestore().get_course(course_key) if course is None: return HttpResponse(status=400) # Create the entrance exam item (currently it's just a chapter) payload = { 'category': "chapter", 'display_name': "Entrance Exam", 'parent_locator': unicode(course.location), 'is_entrance_exam': True, 'in_entrance_exam': True, } factory = RequestFactory() internal_request = factory.post('/', json.dumps(payload), content_type="application/json") internal_request.user = request.user created_item = json.loads(create_item(internal_request).content) # Set the entrance exam metadata flags for this course # Reload the course so we don't overwrite the new child reference course = modulestore().get_course(course_key) metadata = { 'entrance_exam_enabled': True, 'entrance_exam_minimum_score_pct': entrance_exam_minimum_score_pct / 100, 'entrance_exam_id': created_item['locator'], } CourseMetadata.update_from_dict(metadata, course, request.user) # Add an entrance exam milestone if one does not already exist milestone_namespace = generate_milestone_namespace( NAMESPACE_CHOICES['ENTRANCE_EXAM'], course_key) milestones = milestones_api.get_milestones(milestone_namespace) if len(milestones): milestone = milestones[0] else: description = 'Autogenerated during {} entrance exam creation.'.format( unicode(course.id)) milestone = milestones_api.add_milestone({ 'name': 'Completed Course Entrance Exam', 'namespace': milestone_namespace, 'description': description }) relationship_types = milestones_api.get_milestone_relationship_types() milestones_api.add_course_milestone(unicode(course.id), relationship_types['REQUIRES'], milestone) milestones_api.add_course_content_milestone(unicode(course.id), created_item['locator'], relationship_types['FULFILLS'], milestone) return HttpResponse(status=201)