Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
0
    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)}
            )
Esempio n. 4
0
    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)})
Esempio n. 5
0
    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)
Esempio n. 6
0
    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)}
            )
Esempio n. 7
0
    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)})
Esempio n. 8
0
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()
Esempio n. 9
0
    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()
Esempio n. 10
0
    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)})
Esempio n. 11
0
    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)})