def post(self, request): if 'duplicate_source_locator' in request.data: return JsonResponse({'error': 'Duplicate key found'}) else: parent_locator = request.data['parent_locator'] usage_key = usage_key_with_run(parent_locator) category = request.data['category'] 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') created_block = create_xblock( parent_locator=parent_locator, user=request.user, category=category, display_name=request.data.get('display_name'), boilerplate=request.data.get('boilerplate'), ) if 'graderType' in request.data: CourseGradingModel.update_section_grader_type( modulestore().get_item(created_block.location), request.data['graderType'], request.user) return JsonResponse({ 'locator': str(created_block.location), 'courseKey': str(created_block.location.course_key) })
def remove_entrance_exam_graders(course_key, user): """ Removes existing entrance exam graders attached to the specified course Typically used when adding/removing an entrance exam. """ grading_model = CourseGradingModel.fetch(course_key) graders = grading_model.graders for i, grader in enumerate(graders): if grader['type'] == GRADER_TYPES['ENTRANCE_EXAM']: CourseGradingModel.delete_grader(course_key, i, user)
def test_contentstore_views_entrance_exam_post_new_sequential_confirm_grader(self): """ Unit Test: test_contentstore_views_entrance_exam_post """ resp = self.client.post(self.exam_url, {}, http_accept='application/json') self.assertEqual(resp.status_code, 201) resp = self.client.get(self.exam_url) self.assertEqual(resp.status_code, 200) # Reload the test course now that the exam module has been added self.course = modulestore().get_course(self.course.id) # Add a new child sequential to the exam module # Confirm that the grader type is 'Entrance Exam' chapter_locator_string = json.loads(resp.content.decode('utf-8')).get('locator') # chapter_locator = UsageKey.from_string(chapter_locator_string) seq_data = { 'category': "sequential", 'display_name': "Entrance Exam Subsection", 'parent_locator': chapter_locator_string, } resp = self.client.ajax_post(reverse_url('xblock_handler'), seq_data) seq_locator_string = json.loads(resp.content.decode('utf-8')).get('locator') seq_locator = UsageKey.from_string(seq_locator_string) section_grader_type = CourseGradingModel.get_section_grader_type(seq_locator) self.assertEqual(GRADER_TYPES['ENTRANCE_EXAM'], section_grader_type['graderType'])
def test_contentstore_views_entrance_exam_delete(self): """ Unit Test: test_contentstore_views_entrance_exam_delete """ resp = self.client.post(self.exam_url, {}, http_accept='application/json') self.assertEqual(resp.status_code, 201) resp = self.client.get(self.exam_url) self.assertEqual(resp.status_code, 200) resp = self.client.delete(self.exam_url) self.assertEqual(resp.status_code, 204) resp = self.client.get(self.exam_url) self.assertEqual(resp.status_code, 404) user = User.objects.create( username='******', email='*****@*****.**', is_active=True, ) user.set_password('test') user.save() milestones = milestones_helpers.get_course_milestones( six.text_type(self.course_key)) self.assertEqual(len(milestones), 1) milestone_key = '{}.{}'.format(milestones[0]['namespace'], milestones[0]['name']) paths = milestones_helpers.get_course_milestones_fulfillment_paths( six.text_type(self.course_key), milestones_helpers.serialize_user(user)) # What we have now is a course milestone requirement and no valid fulfillment # paths for the specified user. The LMS is going to have to ignore this situation, # because we can't confidently prevent it from occuring at some point in the future. # milestone_key_1 = self.assertEqual(len(paths[milestone_key]), 0) # Re-adding an entrance exam to the course should fix the missing link # It wipes out any old entrance exam artifacts and inserts a new exam course chapter/module resp = self.client.post(self.exam_url, {}, http_accept='application/json') self.assertEqual(resp.status_code, 201) resp = self.client.get(self.exam_url) self.assertEqual(resp.status_code, 200) # Confirm that we have only one Entrance Exam grader after re-adding the exam (validates SOL-475) graders = CourseGradingModel.fetch(self.course_key).graders count = 0 for grader in graders: if grader['type'] == GRADER_TYPES['ENTRANCE_EXAM']: count += 1 self.assertEqual(count, 1)
def val_grade(self): """ Проверка оценок: 1)совпадение указанного и имеющегося количества заданий в каждой проверяемой категории, 2)проверка равенства 100 суммы весов категории 3)Отсутствие в курсе заданий с типом, не указанным в настройках """ report = [] course_details = CourseGradingModel.fetch(self.course_key) graders = course_details.graders grade_strs = [] grade_attributes = ["type", "min_count", "drop_count", "weight"] grade_types = [] grade_nums = [] grade_weights = [] # Вытаскиваем типы и количество заданий, прописанных в настройках for grd in graders: grade_strs.append([unicode(grd[attr]) for attr in grade_attributes]) grade_types.append(unicode(grd["type"])) grade_nums.append(unicode(grd["min_count"])) try: grade_weights.append(float(grd["weight"])) except ValueError: report.append(_("Error occured during weight summation")) head = [_("Grade name"), _("Grade count"), _("Grade kicked"), _("Grade weight")] # Проверка суммы весов if sum(grade_weights) != 100: report.append(_("Tasks weight sum({}) is not equal to 100").format(sum(grade_weights))) # Проверка совпадения настроек заданий с материалом курса grade_items = [i for i in self.items if i.format is not None] for num, key in enumerate(grade_types): cur_items = [i for i in grade_items if unicode(i.format) == key] if len(cur_items) != int(grade_nums[num]): r = _("Task type '{name}': supposed to be {n1}, found in course {n2}"). \ format(name=key, n1=grade_nums[num], n2=len(cur_items)) report.append(r) # Проверка отсутствия в материале курсе заданий с типом не указанным в настройках for item in grade_items: if item.format not in grade_types: report.append(_("Task of type '{}' in course, no such task type in grading settings")) results = Report(name=self.scenarios_names["grade"], head=head, body=grade_strs, warnings=report, ) return results
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 ENTRANCE_EXAMS.is_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, str): 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 ENTRANCE_EXAMS.is_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': 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.block_id, ) ) store.update_item(course, user.id) return created_block