From b4cb93ede73293bb51f807496404b5c0a3819410 Mon Sep 17 00:00:00 2001 From: michaelroytman Date: Tue, 3 Oct 2023 17:02:21 -0400 Subject: [PATCH] feat: add grades event bus event handler for rejected special exam This commit adds an event bus event handler to the grades application. This event handler handles the EXAM_ATTEMPT_REJECTED Open edX event, which is emitted when a learner's exam attempt is rejected. The event handler creates a subsection grade override, overriding the grade to 0.0. --- lms/djangoapps/grades/signals/handlers.py | 27 +++++++++++++++- lms/djangoapps/grades/tests/test_handlers.py | 34 ++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/grades/signals/handlers.py b/lms/djangoapps/grades/signals/handlers.py index 15741197aa8e..58c5fb90aace 100644 --- a/lms/djangoapps/grades/signals/handlers.py +++ b/lms/djangoapps/grades/signals/handlers.py @@ -8,7 +8,7 @@ from django.dispatch import receiver from opaque_keys.edx.keys import LearningContextKey -from openedx_events.learning.signals import EXAM_ATTEMPT_VERIFIED +from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED, EXAM_ATTEMPT_VERIFIED from submissions.models import score_reset, score_set from xblock.scorable import ScorableXBlockMixin, Score @@ -322,3 +322,28 @@ def exam_attempt_verified_event_handler(sender, signal, **kwargs): # pylint: di if should_override_grade_on_rejected_exam(course_key): undo_override_subsection_grade(user_data.id, course_key, usage_key, GradeOverrideFeatureEnum.proctoring) + + +@receiver(EXAM_ATTEMPT_REJECTED) +def exam_attempt_rejected_event_handler(sender, signal, **kwargs): # pylint: disable=unused-argument + """ + Consume `EXAM_ATTEMPT_REJECTED` events from the event bus. This will trigger a subsection override. + """ + from ..api import override_subsection_grade + + event_data = kwargs.get('exam_attempt') + override_grade_value = 0.0 + user_data = event_data.student_user + course_key = event_data.course_key + usage_key = event_data.usage_key + + override_subsection_grade( + user_data.id, + course_key, + usage_key, + earned_all=override_grade_value, + earned_graded=override_grade_value, + feature=GradeOverrideFeatureEnum.proctoring, + overrider=None, + comment=None, + ) diff --git a/lms/djangoapps/grades/tests/test_handlers.py b/lms/djangoapps/grades/tests/test_handlers.py index 42ad9aa1bfe2..72424987b5ab 100644 --- a/lms/djangoapps/grades/tests/test_handlers.py +++ b/lms/djangoapps/grades/tests/test_handlers.py @@ -10,10 +10,13 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from openedx_events.data import EventsMetadata from openedx_events.learning.data import ExamAttemptData, UserData, UserPersonalData -from openedx_events.learning.signals import EXAM_ATTEMPT_VERIFIED +from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED, EXAM_ATTEMPT_VERIFIED from common.djangoapps.student.tests.factories import UserFactory -from lms.djangoapps.grades.signals.handlers import exam_attempt_verified_event_handler +from lms.djangoapps.grades.signals.handlers import ( + exam_attempt_rejected_event_handler, + exam_attempt_verified_event_handler +) from ..constants import GradeOverrideFeatureEnum @@ -101,3 +104,30 @@ def test_exam_attempt_verified_event_handler(self, override_enabled, mock_undo_o ) else: mock_undo_override.assert_not_called() + + @mock.patch('lms.djangoapps.grades.api.override_subsection_grade') + def test_exam_attempt_rejected_event_handler(self, mock_override): + exam_event_data = self._get_exam_event_data(self.student_user, + self.course_key, + self.usage_key, + exam_type='proctored') + event_metadata = self._get_exam_event_metadata(EXAM_ATTEMPT_REJECTED) + + event_kwargs = { + 'exam_attempt': exam_event_data, + 'metadata': event_metadata + } + exam_attempt_rejected_event_handler(None, EXAM_ATTEMPT_REJECTED, ** event_kwargs) + + override_grade_value = 0.0 + + mock_override.assert_called_once_with( + self.student_user.id, + self.course_key, + self.usage_key, + earned_all=override_grade_value, + earned_graded=override_grade_value, + feature=GradeOverrideFeatureEnum.proctoring, + overrider=None, + comment=None, + )