Commit 98c6bf37 by Chris Dodge

address some review feedback

parent c57c8095
...@@ -418,6 +418,15 @@ def update_attempt_status(exam_id, user_id, to_status): ...@@ -418,6 +418,15 @@ def update_attempt_status(exam_id, user_id, to_status):
Internal helper to handle state transitions of attempt status Internal helper to handle state transitions of attempt status
""" """
# In some configuration we may treat timeouts the same
# as the user saying he/she wises to submit the exam
alias_timeout = (
to_status == ProctoredExamStudentAttemptStatus.timed_out and
not settings.PROCTORING_SETTINGS.get('ALLOW_TIMED_OUT_STATE', False)
)
if alias_timeout:
to_status = ProctoredExamStudentAttemptStatus.ready_to_submit
exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt(exam_id, user_id) exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt(exam_id, user_id)
if exam_attempt_obj is None: if exam_attempt_obj is None:
raise StudentExamAttemptDoesNotExistsException('Error. Trying to look up an exam that does not exist.') raise StudentExamAttemptDoesNotExistsException('Error. Trying to look up an exam that does not exist.')
......
...@@ -39,6 +39,7 @@ def start_exam_callback(request, attempt_code): # pylint: disable=unused-argume ...@@ -39,6 +39,7 @@ def start_exam_callback(request, attempt_code): # pylint: disable=unused-argume
IMPORTANT: This is an unauthenticated endpoint, so be VERY CAREFUL about extending IMPORTANT: This is an unauthenticated endpoint, so be VERY CAREFUL about extending
this endpoint this endpoint
""" """
attempt = get_exam_attempt_by_code(attempt_code) attempt = get_exam_attempt_by_code(attempt_code)
if not attempt: if not attempt:
return HttpResponse( return HttpResponse(
......
...@@ -102,7 +102,6 @@ var edx = edx || {}; ...@@ -102,7 +102,6 @@ var edx = edx || {};
return this; return this;
}, },
unloadMessage: function () { unloadMessage: function () {
return null;
return gettext("As you are currently taking a proctored exam,\n" + return gettext("As you are currently taking a proctored exam,\n" +
"you should not be navigation away from the exam.\n" + "you should not be navigation away from the exam.\n" +
"This may be considered as a violation of the \n" + "This may be considered as a violation of the \n" +
...@@ -115,10 +114,8 @@ var edx = edx || {}; ...@@ -115,10 +114,8 @@ var edx = edx || {};
var url = self.model.url + '/' + self.model.get('attempt_id'); var url = self.model.url + '/' + self.model.get('attempt_id');
$.ajax(url).success(function(data) { $.ajax(url).success(function(data) {
if (data.status === 'error') { if (data.status === 'error') {
// Let the student know that his exam has failed due to an error. // The proctoring session is in error state
// This alert may or may not bring the browser window back to the // refresh the page to
// foreground (depending on browser as well as user settings)
alert(gettext('Your exam has failed'));
clearInterval(self.timerId); // stop the timer once the time finishes. clearInterval(self.timerId); // stop the timer once the time finishes.
$(window).unbind('beforeunload', self.unloadMessage); $(window).unbind('beforeunload', self.unloadMessage);
// refresh the page when the timer expired // refresh the page when the timer expired
......
...@@ -53,6 +53,17 @@ ...@@ -53,6 +53,17 @@
var exam_id = $(this).data('exam-id'); var exam_id = $(this).data('exam-id');
var attempt_proctored = $(this).data('attempt-proctored'); var attempt_proctored = $(this).data('attempt-proctored');
var start_immediately = $(this).data('start-immediately'); var start_immediately = $(this).data('start-immediately');
if (!attempt_proctored) {
var msg = gettext(
"Are you sure you want to take this exam without proctoring? " +
"This will make you no longer eligible to earn credit for this course."
)
if (!confirm(msg)) {
return;
}
}
if (typeof action_url === "undefined" ) { if (typeof action_url === "undefined" ) {
return false; return false;
} }
......
...@@ -51,11 +51,22 @@ ...@@ -51,11 +51,22 @@
var _waiting_for_proctored_interval = null; var _waiting_for_proctored_interval = null;
$('.proctored-decline-exam').click( $('.proctored-decline-exam').click(
function(event) { function(e) {
e.preventDefault();
e.stopPropagation();
var action_url = $(this).data('change-state-url'); var action_url = $(this).data('change-state-url');
var exam_id = $(this).data('exam-id'); var exam_id = $(this).data('exam-id');
var action = $(this).data('action') var action = $(this).data('action')
var msg = gettext(
"Are you sure you want to take this exam without proctoring? " +
"This will make you no longer eligible to earn credit for this course."
)
if (!confirm(msg)) {
return;
}
// Update the state of the attempt // Update the state of the attempt
$.ajax({ $.ajax({
url: action_url, url: action_url,
......
...@@ -839,6 +839,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -839,6 +839,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
) )
self.assertIsNone(rendered_response) self.assertIsNone(rendered_response)
@patch.dict('django.conf.settings.PROCTORING_SETTINGS', {'ALLOW_TIMED_OUT_STATE': True})
def test_get_studentview_timedout(self): def test_get_studentview_timedout(self):
""" """
Verifies that if we call get_studentview when the timer has expired Verifies that if we call get_studentview when the timer has expired
...@@ -959,6 +960,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -959,6 +960,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
) )
self.assertIn(self.proctored_exam_completed_msg, rendered_response) self.assertIn(self.proctored_exam_completed_msg, rendered_response)
@patch.dict('django.conf.settings.PROCTORING_SETTINGS', {'ALLOW_TIMED_OUT_STATE': True})
def test_get_studentview_expired(self): def test_get_studentview_expired(self):
""" """
Test for get_student_view proctored exam which has expired. Test for get_student_view proctored exam which has expired.
...@@ -1095,6 +1097,7 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -1095,6 +1097,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
(ProctoredExamStudentAttemptStatus.error, ProctoredExamStudentAttemptStatus.started), (ProctoredExamStudentAttemptStatus.error, ProctoredExamStudentAttemptStatus.started),
) )
@ddt.unpack @ddt.unpack
@patch.dict('django.conf.settings.PROCTORING_SETTINGS', {'ALLOW_TIMED_OUT_STATE': True})
def test_illegal_status_transition(self, from_status, to_status): def test_illegal_status_transition(self, from_status, to_status):
""" """
Verify that we cannot reset backwards an attempt status Verify that we cannot reset backwards an attempt status
...@@ -1109,9 +1112,28 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -1109,9 +1112,28 @@ class ProctoredExamApiTests(LoggedInTestCase):
) )
with self.assertRaises(ProctoredExamIllegalStatusTransition): with self.assertRaises(ProctoredExamIllegalStatusTransition):
print '*** from = {} to {}'.format(from_status, to_status)
update_attempt_status( update_attempt_status(
exam_attempt.proctored_exam_id, exam_attempt.proctored_exam_id,
self.user.id, self.user.id,
to_status to_status
) )
def test_alias_timed_out(self):
"""
Verified that timed_out will automatically state transition
to ready_to_submit
"""
exam_attempt = self._create_started_exam_attempt()
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
ProctoredExamStudentAttemptStatus.timed_out
)
exam_attempt = get_exam_attempt_by_id(exam_attempt.id)
self.assertEqual(
exam_attempt['status'],
ProctoredExamStudentAttemptStatus.ready_to_submit
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment