Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-proctoring
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-proctoring
Commits
45b572a4
Commit
45b572a4
authored
Oct 08, 2015
by
Hasnain
Committed by
Chris Dodge
Oct 09, 2015
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
PHX-149 added new screen
parent
d30a3a91
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
251 additions
and
23 deletions
+251
-23
edx_proctoring/api.py
+12
-3
edx_proctoring/constants.py
+12
-0
edx_proctoring/models.py
+1
-1
edx_proctoring/templates/proctored_exam/waiting_for_app_shutdown.html
+51
-0
edx_proctoring/tests/test_api.py
+53
-3
edx_proctoring/tests/test_views.py
+77
-1
edx_proctoring/utils.py
+14
-0
edx_proctoring/views.py
+31
-15
No files found.
edx_proctoring/api.py
View file @
45b572a4
...
@@ -38,7 +38,10 @@ from edx_proctoring.serializers import (
...
@@ -38,7 +38,10 @@ from edx_proctoring.serializers import (
ProctoredExamStudentAttemptSerializer
,
ProctoredExamStudentAttemptSerializer
,
ProctoredExamStudentAllowanceSerializer
,
ProctoredExamStudentAllowanceSerializer
,
)
)
from
edx_proctoring.utils
import
humanized_time
from
edx_proctoring.utils
import
(
humanized_time
,
has_client_app_shutdown
)
from
edx_proctoring.backends
import
get_backend_provider
from
edx_proctoring.backends
import
get_backend_provider
from
edx_proctoring.runtime
import
get_runtime_service
from
edx_proctoring.runtime
import
get_runtime_service
...
@@ -1312,7 +1315,10 @@ def _get_practice_exam_view(exam, context, exam_id, user_id, course_id):
...
@@ -1312,7 +1315,10 @@ def _get_practice_exam_view(exam, context, exam_id, user_id, course_id):
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
error
:
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
error
:
student_view_template
=
'practice_exam/error.html'
student_view_template
=
'practice_exam/error.html'
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
submitted
:
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
submitted
:
student_view_template
=
'practice_exam/submitted.html'
if
has_client_app_shutdown
(
attempt
):
student_view_template
=
'practice_exam/submitted.html'
else
:
student_view_template
=
'proctored_exam/waiting_for_app_shutdown.html'
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
ready_to_submit
:
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
ready_to_submit
:
student_view_template
=
'proctored_exam/ready_to_submit.html'
student_view_template
=
'proctored_exam/ready_to_submit.html'
...
@@ -1414,7 +1420,10 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
...
@@ -1414,7 +1420,10 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
timed_out
:
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
timed_out
:
raise
NotImplementedError
(
'There is no defined rendering for ProctoredExamStudentAttemptStatus.timed_out!'
)
raise
NotImplementedError
(
'There is no defined rendering for ProctoredExamStudentAttemptStatus.timed_out!'
)
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
submitted
:
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
submitted
:
student_view_template
=
'proctored_exam/submitted.html'
if
has_client_app_shutdown
(
attempt
):
student_view_template
=
'proctored_exam/submitted.html'
else
:
student_view_template
=
'proctored_exam/waiting_for_app_shutdown.html'
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
verified
:
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
verified
:
student_view_template
=
'proctored_exam/verified.html'
student_view_template
=
'proctored_exam/verified.html'
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
rejected
:
elif
attempt_status
==
ProctoredExamStudentAttemptStatus
.
rejected
:
...
...
edx_proctoring/constants.py
View file @
45b572a4
...
@@ -41,3 +41,15 @@ REQUIRE_FAILURE_SECOND_REVIEWS = (
...
@@ -41,3 +41,15 @@ REQUIRE_FAILURE_SECOND_REVIEWS = (
'REQUIRE_FAILURE_SECOND_REVIEWS'
in
settings
.
PROCTORING_SETTINGS
'REQUIRE_FAILURE_SECOND_REVIEWS'
in
settings
.
PROCTORING_SETTINGS
else
getattr
(
settings
,
'REQUIRE_FAILURE_SECOND_REVIEWS'
,
True
)
else
getattr
(
settings
,
'REQUIRE_FAILURE_SECOND_REVIEWS'
,
True
)
)
)
SOFTWARE_SECURE_CLIENT_TIMEOUT
=
(
settings
.
PROCTORING_SETTINGS
[
'SOFTWARE_SECURE_CLIENT_TIMEOUT'
]
if
'SOFTWARE_SECURE_CLIENT_TIMEOUT'
in
settings
.
PROCTORING_SETTINGS
else
getattr
(
settings
,
'SOFTWARE_SECURE_CLIENT_TIMEOUT'
,
30
)
)
SOFTWARE_SECURE_SHUT_DOWN_GRACEPERIOD
=
(
settings
.
PROCTORING_SETTINGS
[
'SOFTWARE_SECURE_SHUT_DOWN_GRACEPERIOD'
]
if
'SOFTWARE_SECURE_SHUT_DOWN_GRACEPERIOD'
in
settings
.
PROCTORING_SETTINGS
else
getattr
(
settings
,
'SOFTWARE_SECURE_SHUT_DOWN_GRACEPERIOD'
,
10
)
)
edx_proctoring/models.py
View file @
45b572a4
...
@@ -109,7 +109,7 @@ class ProctoredExamStudentAttemptStatus(object):
...
@@ -109,7 +109,7 @@ class ProctoredExamStudentAttemptStatus(object):
might change over time.
might change over time.
"""
"""
# the student is eligible to decide if he/she wants to p
e
rsue credit
# the student is eligible to decide if he/she wants to p
u
rsue credit
eligible
=
'eligible'
eligible
=
'eligible'
# the attempt record has been created, but the exam has not yet
# the attempt record has been created, but the exam has not yet
...
...
edx_proctoring/templates/proctored_exam/waiting_for_app_shutdown.html
0 → 100644
View file @
45b572a4
{% load i18n %}
<div
class=
"sequence proctored-exam completed"
data-exam-id=
"{{exam_id}}"
>
<h3>
{% blocktrans %}
You are about to complete your proctored exam
{% endblocktrans %}
</h3>
<p>
{% blocktrans %}
Make sure you return to the proctoring software and select
<strong>
Quit
</strong>
to end proctoring
and submit your exam.
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
If you have questions about the status of your proctored exam results, contact {{ platform_name }} Support.
{% endblocktrans %}
</p>
</div>
<script
type=
"text/javascript"
>
var
_waiting_for_app_timeout
=
null
;
$
(
document
).
ready
(
function
(){
_waiting_for_app_timeout
=
setInterval
(
poll_exam_started
,
1000
);
});
function
poll_exam_started
()
{
var
url
=
'{{ exam_started_poll_url }}'
;
$
.
ajax
(
url
).
success
(
function
(
data
){
if
(
data
.
status
===
'submitted'
)
{
// Do we believe the client proctoring app has shut down
// if so, then refresh the page which will expose
// other content
if
(
data
.
client_has_shutdown
!==
undefined
&&
data
.
client_has_shutdown
)
{
if
(
_waiting_for_app_timeout
!=
null
)
{
clearInterval
(
_waiting_for_app_timeout
)
}
// we've state transitioned, so refresh the page
// to reflect the new state (which will expose the test)
location
.
reload
();
}
}
});
}
</script>
edx_proctoring/tests/test_api.py
View file @
45b572a4
...
@@ -105,6 +105,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -105,6 +105,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
self
.
chose_proctored_exam_msg
=
'Follow these steps to set up and start your proctored exam'
self
.
chose_proctored_exam_msg
=
'Follow these steps to set up and start your proctored exam'
self
.
proctored_exam_optout_msg
=
'Take this exam as an open exam instead'
self
.
proctored_exam_optout_msg
=
'Take this exam as an open exam instead'
self
.
proctored_exam_completed_msg
=
'Are you sure you want to end your proctored exam'
self
.
proctored_exam_completed_msg
=
'Are you sure you want to end your proctored exam'
self
.
proctored_exam_waiting_for_app_shutdown_msg
=
'You are about to complete your proctored exam'
self
.
proctored_exam_submitted_msg
=
'You have submitted this proctored exam for review'
self
.
proctored_exam_submitted_msg
=
'You have submitted this proctored exam for review'
self
.
proctored_exam_verified_msg
=
'Your proctoring session was reviewed and passed all requirements'
self
.
proctored_exam_verified_msg
=
'Your proctoring session was reviewed and passed all requirements'
self
.
proctored_exam_rejected_msg
=
'Your proctoring session was reviewed and did not pass requirements'
self
.
proctored_exam_rejected_msg
=
'Your proctoring session was reviewed and did not pass requirements'
...
@@ -243,6 +244,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -243,6 +244,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
"""
"""
return
ProctoredExamStudentAttempt
.
objects
.
create
(
return
ProctoredExamStudentAttempt
.
objects
.
create
(
proctored_exam_id
=
self
.
practice_exam_id
,
proctored_exam_id
=
self
.
practice_exam_id
,
taking_as_proctored
=
True
,
user_id
=
self
.
user_id
,
user_id
=
self
.
user_id
,
external_id
=
self
.
external_id
,
external_id
=
self
.
external_id
,
started_at
=
started_at
if
started_at
else
datetime
.
now
(
pytz
.
UTC
),
started_at
=
started_at
if
started_at
else
datetime
.
now
(
pytz
.
UTC
),
...
@@ -1067,6 +1069,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -1067,6 +1069,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
"""
"""
exam_attempt
=
self
.
_create_started_exam_attempt
()
exam_attempt
=
self
.
_create_started_exam_attempt
()
exam_attempt
.
status
=
ProctoredExamStudentAttemptStatus
.
submitted
exam_attempt
.
status
=
ProctoredExamStudentAttemptStatus
.
submitted
exam_attempt
.
last_poll_timestamp
=
datetime
.
now
(
pytz
.
UTC
)
exam_attempt
.
save
()
exam_attempt
.
save
()
rendered_response
=
get_student_view
(
rendered_response
=
get_student_view
(
...
@@ -1079,7 +1082,21 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -1079,7 +1082,21 @@ class ProctoredExamApiTests(LoggedInTestCase):
'default_time_limit_mins'
:
90
'default_time_limit_mins'
:
90
}
}
)
)
self
.
assertIn
(
self
.
proctored_exam_submitted_msg
,
rendered_response
)
self
.
assertIn
(
self
.
proctored_exam_waiting_for_app_shutdown_msg
,
rendered_response
)
reset_time
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
minutes
=
2
)
with
freeze_time
(
reset_time
):
rendered_response
=
get_student_view
(
user_id
=
self
.
user_id
,
course_id
=
self
.
course_id
,
content_id
=
self
.
content_id
,
context
=
{
'is_proctored'
:
True
,
'display_name'
:
self
.
exam_name
,
'default_time_limit_mins'
:
90
}
)
self
.
assertIn
(
self
.
proctored_exam_submitted_msg
,
rendered_response
)
def
test_get_studentview_submitted_status_practiceexam
(
self
):
def
test_get_studentview_submitted_status_practiceexam
(
self
):
"""
"""
...
@@ -1087,6 +1104,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -1087,6 +1104,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
"""
"""
exam_attempt
=
self
.
_create_started_practice_exam_attempt
()
exam_attempt
=
self
.
_create_started_practice_exam_attempt
()
exam_attempt
.
status
=
ProctoredExamStudentAttemptStatus
.
submitted
exam_attempt
.
status
=
ProctoredExamStudentAttemptStatus
.
submitted
exam_attempt
.
last_poll_timestamp
=
datetime
.
now
(
pytz
.
UTC
)
exam_attempt
.
save
()
exam_attempt
.
save
()
rendered_response
=
get_student_view
(
rendered_response
=
get_student_view
(
...
@@ -1099,7 +1117,21 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -1099,7 +1117,21 @@ class ProctoredExamApiTests(LoggedInTestCase):
'default_time_limit_mins'
:
90
'default_time_limit_mins'
:
90
}
}
)
)
self
.
assertIn
(
self
.
practice_exam_submitted_msg
,
rendered_response
)
self
.
assertIn
(
self
.
proctored_exam_waiting_for_app_shutdown_msg
,
rendered_response
)
reset_time
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
minutes
=
2
)
with
freeze_time
(
reset_time
):
rendered_response
=
get_student_view
(
user_id
=
self
.
user_id
,
course_id
=
self
.
course_id
,
content_id
=
self
.
content_id_practice
,
context
=
{
'is_proctored'
:
True
,
'display_name'
:
self
.
exam_name
,
'default_time_limit_mins'
:
90
}
)
self
.
assertIn
(
self
.
practice_exam_submitted_msg
,
rendered_response
)
def
test_get_studentview_created_status_practiceexam
(
self
):
def
test_get_studentview_created_status_practiceexam
(
self
):
"""
"""
...
@@ -2046,7 +2078,25 @@ class ProctoredExamApiTests(LoggedInTestCase):
...
@@ -2046,7 +2078,25 @@ class ProctoredExamApiTests(LoggedInTestCase):
}
}
)
)
self
.
assertIsNotNone
(
rendered_response
)
self
.
assertIsNotNone
(
rendered_response
)
self
.
assertIn
(
self
.
footer_msg
,
rendered_response
)
if
status
==
ProctoredExamStudentAttemptStatus
.
submitted
:
exam_attempt
.
last_poll_timestamp
=
datetime
.
now
(
pytz
.
UTC
)
exam_attempt
.
save
()
reset_time
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
minutes
=
2
)
with
freeze_time
(
reset_time
):
rendered_response
=
get_student_view
(
user_id
=
self
.
user_id
,
course_id
=
self
.
course_id
,
content_id
=
self
.
content_id
,
context
=
{
'is_proctored'
:
True
,
'display_name'
:
self
.
exam_name
,
'default_time_limit_mins'
:
90
}
)
self
.
assertIn
(
self
.
footer_msg
,
rendered_response
)
else
:
self
.
assertIn
(
self
.
footer_msg
,
rendered_response
)
def
test_requirement_status_order
(
self
):
def
test_requirement_status_order
(
self
):
"""
"""
...
...
edx_proctoring/tests/test_views.py
View file @
45b572a4
...
@@ -5,7 +5,7 @@ All tests for the proctored_exams.py
...
@@ -5,7 +5,7 @@ All tests for the proctored_exams.py
import
json
import
json
import
pytz
import
pytz
import
ddt
import
ddt
from
mock
import
Mock
from
mock
import
Mock
,
patch
from
freezegun
import
freeze_time
from
freezegun
import
freeze_time
from
httmock
import
HTTMock
from
httmock
import
HTTMock
from
string
import
Template
# pylint: disable=deprecated-module
from
string
import
Template
# pylint: disable=deprecated-module
...
@@ -20,6 +20,9 @@ from edx_proctoring.models import (
...
@@ -20,6 +20,9 @@ from edx_proctoring.models import (
ProctoredExamStudentAllowance
,
ProctoredExamStudentAllowance
,
ProctoredExamStudentAttemptStatus
,
ProctoredExamStudentAttemptStatus
,
)
)
from
edx_proctoring.exceptions
import
(
ProctoredExamIllegalStatusTransition
,
)
from
edx_proctoring.views
import
require_staff
,
require_course_or_global_staff
from
edx_proctoring.views
import
require_staff
,
require_course_or_global_staff
from
edx_proctoring.api
import
(
from
edx_proctoring.api
import
(
create_exam
,
create_exam
,
...
@@ -408,6 +411,34 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
...
@@ -408,6 +411,34 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
set_runtime_service
(
'instructor'
,
MockInstructorService
(
is_user_course_staff
=
True
))
set_runtime_service
(
'instructor'
,
MockInstructorService
(
is_user_course_staff
=
True
))
def
_create_exam_attempt
(
self
):
"""
Create and start the exam attempt, and return the exam attempt object
"""
# Create an exam.
proctored_exam
=
ProctoredExam
.
objects
.
create
(
course_id
=
'a/b/c'
,
content_id
=
'test_content'
,
exam_name
=
'Test Exam'
,
external_id
=
'123aXqe3'
,
time_limit_mins
=
90
)
attempt_data
=
{
'exam_id'
:
proctored_exam
.
id
,
'external_id'
:
proctored_exam
.
external_id
,
'start_clock'
:
True
,
}
# Starting exam attempt
response
=
self
.
client
.
post
(
reverse
(
'edx_proctoring.proctored_exam.attempt.collection'
),
attempt_data
)
self
.
assertEqual
(
response
.
status_code
,
200
)
return
ProctoredExamStudentAttempt
.
objects
.
get_exam_attempt
(
proctored_exam
.
id
,
self
.
user
.
id
)
def
test_start_exam_create
(
self
):
def
test_start_exam_create
(
self
):
"""
"""
Start an exam (create an exam attempt)
Start an exam (create an exam attempt)
...
@@ -617,6 +648,51 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
...
@@ -617,6 +648,51 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
response_data
=
json
.
loads
(
response
.
content
)
response_data
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
response_data
[
'status'
],
ProctoredExamStudentAttemptStatus
.
error
)
self
.
assertEqual
(
response_data
[
'status'
],
ProctoredExamStudentAttemptStatus
.
error
)
def
test_attempt_status_waiting_for_app_shutdown
(
self
):
"""
Test to confirm that attempt status is submitted when proctored client is shutdown
"""
exam_attempt
=
self
.
_create_exam_attempt
()
exam_attempt
.
last_poll_timestamp
=
datetime
.
now
(
pytz
.
UTC
)
exam_attempt
.
status
=
ProctoredExamStudentAttemptStatus
.
submitted
exam_attempt
.
save
()
response
=
self
.
client
.
get
(
reverse
(
'edx_proctoring.proctored_exam.attempt'
,
args
=
[
exam_attempt
.
id
])
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response_data
=
json
.
loads
(
response
.
content
)
self
.
assertFalse
(
response_data
[
'client_has_shutdown'
])
# now reset the time to 2 minutes in the future.
reset_time
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
minutes
=
2
)
with
freeze_time
(
reset_time
):
response
=
self
.
client
.
get
(
reverse
(
'edx_proctoring.proctored_exam.attempt'
,
args
=
[
exam_attempt
.
id
])
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response_data
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
response_data
[
'client_has_shutdown'
])
def
test_attempt_status_for_exception
(
self
):
"""
Test to confirm that exception will not effect the API call
"""
exam_attempt
=
self
.
_create_exam_attempt
()
exam_attempt
.
last_poll_timestamp
=
datetime
.
now
(
pytz
.
UTC
)
exam_attempt
.
status
=
ProctoredExamStudentAttemptStatus
.
verified
exam_attempt
.
save
()
# now reset the time to 2 minutes in the future.
reset_time
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
minutes
=
2
)
with
patch
(
'edx_proctoring.api.update_attempt_status'
,
Mock
(
side_effect
=
ProctoredExamIllegalStatusTransition
)):
with
freeze_time
(
reset_time
):
response
=
self
.
client
.
get
(
reverse
(
'edx_proctoring.proctored_exam.attempt'
,
args
=
[
exam_attempt
.
id
])
)
self
.
assertEqual
(
response
.
status_code
,
200
)
def
test_attempt_status_stickiness
(
self
):
def
test_attempt_status_stickiness
(
self
):
"""
"""
Test to confirm that a status timeout error will not alter a completed state
Test to confirm that a status timeout error will not alter a completed state
...
...
edx_proctoring/utils.py
View file @
45b572a4
...
@@ -15,6 +15,7 @@ from edx_proctoring.models import (
...
@@ -15,6 +15,7 @@ from edx_proctoring.models import (
ProctoredExamStudentAttempt
,
ProctoredExamStudentAttempt
,
ProctoredExamStudentAttemptHistory
,
ProctoredExamStudentAttemptHistory
,
)
)
from
edx_proctoring
import
constants
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -114,3 +115,16 @@ def locate_attempt_by_attempt_code(attempt_code):
...
@@ -114,3 +115,16 @@ def locate_attempt_by_attempt_code(attempt_code):
log
.
error
(
err_msg
)
log
.
error
(
err_msg
)
return
(
attempt_obj
,
is_archived_attempt
)
return
(
attempt_obj
,
is_archived_attempt
)
def
has_client_app_shutdown
(
attempt
):
"""
Returns True if the client app has shut down, False otherwise
"""
# we never heard from the client, so it must not have started
if
not
attempt
[
'last_poll_timestamp'
]:
return
True
elapsed_time
=
(
datetime
.
now
(
pytz
.
UTC
)
-
attempt
[
'last_poll_timestamp'
])
.
total_seconds
()
return
elapsed_time
>
constants
.
SOFTWARE_SECURE_SHUT_DOWN_GRACEPERIOD
edx_proctoring/views.py
View file @
45b572a4
...
@@ -40,16 +40,20 @@ from edx_proctoring.exceptions import (
...
@@ -40,16 +40,20 @@ from edx_proctoring.exceptions import (
ProctoredExamIllegalStatusTransition
,
ProctoredExamIllegalStatusTransition
,
ProctoredExamNotActiveException
,
ProctoredExamNotActiveException
,
)
)
from
edx_proctoring
import
constants
from
edx_proctoring.runtime
import
get_runtime_service
from
edx_proctoring.runtime
import
get_runtime_service
from
edx_proctoring.serializers
import
ProctoredExamSerializer
,
ProctoredExamStudentAttemptSerializer
from
edx_proctoring.serializers
import
ProctoredExamSerializer
,
ProctoredExamStudentAttemptSerializer
from
edx_proctoring.models
import
ProctoredExamStudentAttemptStatus
,
ProctoredExamStudentAttempt
,
ProctoredExam
from
edx_proctoring.models
import
ProctoredExamStudentAttemptStatus
,
ProctoredExamStudentAttempt
,
ProctoredExam
from
.utils
import
AuthenticatedAPIView
,
get_time_remaining_for_attempt
,
humanized_time
from
edx_proctoring.utils
import
(
AuthenticatedAPIView
,
get_time_remaining_for_attempt
,
humanized_time
,
has_client_app_shutdown
,
)
ATTEMPTS_PER_PAGE
=
25
ATTEMPTS_PER_PAGE
=
25
SOFTWARE_SECURE_CLIENT_TIMEOUT
=
settings
.
PROCTORING_SETTINGS
.
get
(
'SOFTWARE_SECURE_CLIENT_TIMEOUT'
,
30
)
LOG
=
logging
.
getLogger
(
"edx_proctoring_views"
)
LOG
=
logging
.
getLogger
(
"edx_proctoring_views"
)
...
@@ -323,18 +327,30 @@ class StudentProctoredExamAttempt(AuthenticatedAPIView):
...
@@ -323,18 +327,30 @@ class StudentProctoredExamAttempt(AuthenticatedAPIView):
# and if it is older than SOFTWARE_SECURE_CLIENT_TIMEOUT
# and if it is older than SOFTWARE_SECURE_CLIENT_TIMEOUT
# then attempt status should be marked as error.
# then attempt status should be marked as error.
last_poll_timestamp
=
attempt
[
'last_poll_timestamp'
]
last_poll_timestamp
=
attempt
[
'last_poll_timestamp'
]
if
last_poll_timestamp
is
not
None
\
and
(
datetime
.
now
(
pytz
.
UTC
)
-
last_poll_timestamp
)
.
total_seconds
()
>
SOFTWARE_SECURE_CLIENT_TIMEOUT
:
# if we never heard from the client, then we assume it is shut down
try
:
attempt
[
'client_has_shutdown'
]
=
last_poll_timestamp
is
None
update_attempt_status
(
attempt
[
'proctored_exam'
][
'id'
],
if
last_poll_timestamp
is
not
None
:
attempt
[
'user'
][
'id'
],
# Let's pass along information if we think the SoftwareSecure has completed
ProctoredExamStudentAttemptStatus
.
error
# a healthy shutdown which is when our attempt is in a 'submitted' status
)
if
attempt
[
'status'
]
==
ProctoredExamStudentAttemptStatus
.
submitted
:
attempt
[
'status'
]
=
ProctoredExamStudentAttemptStatus
.
error
attempt
[
'client_has_shutdown'
]
=
has_client_app_shutdown
(
attempt
)
except
ProctoredExamIllegalStatusTransition
:
else
:
# don't transition a completed state to an error state
# otherwise, let's see if the shutdown happened in error
pass
# e.g. a crash
time_passed_since_last_poll
=
(
datetime
.
now
(
pytz
.
UTC
)
-
last_poll_timestamp
)
.
total_seconds
()
if
time_passed_since_last_poll
>
constants
.
SOFTWARE_SECURE_CLIENT_TIMEOUT
:
try
:
update_attempt_status
(
attempt
[
'proctored_exam'
][
'id'
],
attempt
[
'user'
][
'id'
],
ProctoredExamStudentAttemptStatus
.
error
)
attempt
[
'status'
]
=
ProctoredExamStudentAttemptStatus
.
error
except
ProctoredExamIllegalStatusTransition
:
# don't transition a completed state to an error state
pass
# add in the computed time remaining as a helper to a client app
# add in the computed time remaining as a helper to a client app
time_remaining_seconds
=
get_time_remaining_for_attempt
(
attempt
)
time_remaining_seconds
=
get_time_remaining_for_attempt
(
attempt
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment