def _assert_grades_absent_for_courses(self, course_keys): """ Assert grades for given courses do not exist. """ for course_key in course_keys: with self.assertRaises(PersistentCourseGrade.DoesNotExist): PersistentCourseGrade.read_course_grade(self.user_ids[0], course_key) for subsection_key in self.subsection_keys_by_course[course_key]: with self.assertRaises(PersistentSubsectionGrade.DoesNotExist): PersistentSubsectionGrade.read_grade(self.user_ids[0], subsection_key)
def _assert_grades_absent_for_courses(self, course_keys, db_table=None): """ Assert grades for given courses do not exist. """ for course_key in course_keys: if db_table == "course" or db_table is None: with self.assertRaises(PersistentCourseGrade.DoesNotExist): PersistentCourseGrade.read(self.user_ids[0], course_key) if db_table == "subsection" or db_table is None: for subsection_key in self.subsection_keys_by_course[course_key]: with self.assertRaises(PersistentSubsectionGrade.DoesNotExist): PersistentSubsectionGrade.read_grade(self.user_ids[0], subsection_key)
def _assert_grades_absent_for_courses(self, course_keys, db_table=None): """ Assert grades for given courses do not exist. """ for course_key in course_keys: if db_table == "course" or db_table is None: with self.assertRaises(PersistentCourseGrade.DoesNotExist): PersistentCourseGrade.read_course_grade(self.user_ids[0], course_key) if db_table == "subsection" or db_table is None: for subsection_key in self.subsection_keys_by_course[course_key]: with self.assertRaises(PersistentSubsectionGrade.DoesNotExist): PersistentSubsectionGrade.read_grade(self.user_ids[0], subsection_key)
def update(self, subsection, only_if_higher=None, score_deleted=False): """ Updates the SubsectionGrade object for the student and subsection. """ self._log_event(log.debug, u"update, subsection: {}".format(subsection.location), subsection) calculated_grade = CreateSubsectionGrade( subsection, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if should_persist_grades(self.course_data.course_key): if only_if_higher: try: grade_model = PersistentSubsectionGrade.read_grade(self.student.id, subsection.location) except PersistentSubsectionGrade.DoesNotExist: pass else: orig_subsection_grade = ReadSubsectionGrade(subsection, grade_model, self) if not is_score_higher_or_equal( orig_subsection_grade.graded_total.earned, orig_subsection_grade.graded_total.possible, calculated_grade.graded_total.earned, calculated_grade.graded_total.possible, ): return orig_subsection_grade grade_model = calculated_grade.update_or_create_model(self.student, score_deleted) self._update_saved_subsection_grade(subsection.location, grade_model) return calculated_grade
def update(self, subsection, only_if_higher=None): """ Updates the SubsectionGrade object for the student and subsection. """ # Save ourselves the extra queries if the course does not persist # subsection grades. if not PersistentGradesEnabledFlag.feature_enabled(self.course.id): return self._log_event(log.warning, u"update, subsection: {}".format(subsection.location), subsection) calculated_grade = SubsectionGrade(subsection).init_from_structure( self.student, self.course_structure, self._submissions_scores, self._csm_scores, ) if only_if_higher: try: grade_model = PersistentSubsectionGrade.read_grade(self.student.id, subsection.location) except PersistentSubsectionGrade.DoesNotExist: pass else: orig_subsection_grade = SubsectionGrade(subsection).init_from_model( self.student, grade_model, self.course_structure, self._submissions_scores, self._csm_scores, ) if not is_score_higher( orig_subsection_grade.graded_total.earned, orig_subsection_grade.graded_total.possible, calculated_grade.graded_total.earned, calculated_grade.graded_total.possible, ): return orig_subsection_grade grade_model = calculated_grade.update_or_create_model(self.student) self._update_saved_subsection_grade(subsection.location, grade_model) return calculated_grade
def update(self, subsection, only_if_higher=None, score_deleted=False, force_update_subsections=False, persist_grade=True): """ Updates the SubsectionGrade object for the student and subsection. """ self._log_event(log.debug, u"update, subsection: {}".format(subsection.location), subsection) calculated_grade = CreateSubsectionGrade( subsection, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if persist_grade and should_persist_grades(self.course_data.course_key): if only_if_higher: try: grade_model = PersistentSubsectionGrade.read_grade(self.student.id, subsection.location) except PersistentSubsectionGrade.DoesNotExist: pass else: orig_subsection_grade = ReadSubsectionGrade(subsection, grade_model, self) if not is_score_higher_or_equal( orig_subsection_grade.graded_total.earned, orig_subsection_grade.graded_total.possible, calculated_grade.graded_total.earned, calculated_grade.graded_total.possible, treat_undefined_as_zero=True, ): return orig_subsection_grade grade_model = calculated_grade.update_or_create_model( self.student, score_deleted, force_update_subsections ) self._update_saved_subsection_grade(subsection.location, grade_model) return calculated_grade
def _get_saved_grade(self, subsection, course_structure, course): # pylint: disable=unused-argument """ Returns the saved grade for the student and subsection. """ if PersistentGradesEnabledFlag.feature_enabled(course.id): try: model = PersistentSubsectionGrade.read_grade( user_id=self.student.id, usage_key=subsection.location, ) subsection_grade = SubsectionGrade(subsection) subsection_grade.load_from_data(model, course_structure, self._scores_client, self._submissions_scores) log.warning( u"Persistent Grades: Loaded grade for course id: {0}, version: {1}, subtree edited on: {2}," u" grade: {3}, user: {4}".format( course.id, getattr(course, 'course_version', None), course.subtree_edited_on, subsection_grade, self.student.id)) return subsection_grade except PersistentSubsectionGrade.DoesNotExist: log.warning( u"Persistent Grades: Could not find grade for course id: {0}, version: {1}, subtree edited" u" on: {2}, subsection: {3}, user: {4}".format( course.id, getattr(course, 'course_version', None), course.subtree_edited_on, subsection.location, self.student.id)) return None
def _assert_grades_exist_for_courses(self, course_keys): """ Assert grades for given courses exist. """ for course_key in course_keys: self.assertIsNotNone(PersistentCourseGrade.read_course_grade(self.user_ids[0], course_key)) for subsection_key in self.subsection_keys_by_course[course_key]: self.assertIsNotNone(PersistentSubsectionGrade.read_grade(self.user_ids[0], subsection_key))
def _assert_grades_exist_for_courses(self, course_keys, db_table=None): """ Assert grades for given courses exist. """ for course_key in course_keys: if db_table == "course" or db_table is None: self.assertIsNotNone(PersistentCourseGrade.read(self.user_ids[0], course_key)) if db_table == "subsection" or db_table is None: for subsection_key in self.subsection_keys_by_course[course_key]: self.assertIsNotNone(PersistentSubsectionGrade.read_grade(self.user_ids[0], subsection_key))
def _assert_grades_exist_for_courses(self, course_keys, db_table=None): """ Assert grades for given courses exist. """ for course_key in course_keys: if db_table == "course" or db_table is None: self.assertIsNotNone(PersistentCourseGrade.read_course_grade(self.user_ids[0], course_key)) if db_table == "subsection" or db_table is None: for subsection_key in self.subsection_keys_by_course[course_key]: self.assertIsNotNone(PersistentSubsectionGrade.read_grade(self.user_ids[0], subsection_key))
def test_logging_for_save(self): with patch('lms.djangoapps.grades.models.log') as log_mock: PersistentSubsectionGrade.save_grade(**self.params) read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) log_mock.info.assert_called_with( u"Persistent Grades: Grade model saved: {0}".format(read_grade) )
def test_create(self): """ Tests model creation, and confirms error when trying to recreate model. """ created_grade = PersistentSubsectionGrade.objects.create(**self.params) read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) self.assertEqual(created_grade, read_grade) with self.assertRaises(ValidationError): created_grade = PersistentSubsectionGrade.objects.create(**self.params)
def test_create(self): """ Tests model creation, and confirms error when trying to recreate model. """ created_grade = PersistentSubsectionGrade.create_grade(**self.params) with self.assertNumQueries(1): read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) self.assertEqual(created_grade, read_grade) self.assertEqual(read_grade.visible_blocks.blocks, self.block_records) with self.assertRaises(IntegrityError): PersistentSubsectionGrade.create_grade(**self.params)
def update(self, subsection, only_if_higher=None, score_deleted=False, force_update_subsections=False, persist_grade=True): # lint-amnesty, pylint: disable=line-too-long """ Updates the SubsectionGrade object for the student and subsection. """ self._log_event(log.debug, u"update, subsection: {}".format(subsection.location), subsection) calculated_grade = CreateSubsectionGrade( subsection, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if persist_grade and should_persist_grades( self.course_data.course_key): if only_if_higher: try: grade_model = PersistentSubsectionGrade.read_grade( self.student.id, subsection.location) except PersistentSubsectionGrade.DoesNotExist: pass else: orig_subsection_grade = ReadSubsectionGrade( subsection, grade_model, self) if not is_score_higher_or_equal( orig_subsection_grade.graded_total.earned, orig_subsection_grade.graded_total.possible, calculated_grade.graded_total.earned, calculated_grade.graded_total.possible, treat_undefined_as_zero=True, ): return orig_subsection_grade grade_model = calculated_grade.update_or_create_model( self.student, score_deleted, force_update_subsections) self._update_saved_subsection_grade(subsection.location, grade_model) if settings.FEATURES.get( 'ENABLE_COURSE_ASSESSMENT_GRADE_CHANGE_SIGNAL'): COURSE_ASSESSMENT_GRADE_CHANGED.send( sender=self, course_id=self.course_data.course_key, user=self.student, subsection_id=calculated_grade.location, subsection_grade=calculated_grade.graded_total.earned) return calculated_grade
def _get_saved_grade(self, subsection, course_structure, course): # pylint: disable=unused-argument """ Returns the saved grade for the student and subsection. """ if settings.FEATURES.get('ENABLE_SUBSECTION_GRADES_SAVED') and course.enable_subsection_grades_saved: try: model = PersistentSubsectionGrade.read_grade( user_id=self.student.id, usage_key=subsection.location, ) subsection_grade = SubsectionGrade(subsection) subsection_grade.load_from_data(model, course_structure, self._scores_client, self._submissions_scores) return subsection_grade except PersistentSubsectionGrade.DoesNotExist: return None
def _get_saved_grade(self, subsection, course_structure, course): # pylint: disable=unused-argument """ Returns the saved grade for the student and subsection. """ if PersistentGradesEnabledFlag.feature_enabled(course.id): try: model = PersistentSubsectionGrade.read_grade( user_id=self.student.id, usage_key=subsection.location, ) subsection_grade = SubsectionGrade(subsection) subsection_grade.load_from_data(model, course_structure, self._scores_client, self._submissions_scores) return subsection_grade except PersistentSubsectionGrade.DoesNotExist: return None
def test_update_grade(self): """ Tests model update, and confirms error when updating a nonexistent model. """ with self.assertRaises(PersistentSubsectionGrade.DoesNotExist): PersistentSubsectionGrade.update_grade(**self.params) PersistentSubsectionGrade.objects.create(**self.params) self.params['earned_all'] = 12 self.params['earned_graded'] = 8 PersistentSubsectionGrade.update_grade(**self.params) read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) self.assertEqual(read_grade.earned_all, 12) self.assertEqual(read_grade.earned_graded, 8)
def test_update_or_create_grade(self, already_created): created_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) if already_created else None self.params["earned_all"] = 7 updated_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertEqual(updated_grade.earned_all, 7) if already_created: self.assertEqual(created_grade.id, updated_grade.id) self.assertEqual(created_grade.earned_all, 6) with self.assertNumQueries(1): read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) self.assertEqual(updated_grade, read_grade) self.assertEqual(read_grade.visible_blocks.blocks, self.block_records)
def test_update_or_create_grade(self, already_created): created_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) if already_created else None self.params["earned_all"] = 7 updated_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertEqual(updated_grade.earned_all, 7) if already_created: self.assertEqual(created_grade.id, updated_grade.id) self.assertEqual(created_grade.earned_all, 6) with self.assertNumQueries(1): read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) self.assertEqual(updated_grade, read_grade) self.assertEqual(read_grade.visible_blocks.blocks, self.block_records)
def test_update_or_create_grade(self, already_created): created_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) if already_created else None self.params["earned_all"] = 7 updated_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) assert updated_grade.earned_all == 7 if already_created: assert created_grade.id == updated_grade.id assert created_grade.earned_all == 6 with self.assertNumQueries(1): read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) assert updated_grade == read_grade assert read_grade.visible_blocks.blocks == self.block_records
def _get_saved_grade(self, subsection, course_structure, course): # pylint: disable=unused-argument """ Returns the saved grade for the student and subsection. """ if PersistentGradesEnabledFlag.feature_enabled(course.id): try: model = PersistentSubsectionGrade.read_grade( user_id=self.student.id, usage_key=subsection.location, ) subsection_grade = SubsectionGrade(subsection) subsection_grade.load_from_data(model, course_structure, self._scores_client, self._submissions_scores) return subsection_grade except PersistentSubsectionGrade.DoesNotExist: return None
def test_update_grade(self): """ Tests model update, and confirms error when updating a nonexistent model. """ with self.assertRaises(PersistentSubsectionGrade.DoesNotExist): PersistentSubsectionGrade.update_grade(**self.params) PersistentSubsectionGrade.objects.create(**self.params) self.params['earned_all'] = 12.0 self.params['earned_graded'] = 8.0 with patch('lms.djangoapps.grades.models.log') as log_mock: PersistentSubsectionGrade.update_grade(**self.params) read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) log_mock.info.assert_called_with( u"Persistent Grades: Grade model updated: {0}".format(read_grade) ) self.assertEqual(read_grade.earned_all, 12.0) self.assertEqual(read_grade.earned_graded, 8.0)
def get(self, request, subsection_id): """ Returns subection grade data, override grade data and a history of changes made to a specific users specific subsection grade. Args: subsection_id: String representation of a usage_key, which is an opaque key of a persistant subection grade. user_id: An integer represenation of a user """ try: usage_key = UsageKey.from_string(subsection_id) except InvalidKeyError: raise self.api_error( status_code=status.HTTP_404_NOT_FOUND, developer_message='Invalid UsageKey', error_code='invalid_usage_key' ) if not has_course_author_access(request.user, usage_key.course_key): raise DeveloperErrorViewMixin.api_error( status_code=status.HTTP_403_FORBIDDEN, developer_message='The requesting user does not have course author permissions.', error_code='user_permissions', ) try: user_id = int(request.GET.get('user_id')) except ValueError: raise self.api_error( status_code=status.HTTP_404_NOT_FOUND, developer_message='Invalid UserID', error_code='invalid_user_id' ) try: original_grade = PersistentSubsectionGrade.read_grade(user_id, usage_key) except PersistentSubsectionGrade.DoesNotExist: results = SubsectionGradeResponseSerializer({ 'original_grade': None, 'override': None, 'history': [], 'subsection_id': usage_key, 'user_id': user_id, 'course_id': None, }) return Response(results.data) try: override = original_grade.override history = PersistentSubsectionGradeOverrideHistory.objects.filter(override_id=override.id) except PersistentSubsectionGradeOverride.DoesNotExist: override = None history = [] results = SubsectionGradeResponseSerializer({ 'original_grade': original_grade, 'override': override, 'history': history, 'subsection_id': original_grade.usage_key, 'user_id': original_grade.user_id, 'course_id': original_grade.course_id, }) return Response(results.data)
def get(self, request, subsection_id): """ Returns subection grade data, override grade data and a history of changes made to a specific users specific subsection grade. Args: subsection_id: String representation of a usage_key, which is an opaque key of a persistant subection grade. user_id: An integer represenation of a user """ try: usage_key = UsageKey.from_string(subsection_id) except InvalidKeyError: raise self.api_error(status_code=status.HTTP_404_NOT_FOUND, developer_message='Invalid UsageKey', error_code='invalid_usage_key') if not has_course_author_access(request.user, usage_key.course_key): raise DeveloperErrorViewMixin.api_error( status_code=status.HTTP_403_FORBIDDEN, developer_message= 'The requesting user does not have course author permissions.', error_code='user_permissions', ) try: user_id = int(request.GET.get('user_id')) except ValueError: raise self.api_error(status_code=status.HTTP_404_NOT_FOUND, developer_message='Invalid UserID', error_code='invalid_user_id') success = True err_msg = "" override = None history = [] history_record_limit = request.GET.get('history_record_limit') if history_record_limit is not None: try: history_record_limit = int(history_record_limit) except ValueError: history_record_limit = 0 try: original_grade = PersistentSubsectionGrade.read_grade( user_id, usage_key) if original_grade is not None and hasattr(original_grade, 'override'): override = original_grade.override # pylint: disable=no-member history = list( PersistentSubsectionGradeOverride.history.filter( grade_id=original_grade.id).order_by('history_date') [:history_record_limit]) grade_data = { 'earned_all': original_grade.earned_all, 'possible_all': original_grade.possible_all, 'earned_graded': original_grade.earned_graded, 'possible_graded': original_grade.possible_graded, } except PersistentSubsectionGrade.DoesNotExist: try: grade_data = self._get_grade_data_for_not_attempted_assignment( user_id, usage_key) except SubsectionUnavailableToUserException as exc: success = False err_msg = str(exc) grade_data = { 'earned_all': 0, 'possible_all': 0, 'earned_graded': 0, 'possible_graded': 0, } response_data = { 'success': success, 'original_grade': grade_data, 'override': override, 'history': history, 'subsection_id': usage_key, 'user_id': user_id, 'course_id': usage_key.course_key, } if not success: response_data['error_message'] = err_msg results = SubsectionGradeResponseSerializer(response_data) return Response(results.data)
def test_override_is_created(self): """ Test that when we make multiple requests to update grades for the same user/subsection, the score from the most recent request is recorded. """ with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() post_data = [{ 'user_id': self.student.id, 'usage_id': text_type( self.subsections[self.chapter_1.location][0].location), 'grade': { 'earned_all_override': 3, 'possible_all_override': 3, 'earned_graded_override': 2, 'possible_graded_override': 2, }, }, { 'user_id': self.student.id, 'usage_id': text_type( self.subsections[self.chapter_1.location][1].location), 'grade': { 'earned_all_override': 1, 'possible_all_override': 4, 'earned_graded_override': 1, 'possible_graded_override': 4, }, }] resp = self.client.post( self.get_url(), data=json.dumps(post_data), content_type='application/json', ) expected_data = [ { 'user_id': self.student.id, 'usage_id': text_type( self.subsections[self.chapter_1.location][0].location), 'success': True, 'reason': None, }, { 'user_id': self.student.id, 'usage_id': text_type( self.subsections[self.chapter_1.location][1].location), 'success': True, 'reason': None, }, ] self.assertEqual(status.HTTP_202_ACCEPTED, resp.status_code) self.assertEqual(expected_data, resp.data) second_post_data = [ { 'user_id': self.student.id, 'usage_id': text_type( self.subsections[self.chapter_1.location][1].location), 'grade': { 'earned_all_override': 3, 'possible_all_override': 4, 'earned_graded_override': 3, 'possible_graded_override': 4, }, }, ] self.client.post( self.get_url(), data=json.dumps(second_post_data), content_type='application/json', ) GradeFields = namedtuple('GradeFields', [ 'earned_all', 'possible_all', 'earned_graded', 'possible_graded' ]) # We should now have PersistentSubsectionGradeOverride records corresponding to # our bulk-update request, and PersistentSubsectionGrade records with grade values # equal to those of the override. for usage_key, expected_grades in ( (self.subsections[self.chapter_1.location][0].location, GradeFields(3, 3, 2, 2)), (self.subsections[self.chapter_1.location][1].location, GradeFields(3, 4, 3, 4)), ): # this selects related PersistentSubsectionGradeOverride objects. grade = PersistentSubsectionGrade.read_grade( user_id=self.student.id, usage_key=usage_key, ) for field_name in expected_grades._fields: expected_value = getattr(expected_grades, field_name) self.assertEqual(expected_value, getattr(grade, field_name)) self.assertEqual( expected_value, getattr(grade.override, field_name + '_override'))
def test_override_is_created(self): """ Test that when we make multiple requests to update grades for the same user/subsection, the score from the most recent request is recorded. """ with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() post_data = [ { 'user_id': self.student.id, 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), 'grade': { 'earned_all_override': 3, 'possible_all_override': 3, 'earned_graded_override': 2, 'possible_graded_override': 2, }, }, { 'user_id': self.student.id, 'usage_id': text_type(self.subsections[self.chapter_1.location][1].location), 'grade': { 'earned_all_override': 1, 'possible_all_override': 4, 'earned_graded_override': 1, 'possible_graded_override': 4, }, } ] resp = self.client.post( self.get_url(), data=json.dumps(post_data), content_type='application/json', ) expected_data = [ { 'user_id': self.student.id, 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), 'success': True, 'reason': None, }, { 'user_id': self.student.id, 'usage_id': text_type(self.subsections[self.chapter_1.location][1].location), 'success': True, 'reason': None, }, ] self.assertEqual(status.HTTP_202_ACCEPTED, resp.status_code) self.assertEqual(expected_data, resp.data) second_post_data = [ { 'user_id': self.student.id, 'usage_id': text_type(self.subsections[self.chapter_1.location][1].location), 'grade': { 'earned_all_override': 3, 'possible_all_override': 4, 'earned_graded_override': 3, 'possible_graded_override': 4, }, }, ] self.client.post( self.get_url(), data=json.dumps(second_post_data), content_type='application/json', ) GradeFields = namedtuple('GradeFields', ['earned_all', 'possible_all', 'earned_graded', 'possible_graded']) # We should now have PersistentSubsectionGradeOverride records corresponding to # our bulk-update request, and PersistentSubsectionGrade records with grade values # equal to those of the override. for usage_key, expected_grades in ( (self.subsections[self.chapter_1.location][0].location, GradeFields(3, 3, 2, 2)), (self.subsections[self.chapter_1.location][1].location, GradeFields(3, 4, 3, 4)), ): # this selects related PersistentSubsectionGradeOverride objects. grade = PersistentSubsectionGrade.read_grade( user_id=self.student.id, usage_key=usage_key, ) for field_name in expected_grades._fields: expected_value = getattr(expected_grades, field_name) self.assertEqual(expected_value, getattr(grade, field_name)) self.assertEqual(expected_value, getattr(grade.override, field_name + '_override')) update_records = PersistentSubsectionGradeOverrideHistory.objects.filter(user=self.global_staff) self.assertEqual(update_records.count(), 3) for audit_item in update_records: self.assertEqual(audit_item.user, self.global_staff) self.assertIsNotNone(audit_item.created) self.assertEqual(audit_item.feature, PersistentSubsectionGradeOverrideHistory.GRADEBOOK) self.assertEqual(audit_item.action, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE)
def get(self, request, subsection_id): """ Returns subection grade data, override grade data and a history of changes made to a specific users specific subsection grade. Args: subsection_id: String representation of a usage_key, which is an opaque key of a persistant subection grade. user_id: An integer represenation of a user """ try: usage_key = UsageKey.from_string(subsection_id) except InvalidKeyError: raise self.api_error(status_code=status.HTTP_404_NOT_FOUND, developer_message='Invalid UsageKey', error_code='invalid_usage_key') if not has_course_author_access(request.user, usage_key.course_key): raise DeveloperErrorViewMixin.api_error( status_code=status.HTTP_403_FORBIDDEN, developer_message= 'The requesting user does not have course author permissions.', error_code='user_permissions', ) try: user_id = int(request.GET.get('user_id')) except ValueError: raise self.api_error(status_code=status.HTTP_404_NOT_FOUND, developer_message='Invalid UserID', error_code='invalid_user_id') try: original_grade = PersistentSubsectionGrade.read_grade( user_id, usage_key) except PersistentSubsectionGrade.DoesNotExist: results = SubsectionGradeResponseSerializer({ 'original_grade': None, 'override': None, 'history': [], 'subsection_id': usage_key, 'user_id': user_id, 'course_id': None, }) return Response(results.data) try: override = original_grade.override history = PersistentSubsectionGradeOverrideHistory.objects.filter( override_id=override.id) except PersistentSubsectionGradeOverride.DoesNotExist: override = None history = [] results = SubsectionGradeResponseSerializer({ 'original_grade': original_grade, 'override': override, 'history': history, 'subsection_id': original_grade.usage_key, 'user_id': original_grade.user_id, 'course_id': original_grade.course_id, }) return Response(results.data)