def add_allowance_for_user(exam_id, user_info, key, value): """ Adds (or updates) an allowance for a user within a given exam """ log_msg = ( 'Adding allowance "{key}" with value "{value}" for exam_id {exam_id} ' 'for user {user_info} '.format( key=key, value=value, exam_id=exam_id, user_info=user_info ) ) log.info(log_msg) ProctoredExamStudentAllowance.add_allowance_for_user(exam_id, user_info, key, value)
def add_allowance_for_user(exam_id, user_info, key, value): """ Adds (or updates) an allowance for a user within a given exam """ log_msg = ( 'Adding allowance "{key}" with value "{value}" for exam_id {exam_id} ' 'for user {user_info} '.format(key=key, value=value, exam_id=exam_id, user_info=user_info)) log.info(log_msg) ProctoredExamStudentAllowance.add_allowance_for_user( exam_id, user_info, key, value)
def get_allowances_for_course(course_id): """ Get all the allowances for the course. """ student_allowances = ProctoredExamStudentAllowance.get_allowances_for_course( course_id) return [ ProctoredExamStudentAllowanceSerializer(allowance).data for allowance in student_allowances ]
def remove_allowance_for_user(exam_id, user_id, key): """ Deletes an allowance for a user within a given exam. """ log_msg = ( 'Removing allowance "{key}" for exam_id {exam_id} for user_id {user_id} ' .format(key=key, exam_id=exam_id, user_id=user_id)) log.info(log_msg) student_allowance = ProctoredExamStudentAllowance.get_allowance_for_user( exam_id, user_id, key) if student_allowance is not None: student_allowance.delete()
def remove_allowance_for_user(exam_id, user_id, key): """ Deletes an allowance for a user within a given exam. """ log_msg = ( 'Removing allowance "{key}" for exam_id {exam_id} for user_id {user_id} '.format( key=key, exam_id=exam_id, user_id=user_id ) ) log.info(log_msg) student_allowance = ProctoredExamStudentAllowance.get_allowance_for_user(exam_id, user_id, key) if student_allowance is not None: student_allowance.delete()
def get_active_exams_for_user(user_id, course_id=None): """ This method will return a list of active exams for the user, i.e. started_at != None and completed_at == None. Theoretically there could be more than one, but in practice it will be one active exam. If course_id is set, then attempts only for an exam in that course_id should be returned. The return set should be a list of dictionary objects which are nested [{ 'exam': <exam fields as dict>, 'attempt': <student attempt fields as dict>, 'allowances': <student allowances as dict of key/value pairs }, {}, ...] """ result = [] student_active_exams = ProctoredExamStudentAttempt.objects.get_active_student_attempts( user_id, course_id) for active_exam in student_active_exams: # convert the django orm objects # into the serialized form. exam_serialized_data = ProctoredExamSerializer( active_exam.proctored_exam).data active_exam_serialized_data = ProctoredExamStudentAttemptSerializer( active_exam).data student_allowances = ProctoredExamStudentAllowance.get_allowances_for_user( active_exam.proctored_exam.id, user_id) allowance_serialized_data = [ ProctoredExamStudentAllowanceSerializer(allowance).data for allowance in student_allowances ] result.append({ 'exam': exam_serialized_data, 'attempt': active_exam_serialized_data, 'allowances': allowance_serialized_data }) return result
def get_active_exams_for_user(user_id, course_id=None): """ This method will return a list of active exams for the user, i.e. started_at != None and completed_at == None. Theoretically there could be more than one, but in practice it will be one active exam. If course_id is set, then attempts only for an exam in that course_id should be returned. The return set should be a list of dictionary objects which are nested [{ 'exam': <exam fields as dict>, 'attempt': <student attempt fields as dict>, 'allowances': <student allowances as dict of key/value pairs }, {}, ...] """ result = [] student_active_exams = ProctoredExamStudentAttempt.objects.get_active_student_attempts(user_id, course_id) for active_exam in student_active_exams: # convert the django orm objects # into the serialized form. exam_serialized_data = ProctoredExamSerializer(active_exam.proctored_exam).data active_exam_serialized_data = ProctoredExamStudentAttemptSerializer(active_exam).data student_allowances = ProctoredExamStudentAllowance.get_allowances_for_user( active_exam.proctored_exam.id, user_id ) allowance_serialized_data = [ProctoredExamStudentAllowanceSerializer(allowance).data for allowance in student_allowances] result.append({ 'exam': exam_serialized_data, 'attempt': active_exam_serialized_data, 'allowances': allowance_serialized_data }) return result
def create_exam_attempt(exam_id, user_id, taking_as_proctored=False): """ Creates an exam attempt for user_id against exam_id. There should only be one exam_attempt per user per exam. Multiple attempts by user will be archived in a separate table """ # for now the student is allowed the exam default log_msg = ( 'Creating exam attempt for exam_id {exam_id} for ' 'user_id {user_id} with taking as proctored = {taking_as_proctored}'.format( exam_id=exam_id, user_id=user_id, taking_as_proctored=taking_as_proctored ) ) log.info(log_msg) exam = get_exam_by_id(exam_id) existing_attempt = ProctoredExamStudentAttempt.objects.get_exam_attempt(exam_id, user_id) if existing_attempt: if existing_attempt.is_sample_attempt: # Archive the existing attempt by deleting it. existing_attempt.delete_exam_attempt() else: err_msg = ( 'Cannot create new exam attempt for exam_id = {exam_id} and ' 'user_id = {user_id} because it already exists!' ).format(exam_id=exam_id, user_id=user_id) raise StudentExamAttemptAlreadyExistsException(err_msg) allowed_time_limit_mins = exam['time_limit_mins'] # add in the allowed additional time allowance_extra_mins = ProctoredExamStudentAllowance.get_additional_time_granted(exam_id, user_id) if allowance_extra_mins: allowed_time_limit_mins += allowance_extra_mins attempt_code = unicode(uuid.uuid4()).upper() external_id = None review_policy = ProctoredExamReviewPolicy.get_review_policy_for_exam(exam_id) review_policy_exception = ProctoredExamStudentAllowance.get_review_policy_exception(exam_id, user_id) if taking_as_proctored: scheme = 'https' if getattr(settings, 'HTTPS', 'on') == 'on' else 'http' callback_url = '{scheme}://{hostname}{path}'.format( scheme=scheme, hostname=settings.SITE_NAME, path=reverse( 'edx_proctoring.anonymous.proctoring_launch_callback.start_exam', args=[attempt_code] ) ) # get the name of the user, if the service is available full_name = None credit_service = get_runtime_service('credit') if credit_service: credit_state = credit_service.get_credit_state(user_id, exam['course_id']) full_name = credit_state['profile_fullname'] context = { 'time_limit_mins': allowed_time_limit_mins, 'attempt_code': attempt_code, 'is_sample_attempt': exam['is_practice_exam'], 'callback_url': callback_url, 'full_name': full_name, } # see if there is an exam review policy for this exam # if so, then pass it into the provider if review_policy: context.update({ 'review_policy': review_policy.review_policy }) # see if there is a review policy exception for this *user* # exceptions are granted on a individual basis as an # allowance if review_policy_exception: context.update({ 'review_policy_exception': review_policy_exception }) # now call into the backend provider to register exam attempt course_id = exam['course_id'] course_key = CourseKey.from_string(course_id) course = modulestore().get_course(course_key) provider_name = course.proctoring_service external_id = get_backend_provider(provider_name).register_exam_attempt( exam, context=context, ) attempt = ProctoredExamStudentAttempt.create_exam_attempt( exam_id, user_id, '', # student name is TBD allowed_time_limit_mins, attempt_code, taking_as_proctored, exam['is_practice_exam'], external_id, review_policy_id=review_policy.id if review_policy else None, ) log_msg = ( 'Created exam attempt ({attempt_id}) for exam_id {exam_id} for ' 'user_id {user_id} with taking as proctored = {taking_as_proctored} ' 'with allowed time limit minutes of {allowed_time_limit_mins}. ' 'Attempt_code {attempt_code} was generated which has a ' 'external_id of {external_id}'.format( attempt_id=attempt.id, exam_id=exam_id, user_id=user_id, taking_as_proctored=taking_as_proctored, allowed_time_limit_mins=allowed_time_limit_mins, attempt_code=attempt_code, external_id=external_id ) ) log.info(log_msg) return attempt.id
def get_allowances_for_course(course_id): """ Get all the allowances for the course. """ student_allowances = ProctoredExamStudentAllowance.get_allowances_for_course(course_id) return [ProctoredExamStudentAllowanceSerializer(allowance).data for allowance in student_allowances]
def create_exam_attempt(exam_id, user_id, taking_as_proctored=False): """ Creates an exam attempt for user_id against exam_id. There should only be one exam_attempt per user per exam. Multiple attempts by user will be archived in a separate table """ # for now the student is allowed the exam default exam = get_exam_by_id(exam_id) existing_attempt = ProctoredExamStudentAttempt.objects.get_exam_attempt( exam_id, user_id) if existing_attempt: log_msg = ( 'Creating exam attempt for exam_id {exam_id} for ' 'user_id {user_id} with taking as proctored = {taking_as_proctored}' .format(exam_id=exam_id, user_id=user_id, taking_as_proctored=taking_as_proctored)) log.info(log_msg) if existing_attempt.is_sample_attempt: # Archive the existing attempt by deleting it. existing_attempt.delete_exam_attempt() else: err_msg = ( 'Cannot create new exam attempt for exam_id = {exam_id} and ' 'user_id = {user_id} because it already exists!').format( exam_id=exam_id, user_id=user_id) raise StudentExamAttemptAlreadyExistsException(err_msg) allowed_time_limit_mins = exam['time_limit_mins'] # add in the allowed additional time allowance_extra_mins = ProctoredExamStudentAllowance.get_additional_time_granted( exam_id, user_id) if allowance_extra_mins: allowed_time_limit_mins += allowance_extra_mins attempt_code = unicode(uuid.uuid4()).upper() external_id = None review_policy = ProctoredExamReviewPolicy.get_review_policy_for_exam( exam_id) review_policy_exception = ProctoredExamStudentAllowance.get_review_policy_exception( exam_id, user_id) if taking_as_proctored: content_id = exam['content_id'].split('@')[-1] # get hash scheme = 'https' if getattr(settings, 'HTTPS', 'on') == 'on' else 'http' callback_url = '{scheme}://{hostname}{path}'.format( scheme=scheme, hostname=settings.SITE_NAME, path=reverse('jump_to_id', kwargs={ 'course_id': exam['course_id'], 'module_id': content_id })) # get the name of the user, if the service is available full_name = None credit_service = get_runtime_service('credit') user = User.objects.get(pk=user_id) context = { 'time_limit_mins': allowed_time_limit_mins, 'attempt_code': attempt_code, 'is_sample_attempt': exam['is_practice_exam'], 'callback_url': callback_url, 'user_id': user_id, 'full_name': " ".join((user.first_name, user.last_name)), 'username': user.username, 'email': user.email } # see if there is an exam review policy for this exam # if so, then pass it into the provider if review_policy: context.update({'review_policy': review_policy.review_policy}) # see if there is a review policy exception for this *user* # exceptions are granted on a individual basis as an # allowance if review_policy_exception: context.update( {'review_policy_exception': review_policy_exception}) # now call into the backend provider to register exam attempt provider_name = get_provider_name_by_course_id(exam['course_id']) external_id = get_backend_provider( provider_name).register_exam_attempt( exam, context=context, ) attempt = ProctoredExamStudentAttempt.create_exam_attempt( exam_id, user_id, '', # student name is TBD allowed_time_limit_mins, attempt_code, taking_as_proctored, exam['is_practice_exam'], external_id, review_policy_id=review_policy.id if review_policy else None, ) log_msg = ( '{attempt_code} - {username} ({email}) ' 'Created exam attempt ({attempt_id}) for exam_id {exam_id} for ' 'user_id {user_id} with taking as proctored = {taking_as_proctored} ' 'with allowed time limit minutes of {allowed_time_limit_mins}. ' 'external_id of {external_id}'.format( attempt_id=attempt.id, exam_id=exam_id, user_id=user_id, taking_as_proctored=taking_as_proctored, allowed_time_limit_mins=allowed_time_limit_mins, attempt_code=attempt_code, external_id=external_id, username=attempt.user.username, email=attempt.user.email)) log.info(log_msg) return attempt.id
def create_exam_attempt(exam_id, user_id, taking_as_proctored=False): """ Creates an exam attempt for user_id against exam_id. There should only be one exam_attempt per user per exam. Multiple attempts by user will be archived in a separate table """ # for now the student is allowed the exam default exam = get_exam_by_id(exam_id) existing_attempt = ProctoredExamStudentAttempt.objects.get_exam_attempt(exam_id, user_id) if existing_attempt: log_msg = ( 'Creating exam attempt for exam_id {exam_id} for ' 'user_id {user_id} with taking as proctored = {taking_as_proctored}'.format( exam_id=exam_id, user_id=user_id, taking_as_proctored=taking_as_proctored ) ) log.info(log_msg) if existing_attempt.is_sample_attempt: # Archive the existing attempt by deleting it. existing_attempt.delete_exam_attempt() else: err_msg = ( 'Cannot create new exam attempt for exam_id = {exam_id} and ' 'user_id = {user_id} because it already exists!' ).format(exam_id=exam_id, user_id=user_id) raise StudentExamAttemptAlreadyExistsException(err_msg) allowed_time_limit_mins = exam['time_limit_mins'] # add in the allowed additional time allowance_extra_mins = ProctoredExamStudentAllowance.get_additional_time_granted(exam_id, user_id) if allowance_extra_mins: allowed_time_limit_mins += allowance_extra_mins attempt_code = unicode(uuid.uuid4()).upper() external_id = None review_policy = ProctoredExamReviewPolicy.get_review_policy_for_exam(exam_id) review_policy_exception = ProctoredExamStudentAllowance.get_review_policy_exception(exam_id, user_id) if taking_as_proctored: content_id = exam['content_id'].split('@')[-1] # get hash scheme = 'https' if getattr(settings, 'HTTPS', 'on') == 'on' else 'http' callback_url = '{scheme}://{hostname}{path}'.format( scheme=scheme, hostname=settings.SITE_NAME, path=reverse( 'jump_to_id', kwargs={'course_id': exam['course_id'], 'module_id': content_id} ) ) # get the name of the user, if the service is available full_name = None credit_service = get_runtime_service('credit') user = User.objects.get(pk=user_id) context = { 'time_limit_mins': allowed_time_limit_mins, 'attempt_code': attempt_code, 'is_sample_attempt': exam['is_practice_exam'], 'callback_url': callback_url, 'user_id': user_id, 'full_name': " ".join((user.first_name,user.last_name)), 'username': user.username, 'email': user.email } # see if there is an exam review policy for this exam # if so, then pass it into the provider if review_policy: context.update({ 'review_policy': review_policy.review_policy }) # see if there is a review policy exception for this *user* # exceptions are granted on a individual basis as an # allowance if review_policy_exception: context.update({ 'review_policy_exception': review_policy_exception }) # now call into the backend provider to register exam attempt provider_name = get_provider_name_by_course_id(exam['course_id']) external_id = get_backend_provider(provider_name).register_exam_attempt( exam, context=context, ) attempt = ProctoredExamStudentAttempt.create_exam_attempt( exam_id, user_id, '', # student name is TBD allowed_time_limit_mins, attempt_code, taking_as_proctored, exam['is_practice_exam'], external_id, review_policy_id=review_policy.id if review_policy else None, ) log_msg = ( '{attempt_code} - {username} ({email}) ' 'Created exam attempt ({attempt_id}) for exam_id {exam_id} for ' 'user_id {user_id} with taking as proctored = {taking_as_proctored} ' 'with allowed time limit minutes of {allowed_time_limit_mins}. ' 'external_id of {external_id}'.format( attempt_id=attempt.id, exam_id=exam_id, user_id=user_id, taking_as_proctored=taking_as_proctored, allowed_time_limit_mins=allowed_time_limit_mins, attempt_code=attempt_code, external_id=external_id, username=attempt.user.username, email=attempt.user.email ) ) log.info(log_msg) return attempt.id