def get(self, request, attempt_id): """ HTTP GET Handler. Returns the status of the exam attempt. """ attempt = get_exam_attempt_by_id(attempt_id) if not attempt: err_msg = (u'Attempted to access attempt_id {attempt_id} but ' u'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 = (u'Attempted to access attempt_id {attempt_id} but ' u'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 = _( u'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 = _( u'you have less than a minute remaining') attempt['accessibility_time_string'] = accessibility_time_string return Response(attempt)
def post(self, request): """ HTTP POST handler. To create an exam attempt. """ start_immediately = request.data.get('start_clock', 'false').lower() == 'true' exam_id = request.data.get('exam_id', None) attempt_proctored = request.data.get('attempt_proctored', 'false').lower() == 'true' exam = get_exam_by_id(exam_id) # Bypassing the due date check for practice exam # because student can attempt the practice after the due date if not exam.get("is_practice_exam") and is_exam_passed_due(exam, request.user): raise ProctoredExamPermissionDenied( u'Attempted to access expired exam with exam_id {exam_id}'.format(exam_id=exam_id) ) exam_attempt_id = create_exam_attempt( exam_id=exam_id, user_id=request.user.id, taking_as_proctored=attempt_proctored ) # if use elected not to take as proctored exam, then # use must take as open book, and loose credit eligibility if exam['is_proctored'] and not attempt_proctored: update_attempt_status( exam_id, request.user.id, ProctoredExamStudentAttemptStatus.declined ) elif start_immediately: start_exam_attempt(exam_id, request.user.id) data = {'exam_attempt_id': exam_attempt_id} return Response(data)
def put(self, request, attempt_id): # pylint: disable=unused-argument """ Update the is_status_acknowledge flag for the specific attempt """ try: attempt = get_exam_attempt_by_id(attempt_id) # make sure the the attempt belongs to the calling user_id if attempt and 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) update_exam_attempt(attempt_id, is_status_acknowledged=True) return Response( status=status.HTTP_200_OK ) except (StudentExamAttemptDoesNotExistsException, ProctoredExamPermissionDenied) as ex: LOG.exception(ex) return Response( status=status.HTTP_400_BAD_REQUEST, data={"detail": str(ex)} )
def get(self, request, attempt_id): """ HTTP GET Handler. Returns the status of the exam attempt. """ try: attempt = get_exam_attempt_by_id(attempt_id) 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) # check if the last_poll_timestamp is not None # and if it is older than CLIENT_TIMEOUT # then attempt status should be marked as error. last_poll_timestamp = attempt['last_poll_timestamp'] if last_poll_timestamp is not None \ and (datetime.now(pytz.UTC) - last_poll_timestamp).total_seconds() > 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 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 put(self, request, attempt_id): """ HTTP POST handler. To stop an exam. """ attempt = get_exam_attempt_by_id(attempt_id) if not attempt: err_msg = (u'Attempted to access attempt_id {attempt_id} but ' u'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 = (u'Attempted to access attempt_id {attempt_id} but ' u'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 == 'error': backend = attempt['proctored_exam']['backend'] waffle_name = PING_FAILURE_PASSTHROUGH_TEMPLATE.format(backend) should_block_user = not ( backend and waffle.switch_is_active(waffle_name)) and ( not attempt['status'] == ProctoredExamStudentAttemptStatus.submitted) if should_block_user: exam_attempt_id = update_attempt_status( attempt['proctored_exam']['id'], request.user.id, ProctoredExamStudentAttemptStatus.error) else: exam_attempt_id = False LOG.warning( u'Browser JS reported problem with proctoring desktop ' u'application. Did block user: %s, for attempt: %s', should_block_user, attempt['id']) elif action == 'decline': exam_attempt_id = update_attempt_status( attempt['proctored_exam']['id'], request.user.id, ProctoredExamStudentAttemptStatus.declined) data = {"exam_attempt_id": exam_attempt_id} return Response(data)
def get(self, request, attempt_id): """ HTTP GET Handler. Returns the status of the exam attempt. """ try: attempt = get_exam_attempt_by_id(attempt_id) 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 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 update_exam_attempt(attempt_id, **kwargs): """ update exam_attempt """ exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt_by_id( attempt_id) for key, value in kwargs.items(): # only allow a limit set of fields to update # namely because status transitions can trigger workflow if key not in ['last_poll_timestamp', 'last_poll_ipaddr']: err_msg = ('You cannot call into update_exam_attempt to change ' 'field {key}'.format(key=key)) raise ProctoredExamPermissionDenied(err_msg) setattr(exam_attempt_obj, key, value) exam_attempt_obj.save()
def put(self, request, attempt_id): # pylint: disable=unused-argument """ Update the is_status_acknowledge flag for the specific attempt """ attempt = get_exam_attempt_by_id(attempt_id) # make sure the the attempt belongs to the calling user_id if attempt and attempt['user']['id'] != request.user.id: err_msg = (u'Attempted to access attempt_id {attempt_id} but ' u'does not have access to it.'.format( attempt_id=attempt_id)) raise ProctoredExamPermissionDenied(err_msg) update_exam_attempt(attempt_id, is_status_acknowledged=True) return Response()
def post(self, request): """ HTTP POST handler. To create an exam attempt. """ start_immediately = request.data.get('start_clock', 'false').lower() == 'true' exam_id = request.data.get('exam_id', None) attempt_proctored = request.data.get('attempt_proctored', 'false').lower() == 'true' provider_name = request.data.get('provider_name', 'dummy') try: exam = get_exam_by_id(exam_id) # Bypassing the due date check for practice exam # because student can attempt the practice after the due date if not exam.get("is_practice_exam") and has_due_date_passed( exam.get('due_date')): raise ProctoredExamPermissionDenied( 'Attempted to access expired exam with exam_id {exam_id}'. format(exam_id=exam_id)) exam_attempt_id = create_exam_attempt( exam_id=exam_id, user_id=request.user.id, taking_as_proctored=attempt_proctored, provider_name=provider_name) # if use elected not to take as proctored exam, then # use must take as open book, and loose credit eligibility if exam['is_proctored'] and not attempt_proctored: update_attempt_status( exam_id, request.user.id, ProctoredExamStudentAttemptStatus.declined) elif start_immediately: start_exam_attempt(exam_id, request.user.id) return Response({'exam_attempt_id': exam_attempt_id}) except ProctoredBaseException, ex: LOG.exception(ex) return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": unicode(ex)})
def get(self, request, attempt_id): """ HTTP GET Handler. Returns the status of the exam attempt. """ try: attempt = get_exam_attempt_by_id(attempt_id) 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) # check if the last_poll_timestamp is not None # and if it is older than SOFTWARE_SECURE_CLIENT_TIMEOUT # then attempt status should be marked as error. last_poll_timestamp = attempt['last_poll_timestamp'] # if we never heard from the client, then we assume it is shut down attempt['client_has_shutdown'] = last_poll_timestamp is None if last_poll_timestamp is not None: # Let's pass along information if we think the SoftwareSecure has completed # a healthy shutdown which is when our attempt is in a 'submitted' status if attempt[ 'status'] == ProctoredExamStudentAttemptStatus.submitted: attempt['client_has_shutdown'] = has_client_app_shutdown( attempt) else: # otherwise, let's see if the shutdown happened in error # 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 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)})