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 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 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_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 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 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_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_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_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_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.update_from_json( self.fullcourse, {"unsetKeys": ['showanswer', 'xqa_key']}, user=self.user ) # 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, 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 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 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 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 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 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 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 create_new_course(request): """ Create a new course. Returns the URL for the course overview page. """ if not auth.has_access(request.user, CourseCreatorRole()): raise PermissionDenied() org = request.json.get('org') number = request.json.get('number') display_name = request.json.get('display_name') course_category = request.json.get('course_category') course_level = request.json.get('course_level') course_price = request.json.get('course_price') run = request.json.get('run') try: dest_location = Location(u'i4x', org, number, u'course', run) except InvalidLocationError as error: return JsonResponse({ "ErrMsg": _("Unable to create course '{name}'.\n\n{err}").format( name=display_name, err=error.message)}) # see if the course already exists existing_course = None try: existing_course = modulestore('direct').get_item(dest_location) except ItemNotFoundError: pass if existing_course is not None: return JsonResponse({ 'ErrMsg': _( 'There is already a course defined with the same ' 'organization, course number, and course run. Please ' 'change either organization or course number to be ' 'unique.' ), 'OrgErrMsg': _( 'Please change either the organization or ' 'course number so that it is unique.' ), 'CourseErrMsg': _( 'Please change either the organization or ' 'course number so that it is unique.' ), }) # dhm: this query breaks the abstraction, but I'll fix it when I do my suspended refactoring of this # file for new locators. get_items should accept a query rather than requiring it be a legal location course_search_location = bson.son.SON({ '_id.tag': 'i4x', # cannot pass regex to Location constructor; thus this hack # pylint: disable=E1101 '_id.org': re.compile(u'^{}$'.format(dest_location.org), re.IGNORECASE | re.UNICODE), # pylint: disable=E1101 '_id.course': re.compile(u'^{}$'.format(dest_location.course), re.IGNORECASE | re.UNICODE), '_id.category': 'course', }) courses = modulestore().collection.find(course_search_location, fields=('_id')) if courses.count() > 0: return JsonResponse({ 'ErrMsg': _( 'There is already a course defined with the same ' 'organization and course number. Please ' 'change at least one field to be unique.'), 'OrgErrMsg': _( 'Please change either the organization or ' 'course number so that it is unique.'), 'CourseErrMsg': _( 'Please change either the organization or ' 'course number so that it is unique.'), }) # instantiate the CourseDescriptor and then persist it # note: no system to pass if display_name is None and course_category is None and course_level is None: metadata = {} else: metadata = {'display_name': display_name, 'course_category': course_category, 'course_level': course_level, 'course_price': course_price} modulestore('direct').create_and_save_xmodule( dest_location, metadata=metadata ) new_course = modulestore('direct').get_item(dest_location) # clone a default 'about' overview module as well dest_about_location = dest_location.replace( category='about', name='overview' ) overview_template = AboutDescriptor.get_template('overview.yaml') modulestore('direct').create_and_save_xmodule( dest_about_location, system=new_course.system, definition_data=overview_template.get('data') ) initialize_course_tabs(new_course, request.user) new_location = loc_mapper().translate_location(new_course.location.course_id, new_course.location, False, True) # can't use auth.add_users here b/c it requires request.user to already have Instructor perms in this course # however, we can assume that b/c this user had authority to create the course, the user can add themselves CourseInstructorRole(new_location).add_users(request.user) auth.add_users(request.user, CourseStaffRole(new_location), request.user) # seed the forums seed_permissions_roles(new_course.location.course_id) # auto-enroll the course creator in the course so that "View Live" will # work. CourseEnrollment.enroll(request.user, new_course.location.course_id) _users_assign_default_role(new_course.location) # begin add notes when add course # it can also add other parameter on Advanced settings course_location = loc_mapper().translate_locator_to_location(new_location) course_module = get_modulestore(course_location).get_item(course_location) key_val = "/courses/" + org +"/"+ number +"/"+ run + "/notes/api" data_json = { "advanced_modules": ["notes"], "annotation_storage_url": key_val } CourseMetadata.update_from_json(course_module, data_json, True, request.user) # end return JsonResponse({'url': new_location.url_reverse("course/", "")})
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 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")