def handle(self, *args, **options):
        """
        Management command entry point, simply call into the signal firiing
        """
        # pylint: disable=import-outside-toplevel
        from edx_proctoring.api import (update_attempt_status, get_exam_by_id)

        exam_id = options['exam_id']
        user_id = options['user_id']
        to_status = options['to_status']

        msg = (u'Running management command to update user {user_id} '
               u'attempt status on exam_id {exam_id} to {to_status}'.format(
                   user_id=user_id, exam_id=exam_id, to_status=to_status))
        self.stdout.write(msg)

        if not ProctoredExamStudentAttemptStatus.is_valid_status(to_status):
            raise CommandError(
                u'{to_status} is not a valid attempt status!'.format(
                    to_status=to_status))

        # get exam, this will throw exception if does not exist, so let it bomb out
        get_exam_by_id(exam_id)

        update_attempt_status(exam_id, user_id, to_status)

        self.stdout.write('Completed!')
Example #2
0
    def handle(self, *args, **options):
        """
        Management command entry point, simply call into the signal firiing
        """

        from edx_proctoring.api import (
            update_attempt_status,
            get_exam_by_id
        )

        exam_id = options['exam_id']
        user_id = options['user_id']
        to_status = options['to_status']

        msg = (
            'Running management command to update user {user_id} '
            'attempt status on exam_id {exam_id} to {to_status}'.format(
                user_id=user_id,
                exam_id=exam_id,
                to_status=to_status
            )
        )
        self.stdout.write(msg)

        if not ProctoredExamStudentAttemptStatus.is_valid_status(to_status):
            raise CommandError('{to_status} is not a valid attempt status!'.format(to_status=to_status))

        # get exam, this will throw exception if does not exist, so let it bomb out
        get_exam_by_id(exam_id)

        update_attempt_status(exam_id, user_id, to_status)

        self.stdout.write('Completed!')
Example #3
0
    def handle(self, *args, **options):
        """
        Management command entry point, simply call into the signal firiing
        """

        from edx_proctoring.api import (update_attempt_status, get_exam_by_id)

        exam_id = options['exam_id']
        user_id = options['user_id']
        to_status = options['to_status']

        msg = ('Running management command to update user {user_id} '
               'attempt status on exam_id {exam_id} to {to_status}'.format(
                   user_id=user_id, exam_id=exam_id, to_status=to_status))
        print msg

        if not ProctoredExamStudentAttemptStatus.is_valid_status(to_status):
            raise Exception(
                '{to_status} is not a valid attempt status!'.format(
                    to_status=to_status))

        # get exam, this will throw exception if does not exist, so let it bomb out
        get_exam_by_id(exam_id)

        update_attempt_status(exam_id, user_id, to_status)

        print 'Completed!'
Example #4
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(
                '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)
Example #5
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'
        try:
            exam_attempt_id = create_exam_attempt(
                exam_id=exam_id,
                user_id=request.user.id,
                taking_as_proctored=attempt_proctored
            )

            exam = get_exam_by_id(exam_id)

            # 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)}
            )
Example #6
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'
        try:
            exam_attempt_id = create_exam_attempt(
                exam_id=exam_id,
                user_id=request.user.id,
                taking_as_proctored=attempt_proctored)

            exam = get_exam_by_id(exam_id)

            # 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 _render_exam(self, content_id, context_overrides=None):
     """
     Renders a test exam.
     """
     exam = get_exam_by_id(content_id)
     context = {
         'is_proctored': True,
         'allow_proctoring_opt_out': True,
         'display_name': self.exam_name,
         'default_time_limit_mins': 90,
         'is_practice_exam': False,
         'credit_state': {
             'enrollment_mode': 'verified',
             'credit_requirement_status': [],
         },
         'verification_status': 'approved',
         'verification_url': '/reverify',
     }
     if context_overrides:
         context.update(context_overrides)
     return get_student_view(
         user_id=self.user_id,
         course_id=exam['course_id'],
         content_id=exam['content_id'],
         context=context,
     )
Example #8
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)
Example #9
0
 def _render_exam(self, content_id, context_overrides=None):
     """
     Renders a test exam.
     """
     exam = get_exam_by_id(content_id)
     context = {
         'is_proctored': True,
         'allow_proctoring_opt_out': True,
         'display_name': self.exam_name,
         'default_time_limit_mins': 90,
         'is_practice_exam': False,
         'credit_state': {
             'enrollment_mode': 'verified',
             'credit_requirement_status': [],
         },
         'verification_status': 'approved',
         'verification_url': '/reverify',
     }
     if context_overrides:
         context.update(context_overrides)
     return get_student_view(
         user_id=self.user_id,
         course_id=exam['course_id'],
         content_id=exam['content_id'],
         context=context,
     )
Example #10
0
    def get(self, request, course_id, exam_id=None):
        """
        Redirect to dashboard for a given course and optional exam_id
        """
        exam = None
        attempt_id = None
        ext_exam_id = None
        show_configuration_dashboard = False

        if exam_id:
            exam = get_exam_by_id(exam_id)
            # the exam_id in the url is our database id (for ease of lookups)
            # but the backend needs its external id for the instructor dashboard
            ext_exam_id = exam['external_id']
            attempt_id = request.GET.get('attempt', None)

            # only show the configuration dashboard if an exam_id is passed in
            show_configuration_dashboard = request.GET.get('config', '').lower() == 'true'

        else:
            found_backend = None
            for exam in get_all_exams_for_course(course_id, True):
                exam_backend = exam['backend'] or settings.PROCTORING_BACKENDS.get('DEFAULT', None)
                if found_backend and exam_backend != found_backend:
                    # In this case, what are we supposed to do?!
                    # It should not be possible to get in this state, because
                    # course teams will be prevented from updating the backend after the course start date
                    error_message = "Multiple backends for course %r %r != %r" % (course_id,
                                                                                  found_backend,
                                                                                  exam['backend'])
                    return Response(data=error_message, status=400)
                else:
                    found_backend = exam_backend
        if exam is None:
            error = _('No exams in course {course_id}.').format(course_id=course_id)
        else:
            backend = get_backend_provider(exam)
            if backend:
                user = {
                    'id': obscured_user_id(request.user.id, exam['backend']),
                    'full_name': request.user.get_full_name(),
                    'email': request.user.email
                }

                url = backend.get_instructor_url(
                    exam['course_id'],
                    user,
                    exam_id=ext_exam_id,
                    attempt_id=attempt_id,
                    show_configuration_dashboard=show_configuration_dashboard
                )
                if url:
                    return redirect(url)
                else:
                    error = _('No instructor dashboard for {proctor_service}').format(
                        proctor_service=backend.verbose_name)
            else:
                error = _('No proctored exams in course {course_id}').format(course_id=course_id)
        return Response(data=error, status=404, headers={'X-Frame-Options': 'sameorigin'})
Example #11
0
    def get(self, request, course_id, exam_id=None):
        """
        Redirect to dashboard for a given course and optional exam_id
        """
        exam = None
        attempt_id = None
        ext_exam_id = None
        show_configuration_dashboard = False

        if exam_id:
            exam = get_exam_by_id(exam_id)
            # the exam_id in the url is our database id (for ease of lookups)
            # but the backend needs its external id for the instructor dashboard
            ext_exam_id = exam['external_id']
            attempt_id = request.GET.get('attempt', None)

            # only show the configuration dashboard if an exam_id is passed in
            show_configuration_dashboard = request.GET.get('config', '').lower() == 'true'

        else:
            found_backend = None
            for exam in get_all_exams_for_course(course_id, True):
                exam_backend = exam['backend'] or settings.PROCTORING_BACKENDS.get('DEFAULT', None)
                if found_backend and exam_backend != found_backend:
                    # In this case, what are we supposed to do?!
                    # It should not be possible to get in this state, because
                    # course teams will be prevented from updating the backend after the course start date
                    error_message = "Multiple backends for course %r %r != %r" % (course_id,
                                                                                  found_backend,
                                                                                  exam['backend'])
                    return Response(data=error_message, status=400)
                else:
                    found_backend = exam_backend
        if exam is None:
            error = _('No exams in course {course_id}.').format(course_id=course_id)
        else:
            backend = get_backend_provider(exam)
            if backend:
                user = {
                    'id': obscured_user_id(request.user.id, exam['backend']),
                    'full_name': request.user.get_full_name(),
                    'email': request.user.email
                }

                url = backend.get_instructor_url(
                    exam['course_id'],
                    user,
                    exam_id=ext_exam_id,
                    attempt_id=attempt_id,
                    show_configuration_dashboard=show_configuration_dashboard
                )
                if url:
                    return redirect(url)
                else:
                    error = _('No instructor dashboard for {proctor_service}').format(
                        proctor_service=backend.verbose_name)
            else:
                error = _('No proctored exams in course {course_id}').format(course_id=course_id)
        return Response(data=error, status=404, headers={'X-Frame-Options': 'sameorigin'})
Example #12
0
    def get(self, request, exam_id=None, course_id=None, content_id=None):  # pylint: disable=unused-argument
        """
        HTTP GET handler.
            Scenarios:
                by exam_id: calls get_exam_by_id()
                by course_id, content_id: get_exam_by_content_id()

        """

        if exam_id:
            try:
                return Response(data=get_exam_by_id(exam_id), status=status.HTTP_200_OK)
            except ProctoredExamNotFoundException, ex:
                LOG.exception(ex)
                return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": "The exam_id does not exist."})
Example #13
0
    def get(self, request, exam_id=None, course_id=None, content_id=None):  # pylint: disable=unused-argument
        """
        HTTP GET handler.
            Scenarios:
                by exam_id: calls get_exam_by_id()
                by course_id, content_id: get_exam_by_content_id()

        """

        if exam_id:
            try:
                return Response(data=get_exam_by_id(exam_id),
                                status=status.HTTP_200_OK)
            except ProctoredExamNotFoundException, ex:
                LOG.exception(ex)
                return Response(status=status.HTTP_400_BAD_REQUEST,
                                data={"detail": "The exam_id does not exist."})
Example #14
0
    def get(self, request, exam_id=None, course_id=None, content_id=None):  # pylint: disable=unused-argument
        """
        HTTP GET handler.
            Scenarios:
                by exam_id: calls get_exam_by_id()
                by course_id, content_id: get_exam_by_content_id()

        """
        if exam_id:
            data = get_exam_by_id(exam_id)
        elif course_id is not None:
            if content_id is not None:
                # get by course_id & content_id
                data = get_exam_by_content_id(course_id, content_id)
            else:
                data = get_all_exams_for_course(course_id=course_id,
                                                active_only=True)
        return Response(data)
Example #15
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)})
Example #16
0
    def get(self, request, exam_id=None, course_id=None, content_id=None):  # pylint: disable=unused-argument
        """
        HTTP GET handler.
            Scenarios:
                by exam_id: calls get_exam_by_id()
                by course_id, content_id: get_exam_by_content_id()

        """
        if exam_id:
            data = get_exam_by_id(exam_id)
        elif course_id is not None:
            if content_id is not None:
                # get by course_id & content_id
                data = get_exam_by_content_id(course_id, content_id)
            else:
                data = get_all_exams_for_course(
                    course_id=course_id,
                    active_only=True
                )
        return Response(data)
Example #17
0
    def get(self, request, course_id, exam_id=None):
        """
        Redirect to dashboard for a given course and optional exam_id
        """
        exam = None
        backend = None
        ext_exam_id = None
        attempt_id = None
        show_configuration_dashboard = False

        if exam_id:
            exam = get_exam_by_id(exam_id)
            backend = get_backend_provider(exam=exam)
            # the exam_id in the url is our database id (for ease of lookups)
            # but the backend needs its external id for the instructor dashboard
            ext_exam_id = exam['external_id']
            attempt_id = request.GET.get('attempt', None)

            # only show the configuration dashboard if an exam_id is passed in
            show_configuration_dashboard = request.GET.get('config', '').lower() == 'true'
        else:
            existing_backend_name = None
            for exam in get_all_exams_for_course(course_id, True):
                if not exam.get('is_proctored'):
                    # We should only get backends of exams which are configured to be proctored
                    continue

                exam_backend_name = exam.get('backend')
                backend = get_backend_provider(name=exam_backend_name)
                if existing_backend_name and exam_backend_name != existing_backend_name:
                    # In this case, what are we supposed to do?!
                    # It should not be possible to get in this state, because
                    # course teams will be prevented from updating the backend after the course start date
                    error_message = u"Multiple backends for course %r %r != %r" % (
                        course_id,
                        existing_backend_name,
                        exam_backend_name
                    )
                    return Response(data=error_message, status=400)
                else:
                    existing_backend_name = exam_backend_name

        if not exam:
            return Response(
                data=_(u'No exams in course {course_id}.').format(course_id=course_id),
                status=404,
                headers={'X-Frame-Options': 'sameorigin'}
            )
        if not backend:
            return Response(
                data=_(u'No proctored exams in course {course_id}').format(course_id=course_id),
                status=404,
                headers={'X-Frame-Options': 'sameorigin'}
            )
        user = {
            'id': obscured_user_id(request.user.id, exam['backend']),
            'full_name': request.user.profile.name,
            'email': request.user.email
        }

        url = backend.get_instructor_url(
            exam['course_id'],
            user,
            exam_id=ext_exam_id,
            attempt_id=attempt_id,
            show_configuration_dashboard=show_configuration_dashboard
        )
        if not url:
            return Response(
                data=_(u'No instructor dashboard for {proctor_service}').format(
                    proctor_service=backend.verbose_name
                ),
                status=404,
                headers={'X-Frame-Options': 'sameorigin'}
            )
        return redirect(url)