Commit 340bb79c by Chris Dodge

do polling to see when the students attempt status has transitioned to a…

do polling to see when the students attempt status has transitioned to a completed state, then shut down the proctoring software
parent a401b0f1
""" """
Various callback paths Various callback paths that support callbacks from SoftwareSecure
""" """
import logging import logging
from django.template import Context, loader from django.template import Context, loader
from django.http import HttpResponse from django.http import HttpResponse
from django.core.urlresolvers import reverse
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
...@@ -27,7 +28,10 @@ def start_exam_callback(request, attempt_code): # pylint: disable=unused-argume ...@@ -27,7 +28,10 @@ def start_exam_callback(request, attempt_code): # pylint: disable=unused-argume
NOTE: This returns HTML as it will be displayed in an embedded browser NOTE: This returns HTML as it will be displayed in an embedded browser
This is an authenticated endpoint and the attempt_code is passed in This is an authenticated endpoint and the attempt_code is passed in
as a query string parameter as part of the URL path
IMPORTANT: This is an unauthenticated endpoint, so be VERY CAREFUL about extending
this endpoint
""" """
attempt = get_exam_attempt_by_code(attempt_code) attempt = get_exam_attempt_by_code(attempt_code)
...@@ -41,13 +45,27 @@ def start_exam_callback(request, attempt_code): # pylint: disable=unused-argume ...@@ -41,13 +45,27 @@ def start_exam_callback(request, attempt_code): # pylint: disable=unused-argume
template = loader.get_template('proctoring/proctoring_launch_callback.html') template = loader.get_template('proctoring/proctoring_launch_callback.html')
return HttpResponse(template.render(Context({}))) poll_url = reverse(
'edx_proctoring.anonymous.proctoring_poll_status',
args=[attempt_code]
)
return HttpResponse(
template.render(
Context({
'exam_attempt_status_url': poll_url,
})
)
)
class ExamReviewCallback(APIView): class ExamReviewCallback(APIView):
""" """
This endpoint is called by a 3rd party proctoring review service when This endpoint is called by a 3rd party proctoring review service when
there are results available for us to record there are results available for us to record
IMPORTANT: This is an unauthenticated endpoint, so be VERY CAREFUL about extending
this endpoint
""" """
def post(self, request): def post(self, request):
...@@ -63,3 +81,36 @@ class ExamReviewCallback(APIView): ...@@ -63,3 +81,36 @@ class ExamReviewCallback(APIView):
data='OK', data='OK',
status=200 status=200
) )
class AttemptStatus(APIView):
"""
This endpoint is called by a 3rd party proctoring review service to determine
status of an exam attempt.
IMPORTANT: This is an unauthenticated endpoint, so be VERY CAREFUL about extending
this endpoint
"""
def get(self, request, attempt_code): # pylint: disable=unused-argument
"""
Returns the status of an exam attempt. Given that this is an unauthenticated
caller, we will only return the status string, no additional information
about the exam
"""
attempt = get_exam_attempt_by_code(attempt_code)
if not attempt:
return HttpResponse(
content='You have entered an exam code that is not valid.',
status=404
)
return Response(
data={
# IMPORTANT: Don't add more information to this as it is an
# unauthenticated endpoint
'status': attempt['status'],
},
status=200
)
...@@ -145,7 +145,7 @@ ...@@ -145,7 +145,7 @@
background: rgb(8, 92, 150); background: rgb(8, 92, 150);
} }
</style> </style>
<link rel="stylesheet" href="main.css"> <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
</head> </head>
{% load i18n %} {% load i18n %}
<body> <body>
...@@ -200,6 +200,29 @@ ...@@ -200,6 +200,29 @@
</div> </div>
</div> </div>
<script type="text/javascript">
var _poll_interval = null;
$(document).ready(function() {
_poll_interval = setInterval(
poll_exam_status,
5000
);
});
function poll_exam_status() {
var url = '{{exam_attempt_status_url}}';
$.ajax(url).success(function(data){
// has the end user completed the exam in the LMS?!?
if (data.status === 'completed' || data.status === 'submitted' || data.status === 'verified') {
// Signal that the desktop software should terminate
// NOTE: This is per the API documentation from SoftwareSecure
window.external.quitApplication();
}
});
}
</script>
</body> </body>
</html> </html>
...@@ -66,6 +66,10 @@ urlpatterns = patterns( # pylint: disable=invalid-name ...@@ -66,6 +66,10 @@ urlpatterns = patterns( # pylint: disable=invalid-name
views.ActiveExamsForUserView.as_view(), views.ActiveExamsForUserView.as_view(),
name='edx_proctoring.proctored_exam.active_exams_for_user' name='edx_proctoring.proctored_exam.active_exams_for_user'
), ),
#
# Unauthenticated callbacks from SoftwareSecure. Note we use other
# security token measures to protect data
#
url( url(
r'edx_proctoring/proctoring_launch_callback/start_exam/(?P<attempt_code>[-\w]+)$', r'edx_proctoring/proctoring_launch_callback/start_exam/(?P<attempt_code>[-\w]+)$',
callbacks.start_exam_callback, callbacks.start_exam_callback,
...@@ -76,5 +80,10 @@ urlpatterns = patterns( # pylint: disable=invalid-name ...@@ -76,5 +80,10 @@ urlpatterns = patterns( # pylint: disable=invalid-name
callbacks.ExamReviewCallback.as_view(), callbacks.ExamReviewCallback.as_view(),
name='edx_proctoring.anonymous.proctoring_review_callback' name='edx_proctoring.anonymous.proctoring_review_callback'
), ),
url(
r'edx_proctoring/proctoring_poll_status/(?P<attempt_code>[-\w]+)$',
callbacks.AttemptStatus.as_view(),
name='edx_proctoring.anonymous.proctoring_poll_status'
),
url(r'^', include('rest_framework.urls', namespace='rest_framework')) url(r'^', include('rest_framework.urls', namespace='rest_framework'))
) )
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