diff --git a/edx_exams/apps/core/api.py b/edx_exams/apps/core/api.py index 0bce6b4f..f33c69ff 100644 --- a/edx_exams/apps/core/api.py +++ b/edx_exams/apps/core/api.py @@ -18,7 +18,7 @@ ) from edx_exams.apps.core.models import Exam, ExamAttempt from edx_exams.apps.core.statuses import ExamAttemptStatus -from edx_exams.apps.core.signals.signals import emit_exam_attempt_submitted_event +from edx_exams.apps.core.signals.signals import emit_exam_attempt_rejected_event, emit_exam_attempt_submitted_event log = logging.getLogger(__name__) @@ -94,11 +94,12 @@ def update_attempt_status(attempt_id, to_status): attempt_obj.start_time = datetime.now(pytz.UTC) attempt_obj.allowed_time_limit_mins = _calculate_allowed_mins(attempt_obj.exam) + course_key = CourseKey.from_string(attempt_obj.exam.course_id) + usage_key = UsageKey.from_string(attempt_obj.exam.content_id) + if to_status == ExamAttemptStatus.submitted: attempt_obj.end_time = datetime.now(pytz.UTC) - course_key = CourseKey.from_string(attempt_obj.exam.course_id) - usage_key = UsageKey.from_string(attempt_obj.exam.content_id) emit_exam_attempt_submitted_event( attempt_obj.user, course_key, @@ -106,6 +107,13 @@ def update_attempt_status(attempt_id, to_status): attempt_obj.exam.exam_type ) + if to_status == ExamAttemptStatus.rejected: + emit_exam_attempt_rejected_event( + attempt_obj.user, + course_key, + usage_key + ) + attempt_obj.status = to_status attempt_obj.save() diff --git a/edx_exams/apps/core/signals/handlers.py b/edx_exams/apps/core/signals/handlers.py index a8ec893a..c6f07090 100644 --- a/edx_exams/apps/core/signals/handlers.py +++ b/edx_exams/apps/core/signals/handlers.py @@ -4,7 +4,8 @@ from django.dispatch import receiver from openedx_events.event_bus import get_producer -from openedx_events.learning.signals import EXAM_ATTEMPT_SUBMITTED +from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED, EXAM_ATTEMPT_SUBMITTED + @receiver(EXAM_ATTEMPT_SUBMITTED) def listen_for_exam_attempt_submitted(sender, signal, **kwargs): @@ -18,3 +19,17 @@ def listen_for_exam_attempt_submitted(sender, signal, **kwargs): event_data={'exam_attempt': kwargs['exam_attempt']}, event_metadata=kwargs['metadata'], ) + + +@receiver(EXAM_ATTEMPT_REJECTED) +def listen_for_exam_attempt_rejected(sender, signal, **kwargs): + """ + Publish EXAM_ATTEMPT_REJECTED signal onto the event bus + """ + get_producer().send( + signal=EXAM_ATTEMPT_REJECTED, + topic='exam-attempt-rejected', + event_key_field='exam_attempt.course_key', + event_data={'exam_attempt': kwargs['exam_attempt']}, + event_metadata=kwargs['metadata'], + ) diff --git a/edx_exams/apps/core/signals/signals.py b/edx_exams/apps/core/signals/signals.py index bae3d0c2..a5b43308 100644 --- a/edx_exams/apps/core/signals/signals.py +++ b/edx_exams/apps/core/signals/signals.py @@ -3,7 +3,7 @@ """ from openedx_events.learning.data import ExamAttemptData, UserData, UserPersonalData -from openedx_events.learning.signals import EXAM_ATTEMPT_SUBMITTED +from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED, EXAM_ATTEMPT_SUBMITTED def emit_exam_attempt_submitted_event(user, course_key, usage_key, exam_type): @@ -28,3 +28,22 @@ def emit_exam_attempt_submitted_event(user, course_key, usage_key, exam_type): requesting_user=user_data ) ) + + +def emit_exam_attempt_rejected_event(user, course_key, usage_key): + # .. event_implemented_name: EXAM_ATTEMPT_REJECTED + EXAM_ATTEMPT_REJECTED.send_event( + exam_attempt=ExamAttemptData( + student_user=UserData( + id=user.id, + is_active=user.is_active, + pii=UserPersonalData( + username=user.username, + email=user.email, + name=user.full_name + ) + ), + course_key=course_key, + usage_key=usage_key + ) + ) diff --git a/edx_exams/apps/core/tests/test_api.py b/edx_exams/apps/core/tests/test_api.py index 49553449..adb9ae85 100644 --- a/edx_exams/apps/core/tests/test_api.py +++ b/edx_exams/apps/core/tests/test_api.py @@ -328,6 +328,29 @@ def test_submit_attempt_event_emitted(self, mock_event_send): ) mock_event_send.assert_called_with(exam_attempt=expected_data) + @patch('edx_exams.apps.core.signals.signals.EXAM_ATTEMPT_REJECTED.send_event') + def test_reject_attempt_event_emitted(self, mock_event_send): + update_attempt_status(self.exam_attempt.id, ExamAttemptStatus.rejected) + self.assertEqual(mock_event_send.call_count, 1) + + usage_key = UsageKey.from_string(self.exam.content_id) + course_key = CourseKey.from_string(self.exam.course_id) + + expected_data = ExamAttemptData( + student_user=UserData( + id=self.user.id, + is_active=self.user.is_active, + pii=UserPersonalData( + username=self.user.username, + email=self.user.email, + name=self.user.full_name + ) + ), + course_key=course_key, + usage_key=usage_key + ) + mock_event_send.assert_called_with(exam_attempt=expected_data) + def test_illegal_start(self): """ Test that an already started exam cannot be started