def test_update_grader_from_json(self): test_grader = CourseGradingModel.fetch(self.course_locator) altered_grader = CourseGradingModel.update_grader_from_json(self.course_locator, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "Noop update") test_grader.graders[1]["min_count"] = test_grader.graders[1].get("min_count") + 2 altered_grader = CourseGradingModel.update_grader_from_json(self.course_locator, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "min_count[1] + 2") test_grader.graders[1]["drop_count"] = test_grader.graders[1].get("drop_count") + 1 altered_grader = CourseGradingModel.update_grader_from_json(self.course_locator, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "drop_count[1] + 2")
def test_update_grader_from_json(self): test_grader = CourseGradingModel.fetch(self.course.location) altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "Noop update") test_grader.graders[1]['min_count'] = test_grader.graders[1].get('min_count') + 2 altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "min_count[1] + 2") test_grader.graders[1]['drop_count'] = test_grader.graders[1].get('drop_count') + 1 altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1]) self.assertDictEqual(test_grader.graders[1], altered_grader, "drop_count[1] + 2")
def test_wrong_weight_sums(self): """Сумма весов в настройках не равна 100""" self.graders = CourseGradingModel.fetch(self.course.id).graders self.graders[0]['weight'] = 103. CourseGradingModel.update_grader_from_json(self.course.id, self.graders[0], self.user) self.set_task(n_task=5, type_task="Homework") self.set_task(n_task=3, type_task="Lab") self.set_task(n_task=1, type_task="Final Exam") CV = CourseValid(None, str(self.course_key)) rep = CV.val_grade() self.assertEqual(len(rep.warnings), 1)
def setUp(self): """Устанавливает грейдеры на 3 типа заданий и задает им веса""" super(GradingValTest, self).setUp() self.graders = CourseGradingModel.fetch(self.course.id).graders self.graders[0]['min_count'] = 5 self.graders[1]['min_count'] = 3 self.graders[0]['weight'] = 33. self.graders[1]['weight'] = 27. for g in self.graders: CourseGradingModel.update_grader_from_json(self.course.id, g, self.user) CourseGradingModel.delete_grader(self.course_key, 2, self.user)
def course_grader_updates(request, org, course, name, grader_index=None): """ restful CRUD operations on course_info updates. This differs from get_course_settings by communicating purely through json (not rendering any html) and handles section level operations rather than whole page. 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': # Cannot just do a get w/o knowing the course name :-( return HttpResponse(json.dumps( CourseGradingModel.fetch_grader(Location(location), grader_index)), mimetype="application/json") elif real_method == "DELETE": # ??? Should this return anything? Perhaps success fail? CourseGradingModel.delete_grader(Location(location), grader_index) return HttpResponse() elif request.method == 'POST': # post or put, doesn't matter. return HttpResponse(json.dumps( CourseGradingModel.update_grader_from_json(Location(location), request.POST)), mimetype="application/json")
def grading_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None, grader_index=None): """ Course Grading policy configuration GET html: get the page json no grader_index: get the CourseGrading model (graceperiod, cutoffs, and graders) json w/ grader_index: get the specific grader PUT json no grader_index: update the Course through the CourseGrading model json w/ grader_index: create or update the specific grader (create if index out of range) """ 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': course_details = CourseGradingModel.fetch(locator) return render_to_response( 'settings_graders.html', { 'context_course': course_module, 'course_locator': locator, 'course_details': json.dumps(course_details, cls=CourseSettingsEncoder), 'grading_url': locator.url_reverse('/settings/grading/'), }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): if request.method == 'GET': if grader_index is None: return JsonResponse( CourseGradingModel.fetch(locator), # encoder serializes dates, old locations, and instances encoder=CourseSettingsEncoder) else: return JsonResponse( CourseGradingModel.fetch_grader(locator, grader_index)) elif request.method in ('POST', 'PUT'): # post or put, doesn't matter. # None implies update the whole model (cutoffs, graceperiod, and graders) not a specific grader if grader_index is None: return JsonResponse(CourseGradingModel.update_from_json( locator, request.json, request.user), encoder=CourseSettingsEncoder) else: return JsonResponse( CourseGradingModel.update_grader_from_json( locator, request.json, request.user)) elif request.method == "DELETE" and grader_index is not None: CourseGradingModel.delete_grader(locator, grader_index, request.user) return JsonResponse()
def course_grader_updates(request, org, course, name, grader_index=None): """ Restful CRUD operations on course_info updates. This differs from get_course_settings by communicating purely through json (not rendering any html) and handles section level operations rather than whole page. org, course: Attributes of the Location for the item to edit """ location = get_location_and_verify_access(request, org, course, name) if request.method == 'GET': # Cannot just do a get w/o knowing the course name :-( return JsonResponse(CourseGradingModel.fetch_grader( Location(location), grader_index )) elif request.method == "DELETE": # ??? Should this return anything? Perhaps success fail? CourseGradingModel.delete_grader(Location(location), grader_index) return JsonResponse() else: # post or put, doesn't matter. return JsonResponse(CourseGradingModel.update_grader_from_json( Location(location), request.POST ))
def grading_handler(request, tag=None, course_id=None, branch=None, version_guid=None, block=None, grader_index=None): """ Course Grading policy configuration GET html: get the page json no grader_index: get the CourseGrading model (graceperiod, cutoffs, and graders) json w/ grader_index: get the specific grader PUT json no grader_index: update the Course through the CourseGrading model json w/ grader_index: create or update the specific grader (create if index out of range) """ locator = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block) if not has_access(request.user, locator): raise PermissionDenied() if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': course_old_location = loc_mapper().translate_locator_to_location(locator) course_module = modulestore().get_item(course_old_location) course_details = CourseGradingModel.fetch(locator) return render_to_response('settings_graders.html', { 'context_course': course_module, 'course_locator': locator, 'course_details': json.dumps(course_details, cls=CourseSettingsEncoder), 'grading_url': locator.url_reverse('/settings/grading/'), }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): if request.method == 'GET': if grader_index is None: return JsonResponse( CourseGradingModel.fetch(locator), # encoder serializes dates, old locations, and instances encoder=CourseSettingsEncoder ) else: return JsonResponse(CourseGradingModel.fetch_grader(locator, grader_index)) elif request.method in ('POST', 'PUT'): # post or put, doesn't matter. # None implies update the whole model (cutoffs, graceperiod, and graders) not a specific grader if grader_index is None: return JsonResponse( CourseGradingModel.update_from_json(locator, request.json), encoder=CourseSettingsEncoder ) else: return JsonResponse( CourseGradingModel.update_grader_from_json(locator, request.json) ) elif request.method == "DELETE" and grader_index is not None: CourseGradingModel.delete_grader(locator, grader_index) return JsonResponse()
def grading_handler(request, course_key_string, grader_index=None): """ Course Grading policy configuration GET html: get the page json no grader_index: get the CourseGrading model (graceperiod, cutoffs, and graders) json w/ grader_index: get the specific grader PUT json no grader_index: update the Course through the CourseGrading model json w/ grader_index: create or update the specific grader (create if index out of range) """ 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": course_details = CourseGradingModel.fetch(course_key) return render_to_response( "settings_graders.html", { "context_course": course_module, "course_locator": course_key, "course_details": json.dumps(course_details, cls=CourseSettingsEncoder), "grading_url": reverse_course_url("grading_handler", course_key), }, ) elif "application/json" in request.META.get("HTTP_ACCEPT", ""): if request.method == "GET": if grader_index is None: return JsonResponse( CourseGradingModel.fetch(course_key), # encoder serializes dates, old locations, and instances encoder=CourseSettingsEncoder, ) else: return JsonResponse(CourseGradingModel.fetch_grader(course_key, grader_index)) elif request.method in ("POST", "PUT"): # post or put, doesn't matter. # None implies update the whole model (cutoffs, graceperiod, and graders) not a specific grader if grader_index is None: return JsonResponse( CourseGradingModel.update_from_json(course_key, request.json, request.user), encoder=CourseSettingsEncoder, ) else: return JsonResponse(CourseGradingModel.update_grader_from_json(course_key, request.json, request.user)) elif request.method == "DELETE" and grader_index is not None: CourseGradingModel.delete_grader(course_key, grader_index, request.user) return JsonResponse()
def create_xblock(parent_locator, user, category, display_name, boilerplate=None, is_entrance_exam=False): """ Performs the actual grunt work of creating items/xblocks -- knows nothing about requests, views, etc. """ store = modulestore() usage_key = usage_key_with_run(parent_locator) with store.bulk_operations(usage_key.course_key): parent = store.get_item(usage_key) dest_usage_key = usage_key.replace(category=category, name=uuid4().hex) # get the metadata, display_name, and definition from the caller metadata = {} data = None template_id = boilerplate if template_id: clz = parent.runtime.load_block_type(category) if clz is not None: template = clz.get_template(template_id) if template is not None: metadata = template.get('metadata', {}) data = template.get('data') if display_name is not None: metadata['display_name'] = display_name # We should use the 'fields' kwarg for newer module settings/values (vs. metadata or data) fields = {} # Entrance Exams: Chapter module positioning child_position = None 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 child_position = 0 # TODO need to fix components that are sending definition_data as strings, instead of as dicts # For now, migrate them into dicts here. if isinstance(data, basestring): data = {'data': data} created_block = store.create_child( user.id, usage_key, dest_usage_key.block_type, block_id=dest_usage_key.block_id, fields=fields, definition_data=data, metadata=metadata, runtime=parent.runtime, position=child_position, ) # Entrance Exams: Grader assignment 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: if category == 'sequential' and parent_locator == course.entrance_exam_id: # Clean up any pre-existing entrance exam graders remove_entrance_exam_graders(course_key, user) grader = { "type": GRADER_TYPES['ENTRANCE_EXAM'], "min_count": 0, "drop_count": 0, "short_label": "Entrance", "weight": 0 } grading_model = CourseGradingModel.update_grader_from_json( course.id, grader, user ) CourseGradingModel.update_section_grader_type( created_block, grading_model['type'], user ) # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so # if we add one then we need to also add it to the policy information (i.e. metadata) # we should remove this once we can break this reference from the course to static tabs if category == 'static_tab': dog_stats_api.increment( DEPRECATION_VSCOMPAT_EVENT, tags=( "location:create_xblock_static_tab", u"course:{}".format(unicode(dest_usage_key.course_key)), ) ) display_name = display_name or _("Empty") # Prevent name being None course = store.get_course(dest_usage_key.course_key) course.tabs.append( StaticTab( name=display_name, url_slug=dest_usage_key.name, ) ) store.update_item(course, user.id) return created_block
def create_xblock(parent_locator, user, category, display_name, boilerplate=None, is_entrance_exam=False): """ Performs the actual grunt work of creating items/xblocks -- knows nothing about requests, views, etc. """ store = modulestore() usage_key = usage_key_with_run(parent_locator) with store.bulk_operations(usage_key.course_key): parent = store.get_item(usage_key) dest_usage_key = usage_key.replace(category=category, name=uuid4().hex) # get the metadata, display_name, and definition from the caller metadata = {} data = None template_id = boilerplate if template_id: clz = parent.runtime.load_block_type(category) if clz is not None: template = clz.get_template(template_id) if template is not None: metadata = template.get('metadata', {}) data = template.get('data') if display_name is not None: metadata['display_name'] = display_name # We should use the 'fields' kwarg for newer module settings/values (vs. metadata or data) fields = {} # Entrance Exams: Chapter module positioning child_position = None if settings.FEATURES.get('ENTRANCE_EXAMS', False): 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 child_position = 0 # TODO need to fix components that are sending definition_data as strings, instead of as dicts # For now, migrate them into dicts here. if isinstance(data, basestring): data = {'data': data} created_block = store.create_child( user.id, usage_key, dest_usage_key.block_type, block_id=dest_usage_key.block_id, fields=fields, definition_data=data, metadata=metadata, runtime=parent.runtime, position=child_position, ) # Entrance Exams: Grader assignment if settings.FEATURES.get('ENTRANCE_EXAMS', False): course_key = usage_key.course_key course = store.get_course(course_key) if hasattr(course, 'entrance_exam_enabled') and course.entrance_exam_enabled: if category == 'sequential' and parent_locator == course.entrance_exam_id: # Clean up any pre-existing entrance exam graders remove_entrance_exam_graders(course_key, user) grader = { "type": GRADER_TYPES['ENTRANCE_EXAM'], "min_count": 0, "drop_count": 0, "short_label": "Entrance", "weight": 0 } grading_model = CourseGradingModel.update_grader_from_json( course.id, grader, user ) CourseGradingModel.update_section_grader_type( created_block, grading_model['type'], user ) # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so # if we add one then we need to also add it to the policy information (i.e. metadata) # we should remove this once we can break this reference from the course to static tabs if category == 'static_tab': dog_stats_api.increment( DEPRECATION_VSCOMPAT_EVENT, tags=( "location:create_xblock_static_tab", u"course:{}".format(unicode(dest_usage_key.course_key)), ) ) display_name = display_name or _("Empty") # Prevent name being None course = store.get_course(dest_usage_key.course_key) course.tabs.append( StaticTab( name=display_name, url_slug=dest_usage_key.name, ) ) store.update_item(course, user.id) return created_block
def _create_item(request): """View for create items.""" usage_key = usage_key_with_run(request.json['parent_locator']) if not has_studio_write_access(request.user, usage_key.course_key): raise PermissionDenied() category = request.json['category'] display_name = request.json.get('display_name') if isinstance(usage_key, LibraryUsageLocator): # Only these categories are supported at this time. if category not in ['html', 'problem', 'video']: return HttpResponseBadRequest( "Category '%s' not supported for Libraries" % category, content_type='text/plain' ) store = modulestore() with store.bulk_operations(usage_key.course_key): parent = store.get_item(usage_key) dest_usage_key = usage_key.replace(category=category, name=uuid4().hex) # get the metadata, display_name, and definition from the request metadata = {} data = None template_id = request.json.get('boilerplate') if template_id: clz = parent.runtime.load_block_type(category) if clz is not None: template = clz.get_template(template_id) if template is not None: metadata = template.get('metadata', {}) data = template.get('data') if display_name is not None: metadata['display_name'] = display_name # Entrance Exams: Chapter module positioning child_position = None if settings.FEATURES.get('ENTRANCE_EXAMS', False): is_entrance_exam = request.json.get('is_entrance_exam', False) if category == 'chapter' and is_entrance_exam: metadata['is_entrance_exam'] = is_entrance_exam metadata['in_entrance_exam'] = True # Inherited metadata, all children will have it child_position = 0 # TODO need to fix components that are sending definition_data as strings, instead of as dicts # For now, migrate them into dicts here. if isinstance(data, basestring): data = {'data': data} created_block = store.create_child( request.user.id, usage_key, dest_usage_key.block_type, block_id=dest_usage_key.block_id, definition_data=data, metadata=metadata, runtime=parent.runtime, position=child_position ) # Entrance Exams: Grader assignment if settings.FEATURES.get('ENTRANCE_EXAMS', False): course = store.get_course(usage_key.course_key) if hasattr(course, 'entrance_exam_enabled') and course.entrance_exam_enabled: if category == 'sequential' and request.json.get('parent_locator') == course.entrance_exam_id: grader = { "type": "Entrance Exam", "min_count": 0, "drop_count": 0, "short_label": "Entrance", "weight": 0 } grading_model = CourseGradingModel.update_grader_from_json( course.id, grader, request.user ) CourseGradingModel.update_section_grader_type( created_block, grading_model['type'], request.user ) # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so # if we add one then we need to also add it to the policy information (i.e. metadata) # we should remove this once we can break this reference from the course to static tabs if category == 'static_tab': display_name = display_name or _("Empty") # Prevent name being None course = store.get_course(dest_usage_key.course_key) course.tabs.append( StaticTab( name=display_name, url_slug=dest_usage_key.name, ) ) store.update_item(course, request.user.id) return JsonResponse( {"locator": unicode(created_block.location), "courseKey": unicode(created_block.location.course_key)} )