diff --git a/lms/djangoapps/grades/api/v1/gradebook_views.py b/lms/djangoapps/grades/api/v1/gradebook_views.py index 1adf715e90d56df4d7981a6ce1656d014d9a12df..d50a576ee72c537f75c6f06877074caaaec1412b 100644 --- a/lms/djangoapps/grades/api/v1/gradebook_views.py +++ b/lms/djangoapps/grades/api/v1/gradebook_views.py @@ -431,12 +431,14 @@ class GradebookView(GradeViewMixin, PaginatedAPIView): # For ZeroSubsectionGrades, we don't want to crawl the subsection's # subtree to find the problem scores specific to this user # (ZeroSubsectionGrade.attempted_graded is always False). - # We've already fetched the whole course structure in a non-specific way + # We've already fetched the whole course structure in a non-user-specific way # when creating `graded_subsections`. Looking at the problem scores # specific to this user (the user in `course_grade.user`) would require # us to re-fetch the user-specific course structure from the modulestore, - # which is a costly operation. - if subsection_grade.attempted_graded: + # which is a costly operation. So we only drill into the `graded_total` + # attribute if the user has attempted this graded subsection, or if there + # has been a grade override applied. + if subsection_grade.attempted_graded or subsection_grade.override: graded_description = '({earned:.2f}/{possible:.2f})'.format( earned=subsection_grade.graded_total.earned, possible=subsection_grade.graded_total.possible, diff --git a/lms/djangoapps/grades/api/v1/tests/test_gradebook_views.py b/lms/djangoapps/grades/api/v1/tests/test_gradebook_views.py index b704162f2ca228b4c94c9cf5fc28e2648d0eced5..6c9a3af86fa6e74232a91a45f278c02c8ba0d0a6 100644 --- a/lms/djangoapps/grades/api/v1/tests/test_gradebook_views.py +++ b/lms/djangoapps/grades/api/v1/tests/test_gradebook_views.py @@ -647,6 +647,86 @@ class GradebookViewTest(GradebookViewTestBase): actual_data = dict(resp.data) self.assertEqual(expected_results, actual_data) + @ddt.data( + 'login_staff', + 'login_course_admin', + 'login_course_staff', + ) + def test_gradebook_data_for_single_learner_override(self, login_method): + """ + Tests that, when a subsection grade that was created from an override, and thus + does not have a truthy `first_attempted` attribute, is the only grade for a + user's subsection, we still get data including a non-zero possible score. + """ + with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade: + course_grade = self.mock_course_grade(self.student, passed=True, letter_grade='A', percent=0.85) + + mock_subsection_grades = { + self.subsections[self.chapter_1.location][0].location: self.mock_subsection_grade( + self.subsections[self.chapter_1.location][0], + earned_all=1.0, + possible_all=2.0, + earned_graded=1.0, + possible_graded=2.0, + first_attempted=None, + override=MagicMock(), + ), + self.subsections[self.chapter_1.location][1].location: self.mock_subsection_grade( + self.subsections[self.chapter_1.location][1], + earned_all=1.0, + possible_all=2.0, + earned_graded=1.0, + possible_graded=2.0, + first_attempted=None, + override=MagicMock(), + ), + self.subsections[self.chapter_2.location][0].location: self.mock_subsection_grade( + self.subsections[self.chapter_2.location][0], + earned_all=1.0, + possible_all=2.0, + earned_graded=1.0, + possible_graded=2.0, + first_attempted=None, + override=MagicMock(), + ), + self.subsections[self.chapter_2.location][1].location: self.mock_subsection_grade( + self.subsections[self.chapter_2.location][1], + earned_all=1.0, + possible_all=2.0, + earned_graded=1.0, + possible_graded=2.0, + first_attempted=None, + override=MagicMock(), + ), + } + course_grade.subsection_grade = lambda key: mock_subsection_grades[key] + mock_grade.return_value = course_grade + + with override_waffle_flag(self.waffle_flag, active=True): + getattr(self, login_method)() + resp = self.client.get( + self.get_url(course_key=self.course.id, username=self.student.username) + ) + expected_results = OrderedDict([ + ('course_id', text_type(self.course.id)), + ('email', self.student.email), + ('user_id', self.student.id), + ('username', self.student.username), + ('full_name', self.student.get_full_name()), + ('passed', True), + ('percent', 0.85), + ('letter_grade', 'A'), + ('progress_page_url', reverse( + 'student_progress', + kwargs=dict(course_id=text_type(self.course.id), student_id=self.student.id) + )), + ('section_breakdown', self.expected_subsection_grades(letter_grade='A')), + ]) + + self.assertEqual(status.HTTP_200_OK, resp.status_code) + actual_data = dict(resp.data) + self.assertEqual(expected_results, actual_data) + @ddt.data( 'login_staff', 'login_course_admin',