diff --git a/edx_proctoring/__init__.py b/edx_proctoring/__init__.py index 8c06ac15720..8de5a36be38 100644 --- a/edx_proctoring/__init__.py +++ b/edx_proctoring/__init__.py @@ -5,6 +5,6 @@ from __future__ import absolute_import # Be sure to update the version number in edx_proctoring/package.json -__version__ = '2.0.9' +__version__ = '2.1.0' default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name diff --git a/edx_proctoring/api.py b/edx_proctoring/api.py index 66a41665edf..03473ab6c59 100644 --- a/edx_proctoring/api.py +++ b/edx_proctoring/api.py @@ -754,7 +754,8 @@ def mark_exam_attempt_as_ready(exam_id, user_id): def update_attempt_status(exam_id, user_id, to_status, - raise_if_not_found=True, cascade_effects=True, timeout_timestamp=None): + raise_if_not_found=True, cascade_effects=True, timeout_timestamp=None, + update_attributable_to=None): """ Internal helper to handle state transitions of attempt status """ @@ -909,6 +910,8 @@ def update_attempt_status(exam_id, user_id, to_status, cascade_effects=False ) + backend = get_backend_provider(exam) + if ProctoredExamStudentAttemptStatus.needs_grade_override(to_status): grades_service = get_runtime_service('grades') @@ -933,7 +936,11 @@ def update_attempt_status(exam_id, user_id, to_status, course_key_or_id=exam['course_id'], usage_key_or_id=exam_attempt_obj.proctored_exam.content_id, earned_all=REJECTED_GRADE_OVERRIDE_EARNED, - earned_graded=REJECTED_GRADE_OVERRIDE_EARNED + earned_graded=REJECTED_GRADE_OVERRIDE_EARNED, + overrider=update_attributable_to, + comment=('Failed {backend} proctoring'.format(backend=backend.verbose_name) + if backend + else 'Failed Proctoring') ) certificates_service = get_runtime_service('certificates') @@ -1010,7 +1017,6 @@ def update_attempt_status(exam_id, user_id, to_status, attempt = get_exam_attempt(exam_id, user_id) # call back to the backend to register the end of the exam, if necessary - backend = get_backend_provider(exam) if backend: # When onboarding exams change state to a completed status, # look up any exams in the onboarding error states, and delete them. diff --git a/edx_proctoring/signals.py b/edx_proctoring/signals.py index 41a89db2ff1..c5544c3c2ed 100644 --- a/edx_proctoring/signals.py +++ b/edx_proctoring/signals.py @@ -175,7 +175,8 @@ def finish_review_workflow(sender, instance, signal, **kwargs): # pylint: disab attempt['proctored_exam']['id'], attempt['user']['id'], attempt_status, - raise_if_not_found=False + raise_if_not_found=False, + update_attributable_to=review.reviewed_by or None ) # emit an event for 'review_received' diff --git a/edx_proctoring/tests/test_api.py b/edx_proctoring/tests/test_api.py index 55fad99e60d..c83f77d67f6 100644 --- a/edx_proctoring/tests/test_api.py +++ b/edx_proctoring/tests/test_api.py @@ -1116,6 +1116,43 @@ def test_grade_override(self): 'earned_graded': 5.0 }) + def test_grade_overrider(self): + """ + Check we pass along the overrider appropriately, as one would when a rejection-worthy review comes in + """ + grades_service = MockGradesService() + grades_service.override_subsection_grade = MagicMock() + set_runtime_service('grades', grades_service) + exam_attempt = self._create_started_exam_attempt() + update_attempt_status( + exam_attempt.proctored_exam_id, + self.user.id, + ProctoredExamStudentAttemptStatus.rejected, + update_attributable_to=self.user + ) + + assert grades_service.override_subsection_grade.called + # did we pass the user to whom we're attributing the override along? + assert grades_service.override_subsection_grade.call_args[1]['overrider'].id == self.user.id + + def test_grade_override_comment(self): + """ + Check we pass along the backend of the exam for which something failed + """ + grades_service = MockGradesService() + grades_service.override_subsection_grade = MagicMock() + set_runtime_service('grades', grades_service) + exam_attempt = self._create_started_exam_attempt() + update_attempt_status( + exam_attempt.proctored_exam_id, + self.user.id, + ProctoredExamStudentAttemptStatus.rejected + ) + + assert grades_service.override_subsection_grade.called + # did we pass a comment referring to our backend? + assert "Unknown" in grades_service.override_subsection_grade.call_args[1]['comment'] + def test_disabled_grade_override(self): """ Verify that when the REJECTED_EXAM_OVERRIDES_GRADE flag is disabled for a course, diff --git a/edx_proctoring/tests/test_services.py b/edx_proctoring/tests/test_services.py index 37cfbe7b254..9dc21431e20 100644 --- a/edx_proctoring/tests/test_services.py +++ b/edx_proctoring/tests/test_services.py @@ -246,7 +246,7 @@ def get_subsection_grade_override(self, user_id, course_key_or_id, usage_key_or_ return self.overrides.get(key) def override_subsection_grade(self, user_id, course_key_or_id, usage_key_or_id, earned_all=None, - earned_graded=None): + earned_graded=None, overrider=None, comment=None): """Sets grade override earned points for key (user_id + course_key + subsection)""" key = (user_id, course_key_or_id, usage_key_or_id) self.overrides[key] = MockGradeOverride( diff --git a/package.json b/package.json index 8ca41ba5628..6959422de82 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@edx/edx-proctoring", "//": "Be sure to update the version number in edx_proctoring/__init__.py", "//": "Note that the version format is slightly different than that of the Python version when using prereleases.", - "version": "2.0.9", + "version": "2.1.0", "main": "edx_proctoring/static/index.js", "repository": { "type": "git",