Пример #1
0
    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)
Пример #2
0
    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)
Пример #3
0
    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
Пример #5
0
    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
Пример #6
0
    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
Пример #8
0
 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))
Пример #9
0
 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))
Пример #10
0
 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))
Пример #11
0
 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)
         )
Пример #12
0
 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)
Пример #13
0
 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)
Пример #14
0
    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
Пример #15
0
 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
Пример #16
0
 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
Пример #17
0
 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)
Пример #18
0
    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)
Пример #19
0
    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)
Пример #20
0
    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
Пример #21
0
 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
Пример #22
0
    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)
Пример #23
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)
Пример #24
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')
        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)
Пример #25
0
    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'))
Пример #26
0
    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)