def get(self, request, attempt_id): """ HTTP GET Handler. Returns the status of the exam attempt. """ try: attempt_id = int(attempt_id) attempt = get_exam_attempt_by_id(attempt_id) except: attempt = get_exam_attempt_by_code(attempt_id) attempt = get_exam_attempt_by_code(attempt_id) try: if not attempt: err_msg = ('Attempted to access attempt_id {attempt_id} but ' 'it does not exist.'.format(attempt_id=attempt_id)) return Response(status=status.HTTP_400_BAD_REQUEST) # make sure the the attempt belongs to the calling user_id if attempt['user']['id'] != request.user.id: err_msg = ('Attempted to access attempt_id {attempt_id} but ' 'does not have access to it.'.format( attempt_id=attempt_id)) raise ProctoredExamPermissionDenied(err_msg) # add in the computed time remaining as a helper time_remaining_seconds = get_time_remaining_for_attempt(attempt) attempt['time_remaining_seconds'] = time_remaining_seconds accessibility_time_string = _( 'you have {remaining_time} remaining').format( remaining_time=humanized_time( int(round(time_remaining_seconds / 60.0, 0)))) # special case if we are less than a minute, since we don't produce # text translations of granularity at the seconds range if time_remaining_seconds < 60: accessibility_time_string = _( 'you have less than a minute remaining') attempt['accessibility_time_string'] = accessibility_time_string return Response(data=attempt, status=status.HTTP_200_OK) except ProctoredBaseException, ex: LOG.exception(ex) return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": str(ex)})
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) ip_address = get_ip(request) timestamp = datetime.now(pytz.UTC) if not attempt: return HttpResponse( content='You have entered an exam code that is not valid.', status=404 ) update_exam_attempt(attempt['id'], last_poll_timestamp=timestamp, last_poll_ipaddr=ip_address) return Response( data={ # IMPORTANT: Don't add more information to this as it is an # unauthenticated endpoint 'status': attempt['status'], }, status=200 )
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) ip_address = get_ip(request) timestamp = datetime.now(pytz.UTC) if not attempt: return HttpResponse( content='You have entered an exam code that is not valid.', status=404) update_exam_attempt(attempt['id'], last_poll_timestamp=timestamp, last_poll_ipaddr=ip_address) return Response( data={ # IMPORTANT: Don't add more information to this as it is an # unauthenticated endpoint 'status': attempt['status'], }, status=200)
def start_exam_callback(request, attempt_code): # pylint: disable=unused-argument """ A callback endpoint which is called when SoftwareSecure completes the proctoring setup and the exam should be started. This is an authenticated endpoint and the attempt_code is passed in 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) if not attempt: log.warning(u"Attempt code %r cannot be found.", attempt_code) return HttpResponse( content='You have entered an exam code that is not valid.', status=404 ) proctored_exam_id = attempt['proctored_exam']['id'] attempt_status = attempt['status'] user_id = attempt['user']['id'] if attempt_status in [ProctoredExamStudentAttemptStatus.created, ProctoredExamStudentAttemptStatus.download_software_clicked]: mark_exam_attempt_as_ready(proctored_exam_id, user_id) # if a user attempts to re-enter an exam that has not yet been submitted, submit the exam if ProctoredExamStudentAttemptStatus.is_in_progress_status(attempt_status): update_attempt_status(proctored_exam_id, user_id, ProctoredExamStudentAttemptStatus.submitted) else: log.warning(u"Attempted to enter proctored exam attempt {attempt_id} when status was {attempt_status}" .format( attempt_id=attempt['id'], attempt_status=attempt_status, )) if switch_is_active(RPNOWV4_WAFFLE_NAME): # pylint: disable=illegal-waffle-usage course_id = attempt['proctored_exam']['course_id'] content_id = attempt['proctored_exam']['content_id'] exam_url = '' try: exam_url = reverse('jump_to', args=[course_id, content_id]) except NoReverseMatch: log.exception(u"BLOCKING ERROR: Can't find course info url for course %s", course_id) response = HttpResponseRedirect(exam_url) response.set_signed_cookie('exam', attempt['attempt_code']) return response template = loader.get_template('proctored_exam/proctoring_launch_callback.html') return HttpResponse( template.render({ 'platform_name': settings.PLATFORM_NAME, 'link_urls': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}) }) )
def start_exam_callback(request, attempt_code): # pylint: disable=unused-argument """ A callback endpoint which is called when SoftwareSecure completes the proctoring setup and the exam should be started. This is an authenticated endpoint and the attempt_code is passed in 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) if not attempt: log.warning("Attempt code %r cannot be found.", attempt_code) return HttpResponse( content='You have entered an exam code that is not valid.', status=404 ) if attempt['status'] in [ProctoredExamStudentAttemptStatus.created, ProctoredExamStudentAttemptStatus.download_software_clicked]: mark_exam_attempt_as_ready(attempt['proctored_exam']['id'], attempt['user']['id']) else: log.warning("Attempted to enter proctored exam attempt {attempt_id} when status was {attempt_status}" .format( attempt_id=attempt['id'], attempt_status=attempt['status'], )) log.info("Exam %r has been marked as ready", attempt['proctored_exam']['id']) if switch_is_active(RPNOWV4_WAFFLE_NAME): course_id = attempt['proctored_exam']['course_id'] content_id = attempt['proctored_exam']['content_id'] exam_url = '' try: exam_url = reverse('jump_to', args=[course_id, content_id]) except NoReverseMatch: log.exception("BLOCKING ERROR: Can't find course info url for course %s", course_id) response = HttpResponseRedirect(exam_url) response.set_signed_cookie('exam', attempt['attempt_code']) return response template = loader.get_template('proctored_exam/proctoring_launch_callback.html') return HttpResponse( template.render({ 'platform_name': settings.PLATFORM_NAME, 'link_urls': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}) }) )
def put(self, request, attempt_id): """ HTTP POST handler. To stop an exam. """ try: attempt_id = int(attempt_id) attempt = get_exam_attempt_by_id(attempt_id) except: attempt = get_exam_attempt_by_code(attempt_id) try: if not attempt: err_msg = ('Attempted to access attempt_id {attempt_id} but ' 'it does not exist.'.format(attempt_id=attempt_id)) raise StudentExamAttemptDoesNotExistsException(err_msg) # make sure the the attempt belongs to the calling user_id if attempt['user']['id'] != request.user.id: err_msg = ('Attempted to access attempt_id {attempt_id} but ' 'does not have access to it.'.format( attempt_id=attempt_id)) raise ProctoredExamPermissionDenied(err_msg) action = request.data.get('action') if action == 'stop': exam_attempt_id = stop_exam_attempt( exam_id=attempt['proctored_exam']['id'], user_id=request.user.id) elif action == 'start': exam_attempt_id = start_exam_attempt( exam_id=attempt['proctored_exam']['id'], user_id=request.user.id) elif action == 'submit': exam_attempt_id = update_attempt_status( attempt['proctored_exam']['id'], request.user.id, ProctoredExamStudentAttemptStatus.submitted) elif action == 'click_download_software': exam_attempt_id = update_attempt_status( attempt['proctored_exam']['id'], request.user.id, ProctoredExamStudentAttemptStatus.download_software_clicked ) elif action == 'decline': exam_attempt_id = update_attempt_status( attempt['proctored_exam']['id'], request.user.id, ProctoredExamStudentAttemptStatus.declined) return Response({"exam_attempt_id": exam_attempt_id}) except ProctoredBaseException, ex: LOG.exception(ex) return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": str(ex)})
def start_exam_callback(request, attempt_code): # pylint: disable=unused-argument """ A callback endpoint which is called when SoftwareSecure completes the proctoring setup and the exam should be started. This is an authenticated endpoint and the attempt_code is passed in 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) if not attempt: log.warning('attempt_code={attempt_code} cannot be found.'.format( attempt_code=attempt_code)) return HttpResponse( content='You have entered an exam code that is not valid.', status=404) attempt_status = attempt['status'] if attempt_status in [ ProctoredExamStudentAttemptStatus.created, ProctoredExamStudentAttemptStatus.download_software_clicked ]: mark_exam_attempt_as_ready(attempt['id']) # if a user attempts to re-enter an exam that has not yet been submitted, submit the exam if ProctoredExamStudentAttemptStatus.is_in_progress_status(attempt_status): update_attempt_status(attempt['id'], ProctoredExamStudentAttemptStatus.submitted) else: log.warning( 'Attempted to enter proctored exam attempt_id={attempt_id} when status={attempt_status}' .format( attempt_id=attempt['id'], attempt_status=attempt_status, )) course_id = attempt['proctored_exam']['course_id'] content_id = attempt['proctored_exam']['content_id'] exam_url = '' try: exam_url = reverse('jump_to', args=[course_id, content_id]) except NoReverseMatch: log.exception( "BLOCKING ERROR: Can't find course info url for course_id=%s", course_id) response = HttpResponseRedirect(exam_url) response.set_signed_cookie('exam', attempt['attempt_code']) return response
def put(self, request, attempt_code): """ HTTP POST handler. To stop an exam. """ try: attempt = get_exam_attempt_by_code(attempt_code) if not attempt: err_msg = ( 'Attempted to access attempt_code {attempt_code} but ' 'it does not exist.'.format( attempt_code=attempt_code ) ) raise StudentExamAttemptDoesNotExistsException(err_msg) action = request.DATA.get('action') user_id = request.DATA.get('user_id') exam_id = attempt['proctored_exam']['id'] if action and action == 'submit': exam_attempt_id = update_attempt_status( exam_id, user_id, ProctoredExamStudentAttemptStatus.submitted ) if action and action == 'fail': exam_attempt_id = update_attempt_status( exam_id, user_id, ProctoredExamStudentAttemptStatus.error ) if action and action == 'decline': exam_attempt_id = update_attempt_status( exam_id, user_id, ProctoredExamStudentAttemptStatus.timed_out ) return Response({"exam_attempt_id": exam_attempt_id}) except ProctoredBaseException, ex: LOG.exception(ex) return Response( status=status.HTTP_400_BAD_REQUEST, data={"detail": str(ex)} )
def start_exam_callback(request, attempt_code): # pylint: disable=unused-argument """ A callback endpoint which is called when SoftwareSecure completes the proctoring setup and the exam should be started. 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 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) if not attempt: return HttpResponse( content='You have entered an exam code that is not valid.', status=404 ) if attempt['status'] in [ProctoredExamStudentAttemptStatus.created, ProctoredExamStudentAttemptStatus.download_software_clicked]: mark_exam_attempt_as_ready(attempt['proctored_exam']['id'], attempt['user']['id']) template = loader.get_template('proctored_exam/proctoring_launch_callback.html') poll_url = reverse( 'edx_proctoring.anonymous.proctoring_poll_status', args=[attempt_code] ) return HttpResponse( template.render( Context({ 'exam_attempt_status_url': poll_url, 'platform_name': settings.PLATFORM_NAME, 'link_urls': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}) }) ) )
def start_exam_callback(request, attempt_code): # pylint: disable=unused-argument """ A callback endpoint which is called when SoftwareSecure completes the proctoring setup and the exam should be started. 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 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) if not attempt: log.warn("Attempt code %r cannot be found.", attempt_code) return HttpResponse( content='You have entered an exam code that is not valid.', status=404) if attempt['status'] in [ ProctoredExamStudentAttemptStatus.created, ProctoredExamStudentAttemptStatus.download_software_clicked ]: mark_exam_attempt_as_ready(attempt['proctored_exam']['id'], attempt['user']['id']) log.info("Exam %r has been marked as ready", attempt['proctored_exam']['id']) template = loader.get_template( 'proctored_exam/proctoring_launch_callback.html') return HttpResponse( template.render( Context({ 'platform_name': settings.PLATFORM_NAME, 'link_urls': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}) })))
def start_exam_callback(request, attempt_code): # pylint: disable=unused-argument """ A callback endpoint which is called when SoftwareSecure completes the proctoring setup and the exam should be started. 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 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) if not attempt: return HttpResponse( content='You have entered an exam code that is not valid.', status=404) mark_exam_attempt_as_ready(attempt['proctored_exam']['id'], attempt['user']['id']) template = loader.get_template( 'proctoring/proctoring_launch_callback.html') poll_url = reverse('edx_proctoring.anonymous.proctoring_poll_status', args=[attempt_code]) provider_name = get_provider_name_by_course_id( attempt['proctored_exam']['course_id']) proctoring_settings = get_proctoring_settings(provider_name) return HttpResponse( template.render( Context({ 'exam_attempt_status_url': poll_url, 'platform_name': settings.PLATFORM_NAME, 'link_urls': proctoring_settings.get('LINK_URLS', {}) })))
def start_exam_callback(request, attempt_code): # pylint: disable=unused-argument """ A callback endpoint which is called when SoftwareSecure completes the proctoring setup and the exam should be started. 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 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) if not attempt: log.warn("Attempt code %r cannot be found.", attempt_code) return HttpResponse( content='You have entered an exam code that is not valid.', status=404 ) if attempt['status'] in [ProctoredExamStudentAttemptStatus.created, ProctoredExamStudentAttemptStatus.download_software_clicked]: mark_exam_attempt_as_ready(attempt['proctored_exam']['id'], attempt['user']['id']) #log.info("Exam %r has been marked as ready", attempt['proctored_exam']['id']) #template = loader.get_template('proctored_exam/proctoring_launch_callback.html') #return HttpResponse( # template.render( # Context({ # 'platform_name': settings.PLATFORM_NAME, # 'link_urls': get_proctoring_settings(attempt['provider_name']).get('LINK_URLS', {}) # }) # ) #) scheme = 'https' if getattr(settings, 'HTTPS', 'on') == 'on' else 'http' hostname = settings.SITE_NAME path=reverse('jump_to', kwargs={'course_id': attempt['proctored_exam']['course_id'], 'location': attempt['proctored_exam']['content_id']}) return redirect(path)
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 ) log.info("{} {}".format(attempt_code, attempt['status'])) return Response( data={ # IMPORTANT: Don't add more information to this as it is an # unauthenticated endpoint 'status': attempt['status'], }, status=200 )
def put(self, request, attempt_code): """ HTTP POST handler. To stop an exam. """ try: attempt = get_exam_attempt_by_code(attempt_code) if not attempt: err_msg = ( 'Attempted to access attempt_code {attempt_code} but ' 'it does not exist.'.format(attempt_code=attempt_code)) raise StudentExamAttemptDoesNotExistsException(err_msg) action = request.DATA.get('action') user_id = request.DATA.get('user_id') exam_id = attempt['proctored_exam']['id'] if action and action == 'submit': exam_attempt_id = update_attempt_status( exam_id, user_id, ProctoredExamStudentAttemptStatus.submitted) if action and action == 'fail': exam_attempt_id = update_attempt_status( exam_id, user_id, ProctoredExamStudentAttemptStatus.error) if action and action == 'decline': exam_attempt_id = update_attempt_status( exam_id, user_id, ProctoredExamStudentAttemptStatus.timed_out) return Response({"exam_attempt_id": exam_attempt_id}) except ProctoredBaseException, ex: LOG.exception(ex) return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": str(ex)})
def delete(self, request, attempt_id): # pylint: disable=unused-argument """ HTTP DELETE handler. Removes an exam attempt. """ try: attempt_id = int(attempt_id) attempt = get_exam_attempt_by_id(attempt_id) except: attempt = get_exam_attempt_by_code(attempt_id) try: if not attempt: err_msg = ('Attempted to access attempt_id {attempt_id} but ' 'it does not exist.'.format(attempt_id=attempt_id)) raise StudentExamAttemptDoesNotExistsException(err_msg) remove_exam_attempt(attempt_id, request.user) return Response() except ProctoredBaseException, ex: LOG.exception(ex) return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": str(ex)})