From 6d08aa23b4add9aa8de48610e4ace6bd1b0a35cb Mon Sep 17 00:00:00 2001 From: Chris Deery <3932645+cdeery@users.noreply.github.com> Date: Tue, 21 Dec 2021 14:41:25 -0500 Subject: [PATCH] fix: [AA-1076] add override info to progress API (#29585) * fix: [AA-1076] add override info to progress API - Indicate if a grade has been overridden in the progress tab. --- .../course_home_api/progress/serializers.py | 11 ++++ .../progress/tests/test_views.py | 54 ++++++++++++++++++- .../course_home_api/progress/views.py | 3 ++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/course_home_api/progress/serializers.py b/lms/djangoapps/course_home_api/progress/serializers.py index 9831a148b9..190cd76bf9 100644 --- a/lms/djangoapps/course_home_api/progress/serializers.py +++ b/lms/djangoapps/course_home_api/progress/serializers.py @@ -27,6 +27,7 @@ class SubsectionScoresSerializer(ReadOnlySerializer): block_key = serializers.SerializerMethodField() display_name = serializers.CharField() has_graded_assignment = serializers.BooleanField(source='graded') + override = serializers.SerializerMethodField() learner_has_access = serializers.SerializerMethodField() num_points_earned = serializers.FloatField(source='graded_total.earned') num_points_possible = serializers.FloatField(source='graded_total.possible') @@ -36,6 +37,16 @@ class SubsectionScoresSerializer(ReadOnlySerializer): show_grades = serializers.SerializerMethodField() url = serializers.SerializerMethodField() + def get_override(self, subsection): + """Proctoring or grading score override""" + if subsection.override is None: + return None + else: + return { + "system": subsection.override.system, + "reason": subsection.override.override_reason, + } + def get_block_key(self, subsection): return str(subsection.location) diff --git a/lms/djangoapps/course_home_api/progress/tests/test_views.py b/lms/djangoapps/course_home_api/progress/tests/test_views.py index 9c62041fdd..7c0198e407 100644 --- a/lms/djangoapps/course_home_api/progress/tests/test_views.py +++ b/lms/djangoapps/course_home_api/progress/tests/test_views.py @@ -2,9 +2,11 @@ Tests for Progress Tab API in the Course Home API """ +from datetime import datetime, timedelta import dateutil import ddt -from datetime import datetime, timedelta # lint-amnesty, pylint: disable=wrong-import-order +from django.conf import settings + from pytz import UTC from unittest.mock import patch # lint-amnesty, pylint: disable=wrong-import-order from django.urls import reverse @@ -17,6 +19,12 @@ from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests from lms.djangoapps.course_home_api.models import DisableProgressPageStackedConfig from lms.djangoapps.course_home_api.toggles import COURSE_HOME_MICROFRONTEND_PROGRESS_TAB +from lms.djangoapps.grades.config.tests.utils import persistent_grades_feature_flags +from lms.djangoapps.grades.constants import GradeOverrideFeatureEnum +from lms.djangoapps.grades.models import ( + PersistentSubsectionGrade, + PersistentSubsectionGradeOverride +) from lms.djangoapps.verify_student.models import ManualVerification from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.course_date_signals.utils import MIN_DURATION @@ -184,6 +192,50 @@ class ProgressTabTestViews(BaseCourseHomeTests): assert not gated_score['learner_has_access'] assert ungated_score['learner_has_access'] + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=True) + @patch.dict(settings.FEATURES, {'ASSUME_ZERO_GRADE_IF_ABSENT_FOR_ALL_TESTS': False}) + def test_override_is_visible(self): + with persistent_grades_feature_flags(global_flag=True): + chapter = ItemFactory(parent=self.course, category='chapter') + subsection = ItemFactory.create(parent=chapter, category="sequential", display_name="Subsection") + + CourseEnrollment.enroll(self.user, self.course.id) + + params = { + "user_id": self.user.id, + "usage_key": subsection.location, + "course_version": self.course.course_version, + "subtree_edited_timestamp": "2016-08-01 18:53:24.354741Z", + "earned_all": 6.0, + "possible_all": 12.0, + "earned_graded": 6.0, + "possible_graded": 8.0, + "visible_blocks": [], + "first_attempted": datetime.now(), + } + + created_grade = PersistentSubsectionGrade.update_or_create_grade(**params) + proctoring_failure_comment = "Failed Test Proctoring" + override = PersistentSubsectionGradeOverride.update_or_create_override( + requesting_user=self.staff_user, + subsection_grade_model=created_grade, + earned_all_override=0.0, + earned_graded_override=0.0, + system=GradeOverrideFeatureEnum.proctoring, + feature=GradeOverrideFeatureEnum.proctoring, + comment=proctoring_failure_comment + ) + + response = self.client.get(self.url) + assert response.status_code == 200 + + sections = response.data['section_scores'] + overridden_subsection = sections[1]['subsections'][0] + override_entry = overridden_subsection["override"] + + assert override_entry['system'] == GradeOverrideFeatureEnum.proctoring + assert override_entry['reason'] == proctoring_failure_comment + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=True) def test_view_other_students_progress_page(self): # Test the ability to view progress pages of other students by changing the url diff --git a/lms/djangoapps/course_home_api/progress/views.py b/lms/djangoapps/course_home_api/progress/views.py index 4b17dc451b..70afa42f7b 100644 --- a/lms/djangoapps/course_home_api/progress/views.py +++ b/lms/djangoapps/course_home_api/progress/views.py @@ -102,6 +102,9 @@ class ProgressTabView(RetrieveAPIView): learner_has_access: (bool) whether the learner has access to the subsection (could be FBE gated) num_points_earned: (int) the amount of points the user has earned for the given subsection num_points_possible: (int) the total amount of points possible for the given subsection + override: Optional object if grade has been overridden by proctor or grading change + system: (str) either GRADING or PROCTORING + reason: (str) a comment on the grading override percent_graded: (float) the percentage of total points the user has received a grade for in a given subsection problem_scores: List of objects that represent individual problem scores with the following fields: