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 test_fetch_cutoffs(self): test_grader = CourseGradingModel.fetch_cutoffs(self.course.location) # ??? should this check that it's at least a dict? (expected is { "pass" : 0.5 } I think) self.assertIsNotNone(test_grader, "No cutoffs via fetch") test_grader = CourseGradingModel.fetch_cutoffs(self.course.location.url()) self.assertIsNotNone(test_grader, "No cutoffs via fetch with url")
def test_fetch_grace(self): test_grader = CourseGradingModel.fetch_grace_period(self.course.location) # almost a worthless test self.assertIn('grace_period', test_grader, "No grace via fetch") test_grader = CourseGradingModel.fetch_grace_period(self.course.location.url()) self.assertIn('grace_period', test_grader, "No cutoffs via fetch with url")
def test_update_from_json(self): test_grader = CourseGradingModel.fetch(self.course_locator) altered_grader = CourseGradingModel.update_from_json( self.course_locator, test_grader.__dict__, self.user) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "Noop update") test_grader.graders[0]['weight'] = test_grader.graders[0].get( 'weight') * 2 altered_grader = CourseGradingModel.update_from_json( self.course_locator, test_grader.__dict__, self.user) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "Weight[0] * 2") test_grader.grade_cutoffs['D'] = 0.3 altered_grader = CourseGradingModel.update_from_json( self.course_locator, test_grader.__dict__, self.user) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "cutoff add D") test_grader.grace_period = {'hours': 4, 'minutes': 5, 'seconds': 0} altered_grader = CourseGradingModel.update_from_json( self.course_locator, test_grader.__dict__, self.user) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "4 hour grace period")
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 test_update_from_json(self): test_grader = CourseGradingModel.fetch(self.course_location) altered_grader = CourseGradingModel.update_from_json( test_grader.__dict__) self.assertDictEqual( test_grader.__dict__, altered_grader.__dict__, "Noop update") test_grader.graders[0][ 'weight'] = test_grader.graders[0].get('weight') * 2 altered_grader = CourseGradingModel.update_from_json( test_grader.__dict__) self.assertDictEqual( test_grader.__dict__, altered_grader.__dict__, "Weight[0] * 2") test_grader.grade_cutoffs['D'] = 0.3 altered_grader = CourseGradingModel.update_from_json( test_grader.__dict__) self.assertDictEqual( test_grader.__dict__, altered_grader.__dict__, "cutoff add D") test_grader.grace_period = {'hours': 4, 'minutes': 5, 'seconds': 0} altered_grader = CourseGradingModel.update_from_json( test_grader.__dict__) print test_grader.grace_period, altered_grader.grace_period self.assertDictEqual( test_grader.__dict__, altered_grader.__dict__, "4 hour grace period")
def test_update_cutoffs_from_json(self): test_grader = CourseGradingModel.fetch(self.course_locator) CourseGradingModel.update_cutoffs_from_json(self.course_locator, test_grader.grade_cutoffs, self.user) # Unlike other tests, need to actually perform a db fetch for this test since update_cutoffs_from_json # simply returns the cutoffs you send into it, rather than returning the db contents. altered_grader = CourseGradingModel.fetch(self.course_locator) self.assertDictEqual(test_grader.grade_cutoffs, altered_grader.grade_cutoffs, "Noop update") test_grader.grade_cutoffs['D'] = 0.3 CourseGradingModel.update_cutoffs_from_json(self.course_locator, test_grader.grade_cutoffs, self.user) altered_grader = CourseGradingModel.fetch(self.course_locator) self.assertDictEqual(test_grader.grade_cutoffs, altered_grader.grade_cutoffs, "cutoff add D") test_grader.grade_cutoffs['Pass'] = 0.75 CourseGradingModel.update_cutoffs_from_json(self.course_locator, test_grader.grade_cutoffs, self.user) altered_grader = CourseGradingModel.fetch(self.course_locator) self.assertDictEqual(test_grader.grade_cutoffs, altered_grader.grade_cutoffs, "cutoff change 'Pass'")
def test_delete_grace_period(self): test_grader = CourseGradingModel.fetch(self.course_locator) CourseGradingModel.update_grace_period_from_json( self.course_locator, test_grader.grace_period, self.user) # update_grace_period_from_json doesn't return anything, so query the db for its contents. altered_grader = CourseGradingModel.fetch(self.course_locator) self.assertEqual(test_grader.grace_period, altered_grader.grace_period, "Noop update") test_grader.grace_period = {'hours': 15, 'minutes': 5, 'seconds': 30} CourseGradingModel.update_grace_period_from_json( self.course_locator, test_grader.grace_period, self.user) altered_grader = CourseGradingModel.fetch(self.course_locator) self.assertDictEqual(test_grader.grace_period, altered_grader.grace_period, "Adding in a grace period") test_grader.grace_period = {'hours': 1, 'minutes': 10, 'seconds': 0} # Now delete the grace period CourseGradingModel.delete_grace_period(self.course_locator, self.user) # update_grace_period_from_json doesn't return anything, so query the db for its contents. altered_grader = CourseGradingModel.fetch(self.course_locator) # Once deleted, the grace period should simply be None self.assertEqual(None, altered_grader.grace_period, "Delete grace period")
def test_fetch_grader(self): test_grader = CourseGradingModel.fetch(self.course.id) self.assertIsNotNone(test_grader.graders, "No graders") self.assertIsNotNone(test_grader.grade_cutoffs, "No cutoffs") for i, grader in enumerate(test_grader.graders): subgrader = CourseGradingModel.fetch_grader(self.course.id, i) self.assertDictEqual(grader, subgrader, str(i) + "th graders not equal")
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_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 assignment_type_update(request, org, course, category, name): ''' CRUD operations on assignment types for sections and subsections and anything else gradable. ''' location = Location(['i4x', org, course, category, name]) if not has_access(request.user, location): return HttpResponseForbidden() if request.method == 'GET': return JsonResponse(CourseGradingModel.get_section_grader_type(location)) elif request.method in ('POST', 'PUT'): # post or put, doesn't matter. return JsonResponse(CourseGradingModel.update_section_grader_type(location, request.POST))
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_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 assignment_type_update(request, org, course, category, name): """ CRUD operations on assignment types for sections and subsections and anything else gradable. """ location = Location(["i4x", org, course, category, name]) if not has_access(request.user, location): return HttpResponseForbidden() if request.method == "GET": rsp = CourseGradingModel.get_section_grader_type(location) elif request.method in ("POST", "PUT"): # post or put, doesn't matter. rsp = CourseGradingModel.update_section_grader_type(location, request.POST) return JsonResponse(rsp)
def test_update_from_json(self, store): self.course = CourseFactory.create(default_store=store) test_grader = CourseGradingModel.fetch(self.course.id) altered_grader = CourseGradingModel.update_from_json(self.course.id, test_grader.__dict__, self.user) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "Noop update") test_grader.graders[0]['weight'] = test_grader.graders[0].get('weight') * 2 altered_grader = CourseGradingModel.update_from_json(self.course.id, test_grader.__dict__, self.user) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "Weight[0] * 2") # test for bug LMS-11485 with modulestore().bulk_operations(self.course.id): new_grader = test_grader.graders[0].copy() new_grader['type'] += '_foo' new_grader['short_label'] += '_foo' new_grader['id'] = len(test_grader.graders) test_grader.graders.append(new_grader) # don't use altered cached def, get a fresh one CourseGradingModel.update_from_json(self.course.id, test_grader.__dict__, self.user) altered_grader = CourseGradingModel.fetch(self.course.id) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__) test_grader.grade_cutoffs['D'] = 0.3 altered_grader = CourseGradingModel.update_from_json(self.course.id, test_grader.__dict__, self.user) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "cutoff add D") test_grader.grace_period = {'hours': 4, 'minutes': 5, 'seconds': 0} altered_grader = CourseGradingModel.update_from_json(self.course.id, test_grader.__dict__, self.user) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "4 hour grace period")
def course_index(request, org, course, name): """ Display an editable course overview. org, course, name: Attributes of the Location for the item to edit """ location = get_location_and_verify_access(request, org, course, name) lms_link = get_lms_link_for_item(location) upload_asset_callback_url = reverse('upload_asset', kwargs={ 'org': org, 'course': course, 'coursename': name }) course = modulestore().get_item(location, depth=3) sections = course.get_children() return render_to_response('overview.html', { 'context_course': course, 'lms_link': lms_link, 'sections': sections, 'course_graders': json.dumps( CourseGradingModel.fetch(course.location).graders ), 'parent_location': course.location, 'new_section_category': 'chapter', 'new_subsection_category': 'sequential', 'upload_asset_callback_url': upload_asset_callback_url, 'new_unit_category': 'vertical', 'category': 'vertical' })
def test_initial_grader(self): descriptor = get_modulestore(self.course.location).get_item(self.course.location) test_grader = CourseGradingModel(descriptor) # ??? How much should this test bake in expectations about defaults and thus fail if defaults change? self.assertEqual(self.course.location, test_grader.course_location, "Course locations") self.assertIsNotNone(test_grader.graders, "No graders") self.assertIsNotNone(test_grader.grade_cutoffs, "No cutoffs")
def course_index(request, package_id, branch, version_guid, block): """ Display an editable course overview. org, course, name: Attributes of the Location for the item to edit """ locator, course = _get_locator_and_course(package_id, branch, version_guid, block, request.user, depth=3) lms_link = get_lms_link_for_item(course.location) sections = course.get_children() return render_to_response( 'overview.html', { 'context_course': course, 'lms_link': lms_link, 'sections': sections, 'course_graders': json.dumps( CourseGradingModel.fetch(locator).graders), 'parent_locator': locator, 'new_section_category': 'chapter', 'new_subsection_category': 'sequential', 'new_unit_category': 'vertical', 'category': 'vertical' })
def course_index(request, package_id, branch, version_guid, block): """ Display an editable course overview. org, course, name: Attributes of the Location for the item to edit """ locator, course = _get_locator_and_course( package_id, branch, version_guid, block, request.user, depth=3 ) lms_link = get_lms_link_for_item(course.location) sections = course.get_children() return render_to_response('overview.html', { 'context_course': course, 'lms_link': lms_link, 'sections': sections, 'course_graders': json.dumps( CourseGradingModel.fetch(locator).graders ), 'parent_locator': locator, 'new_section_category': 'chapter', 'new_subsection_category': 'sequential', 'new_unit_category': 'vertical', 'category': 'vertical' })
def course_index(request, course_id, branch, version_guid, block): """ Display an editable course overview. org, course, name: Attributes of the Location for the item to edit """ location = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block) # TODO: when converting to split backend, if location does not have a usage_id, # we'll need to get the course's root block_id if not has_access(request.user, location): raise PermissionDenied() old_location = loc_mapper().translate_locator_to_location(location) lms_link = get_lms_link_for_item(old_location) course = modulestore().get_item(old_location, depth=3) sections = course.get_children() return render_to_response('overview.html', { 'context_course': course, 'lms_link': lms_link, 'sections': sections, 'course_graders': json.dumps( CourseGradingModel.fetch(course.location).graders ), 'parent_location': course.location, 'new_section_category': 'chapter', 'new_subsection_category': 'sequential', 'new_unit_category': 'vertical', 'category': 'vertical' })
def course_index(request, course_key): """ Display an editable course overview. org, course, name: Attributes of the Location for the item to edit """ course_module = _get_course_module(course_key, request.user, depth=3) lms_link = get_lms_link_for_item(course_module.location) sections = course_module.get_children() try: current_action = CourseRerunState.objects.find_first(course_key=course_key, should_display=True) except (ItemNotFoundError, CourseActionStateItemNotFoundError): current_action = None return render_to_response('overview.html', { 'context_course': course_module, 'lms_link': lms_link, 'sections': sections, 'course_graders': json.dumps( CourseGradingModel.fetch(course_key).graders ), 'new_section_category': 'chapter', 'new_subsection_category': 'sequential', 'new_unit_category': 'vertical', 'category': 'vertical', 'rerun_notification_id': current_action.id if current_action else None, })
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 course_index(request, course_key): """ Display an editable course overview. org, course, name: Attributes of the Location for the item to edit """ course_module = _get_course_module(course_key, request.user, depth=3) lms_link = get_lms_link_for_item(course_module.location) sections = course_module.get_children() return render_to_response( 'overview.html', { 'context_course': course_module, 'lms_link': lms_link, 'sections': sections, 'course_graders': json.dumps(CourseGradingModel.fetch(course_key).graders), 'new_section_category': 'chapter', 'new_subsection_category': 'sequential', 'new_unit_category': 'vertical', 'category': 'vertical' })
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).get('locator') # chapter_locator = UsageKey.from_string(chapter_locator_string) seq_data = { 'category': "sequential", 'display_name': "Entrance Exam Subsection", 'parent_locator': chapter_locator_string, } resp = self.client.ajax_post(reverse_url('xblock_handler'), seq_data) seq_locator_string = json.loads(resp.content).get('locator') seq_locator = UsageKey.from_string(seq_locator_string) section_grader_type = CourseGradingModel.get_section_grader_type(seq_locator) self.assertEqual(GRADER_TYPES['ENTRANCE_EXAM'], section_grader_type['graderType'])
def course_index(request, org, course, name): """ Display an editable course overview. org, course, name: Attributes of the Location for the item to edit """ location = get_location_and_verify_access(request, org, course, name) lms_link = get_lms_link_for_item(location) upload_asset_callback_url = reverse('upload_asset', kwargs={ 'org': org, 'course': course, 'coursename': name }) course = modulestore().get_item(location, depth=3) sections = course.get_children() return render_to_response('overview.html', { 'active_tab': 'courseware', 'context_course': course, 'lms_link': lms_link, 'sections': sections, 'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders), 'parent_location': course.location, 'new_section_template': Location('i4x', 'edx', 'templates', 'chapter', 'Empty'), 'new_subsection_template': Location('i4x', 'edx', 'templates', 'sequential', 'Empty'), # for now they are the same, but the could be different at some point... 'upload_asset_callback_url': upload_asset_callback_url, 'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty') })
def test_delete(self): """Test deleting a specific grading type record.""" resp = self.client.delete(self.url + '/0', HTTP_ACCEPT="application/json") self.assertEqual(resp.status_code, 204) current_graders = CourseGradingModel.fetch(self.course.id).graders self.assertNotIn(self.starting_graders[0], current_graders) self.assertEqual(len(self.starting_graders) - 1, len(current_graders))
def edit_subsection(request, location): # check that we have permissions to edit this item course = get_course_for_item(location) if not has_access(request.user, course.location): raise PermissionDenied() item = modulestore().get_item(location, depth=1) lms_link = get_lms_link_for_item( location, course_id=course.location.course_id) preview_link = get_lms_link_for_item( location, course_id=course.location.course_id, preview=True) # make sure that location references a 'sequential', otherwise return # BadRequest if item.location.category != 'sequential': return HttpResponseBadRequest() parent_locs = modulestore().get_parent_locations(location, None) # we're for now assuming a single parent if len(parent_locs) != 1: logging.error( 'Multiple (or none) parents have been found for {0}'.format(location)) # this should blow up if we don't find any parents, which would be # erroneous parent = modulestore().get_item(parent_locs[0]) # remove all metadata from the generic dictionary that is presented in a # more normalized UI policy_metadata = dict( (field.name, field.read_from(item)) for field in item.fields if field.name not in ['display_name', 'start', 'due', 'format'] and field.scope == Scope.settings ) can_view_live = False subsection_units = item.get_children() for unit in subsection_units: state = compute_unit_state(unit) if state == UnitState.public or state == UnitState.draft: can_view_live = True break return render_to_response('edit_subsection.html', {'subsection': item, 'context_course': course, 'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'), 'lms_link': lms_link, 'preview_link': preview_link, 'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders), 'parent_location': course.location, 'parent_item': parent, 'policy_metadata': policy_metadata, 'subsection_units': subsection_units, 'can_view_live': can_view_live })
def test_update_from_json(self): test_grader = CourseGradingModel.fetch(self.course.location) altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "Noop update") test_grader.graders[0]["weight"] = test_grader.graders[0].get("weight") * 2 altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "Weight[0] * 2") test_grader.grade_cutoffs["D"] = 0.3 altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "cutoff add D") test_grader.grace_period = {"hours": 4, "minutes": 5, "seconds": 0} altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "4 hour grace period")
def course_index(request, org, course, name): """ Display an editable course overview. org, course, name: Attributes of the Location for the item to edit """ location = get_location_and_verify_access(request, org, course, name) lms_link = get_lms_link_for_item(location) upload_asset_callback_url = reverse("upload_asset", kwargs={"org": org, "course": course, "coursename": name}) course = modulestore().get_item(location, depth=3) sections = course.get_children() return render_to_response( "overview.html", { "context_course": course, "lms_link": lms_link, "sections": sections, "course_graders": json.dumps(CourseGradingModel.fetch(course.location).graders), "parent_location": course.location, "new_section_category": "chapter", "new_subsection_category": "sequential", "upload_asset_callback_url": upload_asset_callback_url, "new_unit_category": "vertical", "category": "vertical", }, )
def test_fetch_grader(self): test_grader = CourseGradingModel.fetch(self.course.location.url()) self.assertEqual(self.course.location, test_grader.course_location, "Course locations") self.assertIsNotNone(test_grader.graders, "No graders") self.assertIsNotNone(test_grader.grade_cutoffs, "No cutoffs") test_grader = CourseGradingModel.fetch(self.course.location) self.assertEqual(self.course.location, test_grader.course_location, "Course locations") self.assertIsNotNone(test_grader.graders, "No graders") self.assertIsNotNone(test_grader.grade_cutoffs, "No cutoffs") for i, grader in enumerate(test_grader.graders): subgrader = CourseGradingModel.fetch_grader(self.course.location, i) self.assertDictEqual(grader, subgrader, str(i) + "th graders not equal") subgrader = CourseGradingModel.fetch_grader(self.course.location.list(), 0) self.assertDictEqual(test_grader.graders[0], subgrader, "failed with location as list")
def test_update_cutoffs_from_json(self): test_grader = CourseGradingModel.fetch(self.course.id) CourseGradingModel.update_cutoffs_from_json(self.course.id, test_grader.grade_cutoffs, self.user) # Unlike other tests, need to actually perform a db fetch for this test since update_cutoffs_from_json # simply returns the cutoffs you send into it, rather than returning the db contents. altered_grader = CourseGradingModel.fetch(self.course.id) self.assertDictEqual(test_grader.grade_cutoffs, altered_grader.grade_cutoffs, "Noop update") test_grader.grade_cutoffs['D'] = 0.3 CourseGradingModel.update_cutoffs_from_json(self.course.id, test_grader.grade_cutoffs, self.user) altered_grader = CourseGradingModel.fetch(self.course.id) self.assertDictEqual(test_grader.grade_cutoffs, altered_grader.grade_cutoffs, "cutoff add D") test_grader.grade_cutoffs['Pass'] = 0.75 CourseGradingModel.update_cutoffs_from_json(self.course.id, test_grader.grade_cutoffs, self.user) altered_grader = CourseGradingModel.fetch(self.course.id) self.assertDictEqual(test_grader.grade_cutoffs, altered_grader.grade_cutoffs, "cutoff change 'Pass'")
def subsection_handler(request, usage_key_string): """ The restful handler for subsection-specific requests. GET html: return html page for editing a subsection json: not currently supported """ if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'): usage_key = UsageKey.from_string(usage_key_string) try: course, item, lms_link = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() preview_link = get_lms_link_for_item(item.location, preview=True) # make sure that location references a 'sequential', otherwise return # BadRequest if item.location.category != 'sequential': return HttpResponseBadRequest() parent = get_parent_xblock(item) # remove all metadata from the generic dictionary that is presented in a # more normalized UI. We only want to display the XBlocks fields, not # the fields from any mixins that have been added fields = getattr(item, 'unmixed_class', item.__class__).fields policy_metadata = dict( (field.name, field.read_from(item)) for field in fields.values() if field.name not in ['display_name', 'start', 'due', 'format'] and field.scope == Scope.settings ) can_view_live = False subsection_units = item.get_children() can_view_live = any([modulestore().has_published_version(unit) for unit in subsection_units]) return render_to_response( 'edit_subsection.html', { 'subsection': item, 'context_course': course, 'new_unit_category': 'vertical', 'lms_link': lms_link, 'preview_link': preview_link, 'course_graders': json.dumps(CourseGradingModel.fetch(item.location.course_key).graders), 'parent_item': parent, 'locator': item.location, 'policy_metadata': policy_metadata, 'subsection_units': subsection_units, 'can_view_live': can_view_live } ) else: return HttpResponseBadRequest("Only supports html requests")
def test_update_section_grader_type(self): # Get the descriptor and the section_grader_type and assert they are the default values descriptor = modulestore().get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual('notgraded', section_grader_type['graderType']) self.assertEqual(None, descriptor.format) self.assertEqual(False, descriptor.graded) # Change the default grader type to Homework, which should also mark the section as graded CourseGradingModel.update_section_grader_type(self.course, 'Homework', self.user) descriptor = modulestore().get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual('Homework', section_grader_type['graderType']) self.assertEqual('Homework', descriptor.format) self.assertEqual(True, descriptor.graded) # Change the grader type back to notgraded, which should also unmark the section as graded CourseGradingModel.update_section_grader_type(self.course, 'notgraded', self.user) descriptor = modulestore().get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual('notgraded', section_grader_type['graderType']) self.assertEqual(None, descriptor.format) self.assertEqual(False, descriptor.graded)
def test_update_section_grader_type(self): # Get the descriptor and the section_grader_type and assert they are the default values descriptor = get_modulestore(self.course.location).get_item( self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type( self.course_locator) self.assertEqual('notgraded', section_grader_type['graderType']) self.assertEqual(None, descriptor.format) self.assertEqual(False, descriptor.graded) # Change the default grader type to Homework, which should also mark the section as graded CourseGradingModel.update_section_grader_type(self.course, 'Homework', self.user) descriptor = get_modulestore(self.course.location).get_item( self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type( self.course_locator) self.assertEqual('Homework', section_grader_type['graderType']) self.assertEqual('Homework', descriptor.format) self.assertEqual(True, descriptor.graded) # Change the grader type back to notgraded, which should also unmark the section as graded CourseGradingModel.update_section_grader_type(self.course, 'notgraded', self.user) descriptor = get_modulestore(self.course.location).get_item( self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type( self.course_locator) self.assertEqual('notgraded', section_grader_type['graderType']) self.assertEqual(None, descriptor.format) self.assertEqual(False, descriptor.graded)
def test_update_section_grader_type(self): # Get the descriptor and the section_grader_type and assert they are the default values descriptor = get_modulestore(self.course.location).get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual("Not Graded", section_grader_type["graderType"]) self.assertEqual(None, descriptor.lms.format) self.assertEqual(False, descriptor.lms.graded) # Change the default grader type to Homework, which should also mark the section as graded CourseGradingModel.update_section_grader_type(self.course.location, {"graderType": "Homework"}) descriptor = get_modulestore(self.course.location).get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual("Homework", section_grader_type["graderType"]) self.assertEqual("Homework", descriptor.lms.format) self.assertEqual(True, descriptor.lms.graded) # Change the grader type back to Not Graded, which should also unmark the section as graded CourseGradingModel.update_section_grader_type(self.course.location, {"graderType": "Not Graded"}) descriptor = get_modulestore(self.course.location).get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual("Not Graded", section_grader_type["graderType"]) self.assertEqual(None, descriptor.lms.format) self.assertEqual(False, descriptor.lms.graded)
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 test_delete_grace_period(self): test_grader = CourseGradingModel.fetch(self.course.location) CourseGradingModel.update_grace_period_from_json(test_grader.course_location, test_grader.grace_period) # update_grace_period_from_json doesn't return anything, so query the db for its contents. altered_grader = CourseGradingModel.fetch(self.course.location) self.assertEqual(test_grader.grace_period, altered_grader.grace_period, "Noop update") test_grader.grace_period = {'hours': 15, 'minutes': 5, 'seconds': 30} CourseGradingModel.update_grace_period_from_json(test_grader.course_location, test_grader.grace_period) altered_grader = CourseGradingModel.fetch(self.course.location) self.assertDictEqual(test_grader.grace_period, altered_grader.grace_period, "Adding in a grace period") test_grader.grace_period = {'hours': 1, 'minutes': 10, 'seconds': 0} # Now delete the grace period CourseGradingModel.delete_grace_period(test_grader.course_location) # update_grace_period_from_json doesn't return anything, so query the db for its contents. altered_grader = CourseGradingModel.fetch(self.course.location) # Once deleted, the grace period should simply be None self.assertEqual(None, altered_grader.grace_period, "Delete grace period")
def test_delete_grace_period(self): test_grader = CourseGradingModel.fetch(self.course.id) CourseGradingModel.update_grace_period_from_json(self.course.id, test_grader.grace_period, self.user) # update_grace_period_from_json doesn't return anything, so query the db for its contents. altered_grader = CourseGradingModel.fetch(self.course.id) self.assertEqual(test_grader.grace_period, altered_grader.grace_period, "Noop update") test_grader.grace_period = {"hours": 15, "minutes": 5, "seconds": 30} CourseGradingModel.update_grace_period_from_json(self.course.id, test_grader.grace_period, self.user) altered_grader = CourseGradingModel.fetch(self.course.id) self.assertDictEqual(test_grader.grace_period, altered_grader.grace_period, "Adding in a grace period") test_grader.grace_period = {"hours": 1, "minutes": 10, "seconds": 0} # Now delete the grace period CourseGradingModel.delete_grace_period(self.course.id, self.user) # update_grace_period_from_json doesn't return anything, so query the db for its contents. altered_grader = CourseGradingModel.fetch(self.course.id) # Once deleted, the grace period should simply be None self.assertEqual(None, altered_grader.grace_period, "Delete grace period")
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 test_update(self): """Test updating a specific grading type record.""" grader = { "id": 0, "type": "manual", "min_count": 5, "drop_count": 10, "short_label": "yo momma", "weight": 17.3, } resp = self.client.ajax_post(self.url + '/0', grader) self.assertEqual(resp.status_code, 200) obj = json.loads(resp.content) self.assertEqual(obj, grader) current_graders = CourseGradingModel.fetch(self.course_locator).graders self.assertEqual(len(self.starting_graders), len(current_graders))
def course_config_graders_page(request, org, course, name): """ Send models and views as well as html for editing the 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) course_details = CourseGradingModel.fetch(location) return render_to_response('settings_graders.html', { 'context_course': course_module, 'course_location': location, 'course_details': json.dumps(course_details, cls=CourseSettingsEncoder) })
def test_add(self): """Test adding a grading type record.""" # the same url works for changing the whole grading model (graceperiod, cutoffs, and grading types) when # the grading_index is None; thus, using None to imply adding a grading_type doesn't work; so, it uses an # index out of bounds to imply create item. grader = { "type": "manual", "min_count": 5, "drop_count": 10, "short_label": "yo momma", "weight": 17.3, } resp = self.client.ajax_post('{}/{}'.format(self.url, len(self.starting_graders) + 1), grader) self.assertEqual(resp.status_code, 200) obj = json.loads(resp.content) self.assertEqual(obj['id'], len(self.starting_graders)) del obj['id'] self.assertEqual(obj, grader) current_graders = CourseGradingModel.fetch(self.course.id).graders self.assertEqual(len(self.starting_graders) + 1, len(current_graders))