From 7b0483260632c0b10f9e898ccfe29eedfa2b4883 Mon Sep 17 00:00:00 2001 From: Alex Dusenbery Date: Mon, 17 Dec 2018 16:25:24 -0500 Subject: [PATCH] EDUCATOR-3668 | The CreateSubsectionGrade class should reflect possibly overridden values from the PersistentSubsectionGrade class. --- lms/djangoapps/grades/subsection_grade.py | 24 +++++++++++- .../tests/test_subsection_grade_factory.py | 38 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/grades/subsection_grade.py b/lms/djangoapps/grades/subsection_grade.py index 80e3384d0e..9b3f013c0b 100644 --- a/lms/djangoapps/grades/subsection_grade.py +++ b/lms/djangoapps/grades/subsection_grade.py @@ -253,7 +253,29 @@ class CreateSubsectionGrade(NonZeroSubsectionGrade): Saves or updates the subsection grade in a persisted model. """ if self._should_persist_per_attempted(score_deleted, force_update_subsections): - return PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student)) + model = PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student)) + self._update_aggregated_scores_from_model(model) + return model + + def _update_aggregated_scores_from_model(self, model): + """ + Updates this grade's `all_total` and `graded_total` attributes + to reflect the values from the related persisted model. + This is important, because PersistentSubsectionGradeOverrides + are only taken into account when reading/writing at the persistence layer, + so after we update a PersistentSubsectionGrade model (which could involve + writing values that are overridden by the PersistentSubsectionGradeOverride model) + we also need to update this grade object's attributes to reflect the + now (possibly) overriden values. + TODO: https://openedx.atlassian.net/browse/EDUCATOR-3835 + """ + self.all_total.earned = model.earned_all + self.all_total.possible = model.possible_all + self.all_total.first_attempted = model.first_attempted + + self.graded_total.earned = model.earned_graded + self.graded_total.possible = model.possible_graded + self.graded_total.first_attempted = model.first_attempted @classmethod def bulk_create_models(cls, student, subsection_grades, course_key): diff --git a/lms/djangoapps/grades/tests/test_subsection_grade_factory.py b/lms/djangoapps/grades/tests/test_subsection_grade_factory.py index b55f512e4a..a9e9a618f1 100644 --- a/lms/djangoapps/grades/tests/test_subsection_grade_factory.py +++ b/lms/djangoapps/grades/tests/test_subsection_grade_factory.py @@ -1,10 +1,13 @@ +""" +Tests for the SubsectionGradeFactory class. +""" import ddt from courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin from django.conf import settings from lms.djangoapps.grades.config.tests.utils import persistent_grades_feature_flags from mock import patch -from ..models import PersistentSubsectionGrade +from ..models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride from ..subsection_grade_factory import ZeroSubsectionGrade from .base import GradeTestBase from .utils import mock_get_score @@ -29,6 +32,10 @@ class TestSubsectionGradeFactory(ProblemSubmissionTestMixin, GradeTestBase): (grade.all_total.earned, grade.all_total.possible), (expected_earned, expected_possible), ) + self.assertEqual( + (grade.graded_total.earned, grade.graded_total.possible), + (expected_earned, expected_possible), + ) def test_create_zero(self): """ @@ -100,3 +107,32 @@ class TestSubsectionGradeFactory(ProblemSubmissionTestMixin, GradeTestBase): ): self.subsection_grade_factory.create(self.sequence) self.assertEqual(mock_read_saved_grade.called, feature_flag and course_setting) + + def test_update_with_override(self): + """ + Tests that when a PersistentSubsectionGradeOverride exists, the update() + method returns a CreateSubsectionGrade with scores that account + for the override. + """ + # first, do an update to create a persistent grade + with mock_get_score(2, 3): + grade = self.subsection_grade_factory.update(self.sequence) + self.assert_grade(grade, 2, 3) + + # there should only be one persistent grade + persistent_grade = PersistentSubsectionGrade.objects.first() + self.assertEqual(2, persistent_grade.earned_graded) + self.assertEqual(3, persistent_grade.possible_graded) + + # Now create the override + PersistentSubsectionGradeOverride.objects.create( + grade=persistent_grade, + earned_graded_override=0, + earned_all_override=0, + ) + + # Now, even if the problem scores interface gives us a 2/3, + # the subsection grade returned should be 0/3 due to the override. + with mock_get_score(2, 3): + grade = self.subsection_grade_factory.update(self.sequence) + self.assert_grade(grade, 0, 3)