def test_get_studentview_submitted_timed_exam_with_past_due_date(self, due_date, hide_after_due): """ Test for get_student_view timed exam with the due date. """ # exam is created with due datetime which has already passed exam_id = self._create_exam_with_due_time(is_proctored=False, due_date=due_date) if hide_after_due: update_exam(exam_id, hide_after_due=hide_after_due) # now create the timed_exam attempt in the submitted state self._create_exam_attempt(exam_id, status='submitted') rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_for_exam_with_due_date, context={ 'is_proctored': False, 'display_name': self.exam_name, 'default_time_limit_mins': 10, 'due_date': due_date, } ) if datetime.now(pytz.UTC) < due_date: self.assertIn(self.timed_exam_submitted, rendered_response) self.assertIn(self.submitted_timed_exam_msg_with_due_date, rendered_response) elif hide_after_due: self.assertIn(self.timed_exam_submitted, rendered_response) self.assertNotIn(self.submitted_timed_exam_msg_with_due_date, rendered_response) else: self.assertIsNone(rendered_response)
def test_get_studentview_submitted_timed_exam_with_past_due_date( self, due_date, hide_after_due): """ Test for get_student_view timed exam with the due date. """ # exam is created with due datetime which has already passed exam_id = self._create_exam_with_due_time(is_proctored=False, due_date=due_date) if hide_after_due: update_exam(exam_id, hide_after_due=hide_after_due) # now create the timed_exam attempt in the submitted state self._create_exam_attempt(exam_id, status='submitted') rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_for_exam_with_due_date, context={ 'is_proctored': False, 'display_name': self.exam_name, 'default_time_limit_mins': 10, 'due_date': due_date, }) if datetime.now(pytz.UTC) < due_date: self.assertIn(self.timed_exam_submitted, rendered_response) self.assertIn(self.submitted_timed_exam_msg_with_due_date, rendered_response) elif hide_after_due: self.assertIn(self.timed_exam_submitted, rendered_response) self.assertNotIn(self.submitted_timed_exam_msg_with_due_date, rendered_response) else: self.assertIsNone(rendered_response)
def test_get_studentview_long_limit(self, under_exception): """ Test for hide_extra_time_footer on exams with > 20 hours time limit """ exam_id = self._create_exam_with_due_time(is_proctored=False, ) if under_exception: update_exam(exam_id, time_limit_mins=((20 * 60))) # exactly 20 hours else: update_exam(exam_id, time_limit_mins=((20 * 60) + 1)) # 1 minute greater than 20 hours rendered_response = get_student_view( user_id=self.user_id, course_id=self.course_id, content_id=self.content_id_for_exam_with_due_date, context={ 'is_proctored': False, 'display_name': self.exam_name, } ) if under_exception: self.assertIn(self.timed_footer_msg, rendered_response) else: self.assertNotIn(self.timed_footer_msg, rendered_response)
def put(self, request): """ HTTP PUT handler. To update an exam. calls the update_exam """ exam_id = update_exam( exam_id=request.data.get('exam_id', None), exam_name=request.data.get('exam_name', None), time_limit_mins=request.data.get('time_limit_mins', None), is_proctored=request.data.get('is_proctored', None), is_practice_exam=request.data.get('is_practice_exam', None), external_id=request.data.get('external_id', None), is_active=request.data.get('is_active', None), hide_after_due=request.data.get('hide_after_due', None), ) return Response({'exam_id': exam_id})
def put(self, request): """ HTTP PUT handler. To update an exam. calls the update_exam """ try: exam_id = update_exam( exam_id=request.data.get("exam_id", None), exam_name=request.data.get("exam_name", None), time_limit_mins=request.data.get("time_limit_mins", None), is_proctored=request.data.get("is_proctored", None), is_practice_exam=request.data.get("is_practice_exam", None), external_id=request.data.get("external_id", None), is_active=request.data.get("is_active", None), ) return Response({"exam_id": exam_id}) except ProctoredExamNotFoundException, ex: LOG.exception(ex) return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": "The exam_id does not exist."})
def put(self, request): """ HTTP PUT handler. To update an exam. calls the update_exam """ try: exam_id = update_exam( exam_id=request.data.get('exam_id', None), exam_name=request.data.get('exam_name', None), time_limit_mins=request.data.get('time_limit_mins', None), is_proctored=request.data.get('is_proctored', None), is_practice_exam=request.data.get('is_practice_exam', None), external_id=request.data.get('external_id', None), is_active=request.data.get('is_active', None), ) return Response({'exam_id': exam_id}) except ProctoredExamNotFoundException, ex: LOG.exception(ex) return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": "The exam_id does not exist."})
def register_special_exams(course_key): """ This is typically called on a course published signal. The course is examined for sequences that are marked as timed exams. Then these are registered with the edx-proctoring subsystem. Likewise, if formerly registered exams are unmarked, then those registered exams are marked as inactive """ if not settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): # if feature is not enabled then do a quick exit return course = modulestore().get_course(course_key) if not course.enable_proctored_exams and not course.enable_timed_exams: # likewise if course does not have these features turned on # then quickly exit return # get all sequences, since they can be marked as timed/proctored exams _timed_exams = modulestore().get_items(course_key, qualifiers={ 'category': 'sequential', }, settings={ 'is_time_limited': True, }) # filter out any potential dangling sequences timed_exams = [ timed_exam for timed_exam in _timed_exams if is_item_in_course_tree(timed_exam) ] # enumerate over list of sequences which are time-limited and # add/update any exam entries in edx-proctoring for timed_exam in timed_exams: msg = ( 'Found {location} as a timed-exam in course structure. Inspecting...' .format(location=unicode(timed_exam.location))) log.info(msg) try: exam = get_exam_by_content_id(unicode(course_key), unicode(timed_exam.location)) # update case, make sure everything is synced exam_id = update_exam( exam_id=exam['id'], exam_name=timed_exam.display_name, time_limit_mins=timed_exam.default_time_limit_minutes, due_date=timed_exam.due, is_proctored=timed_exam.is_proctored_exam, is_practice_exam=timed_exam.is_practice_exam, is_active=True, hide_after_due=timed_exam.hide_after_due, ) msg = 'Updated timed exam {exam_id}'.format(exam_id=exam['id']) log.info(msg) except ProctoredExamNotFoundException: exam_id = create_exam( course_id=unicode(course_key), content_id=unicode(timed_exam.location), exam_name=timed_exam.display_name, time_limit_mins=timed_exam.default_time_limit_minutes, due_date=timed_exam.due, is_proctored=timed_exam.is_proctored_exam, is_practice_exam=timed_exam.is_practice_exam, is_active=True, hide_after_due=timed_exam.hide_after_due, ) msg = 'Created new timed exam {exam_id}'.format(exam_id=exam_id) log.info(msg) # only create/update exam policy for the proctored exams if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam: try: update_review_policy( exam_id=exam_id, set_by_user_id=timed_exam.edited_by, review_policy=timed_exam.exam_review_rules) except ProctoredExamReviewPolicyNotFoundException: if timed_exam.exam_review_rules: # won't save an empty rule. create_exam_review_policy( exam_id=exam_id, set_by_user_id=timed_exam.edited_by, review_policy=timed_exam.exam_review_rules) msg = 'Created new exam review policy with exam_id {exam_id}'.format( exam_id=exam_id) log.info(msg) else: try: # remove any associated review policy remove_review_policy(exam_id=exam_id) except ProctoredExamReviewPolicyNotFoundException: pass # then see which exams we have in edx-proctoring that are not in # our current list. That means the the user has disabled it exams = get_all_exams_for_course(course_key) for exam in exams: if exam['is_active']: # try to look up the content_id in the sequences location search = [ timed_exam for timed_exam in timed_exams if unicode(timed_exam.location) == exam['content_id'] ] if not search: # This means it was turned off in Studio, we need to mark # the exam as inactive (we don't delete!) msg = 'Disabling timed exam {exam_id}'.format( exam_id=exam['id']) log.info(msg) update_exam( exam_id=exam['id'], is_proctored=False, is_active=False, )
def register_special_exams(course_key): """ This is typically called on a course published signal. The course is examined for sequences that are marked as timed exams. Then these are registered with the edx-proctoring subsystem. Likewise, if formerly registered exams are unmarked, then those registered exams are marked as inactive """ if not settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): # if feature is not enabled then do a quick exit return course = modulestore().get_course(course_key) if course is None: raise ItemNotFoundError(u"Course {} does not exist", unicode(course_key)) if not course.enable_proctored_exams and not course.enable_timed_exams: # likewise if course does not have these features turned on # then quickly exit return # get all sequences, since they can be marked as timed/proctored exams _timed_exams = modulestore().get_items( course_key, qualifiers={ 'category': 'sequential', }, settings={ 'is_time_limited': True, } ) # filter out any potential dangling sequences timed_exams = [ timed_exam for timed_exam in _timed_exams if is_item_in_course_tree(timed_exam) ] # enumerate over list of sequences which are time-limited and # add/update any exam entries in edx-proctoring for timed_exam in timed_exams: msg = ( u'Found {location} as a timed-exam in course structure. Inspecting...'.format( location=unicode(timed_exam.location) ) ) log.info(msg) exam_metadata = { 'exam_name': timed_exam.display_name, 'time_limit_mins': timed_exam.default_time_limit_minutes, 'due_date': timed_exam.due, 'is_proctored': timed_exam.is_proctored_exam, # backends that support onboarding exams will treat onboarding exams as practice 'is_practice_exam': timed_exam.is_practice_exam or timed_exam.is_onboarding_exam, 'is_active': True, 'hide_after_due': timed_exam.hide_after_due, 'backend': course.proctoring_provider, } try: exam = get_exam_by_content_id(unicode(course_key), unicode(timed_exam.location)) # update case, make sure everything is synced exam_metadata['exam_id'] = exam['id'] exam_id = update_exam(**exam_metadata) msg = u'Updated timed exam {exam_id}'.format(exam_id=exam['id']) log.info(msg) except ProctoredExamNotFoundException: exam_metadata['course_id'] = unicode(course_key) exam_metadata['content_id'] = unicode(timed_exam.location) exam_id = create_exam(**exam_metadata) msg = u'Created new timed exam {exam_id}'.format(exam_id=exam_id) log.info(msg) exam_review_policy_metadata = { 'exam_id': exam_id, 'set_by_user_id': timed_exam.edited_by, 'review_policy': timed_exam.exam_review_rules, } # only create/update exam policy for the proctored exams if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam and not timed_exam.is_onboarding_exam: try: update_review_policy(**exam_review_policy_metadata) except ProctoredExamReviewPolicyNotFoundException: if timed_exam.exam_review_rules: # won't save an empty rule. create_exam_review_policy(**exam_review_policy_metadata) msg = u'Created new exam review policy with exam_id {exam_id}'.format(exam_id=exam_id) log.info(msg) else: try: # remove any associated review policy remove_review_policy(exam_id=exam_id) except ProctoredExamReviewPolicyNotFoundException: pass # then see which exams we have in edx-proctoring that are not in # our current list. That means the the user has disabled it exams = get_all_exams_for_course(course_key) for exam in exams: if exam['is_active']: # try to look up the content_id in the sequences location search = [ timed_exam for timed_exam in timed_exams if unicode(timed_exam.location) == exam['content_id'] ] if not search: # This means it was turned off in Studio, we need to mark # the exam as inactive (we don't delete!) msg = u'Disabling timed exam {exam_id}'.format(exam_id=exam['id']) log.info(msg) update_exam( exam_id=exam['id'], is_proctored=False, is_active=False, )
def register_special_exams(course_key): """ This is typically called on a course published signal. The course is examined for sequences that are marked as timed exams. Then these are registered with the edx-proctoring subsystem. Likewise, if formerly registered exams are unmarked, then those registered exams are marked as inactive """ if not settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): # if feature is not enabled then do a quick exit return course = modulestore().get_course(course_key) if course is None: raise ItemNotFoundError(u"Course {} does not exist", six.text_type(course_key)) # lint-amnesty, pylint: disable=raising-format-tuple if not course.enable_proctored_exams and not course.enable_timed_exams: # likewise if course does not have these features turned on # then quickly exit return # get all sequences, since they can be marked as timed/proctored exams _timed_exams = modulestore().get_items(course_key, qualifiers={ 'category': 'sequential', }, settings={ 'is_time_limited': True, }) # filter out any potential dangling sequences timed_exams = [ timed_exam for timed_exam in _timed_exams if is_item_in_course_tree(timed_exam) ] # enumerate over list of sequences which are time-limited and # add/update any exam entries in edx-proctoring for timed_exam in timed_exams: msg = ( u'Found {location} as a timed-exam in course structure. Inspecting...' .format(location=six.text_type(timed_exam.location))) log.info(msg) exam_metadata = { 'exam_name': timed_exam.display_name, 'time_limit_mins': timed_exam.default_time_limit_minutes, 'due_date': timed_exam.due if not course.self_paced else None, 'is_proctored': timed_exam.is_proctored_exam, # backends that support onboarding exams will treat onboarding exams as practice 'is_practice_exam': timed_exam.is_practice_exam or timed_exam.is_onboarding_exam, 'is_active': True, 'hide_after_due': timed_exam.hide_after_due, 'backend': course.proctoring_provider, } try: exam = get_exam_by_content_id(six.text_type(course_key), six.text_type(timed_exam.location)) # update case, make sure everything is synced exam_metadata['exam_id'] = exam['id'] exam_id = update_exam(**exam_metadata) msg = u'Updated timed exam {exam_id}'.format(exam_id=exam['id']) log.info(msg) except ProctoredExamNotFoundException: exam_metadata['course_id'] = six.text_type(course_key) exam_metadata['content_id'] = six.text_type(timed_exam.location) exam_id = create_exam(**exam_metadata) msg = u'Created new timed exam {exam_id}'.format(exam_id=exam_id) log.info(msg) exam_review_policy_metadata = { 'exam_id': exam_id, 'set_by_user_id': timed_exam.edited_by, 'review_policy': timed_exam.exam_review_rules, } # only create/update exam policy for the proctored exams if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam and not timed_exam.is_onboarding_exam: try: update_review_policy(**exam_review_policy_metadata) except ProctoredExamReviewPolicyNotFoundException: if timed_exam.exam_review_rules: # won't save an empty rule. create_exam_review_policy(**exam_review_policy_metadata) msg = u'Created new exam review policy with exam_id {exam_id}'.format( exam_id=exam_id) log.info(msg) else: try: # remove any associated review policy remove_review_policy(exam_id=exam_id) except ProctoredExamReviewPolicyNotFoundException: pass # then see which exams we have in edx-proctoring that are not in # our current list. That means the the user has disabled it exams = get_all_exams_for_course(course_key) for exam in exams: if exam['is_active']: # try to look up the content_id in the sequences location search = [ timed_exam for timed_exam in timed_exams if six.text_type(timed_exam.location) == exam['content_id'] ] if not search: # This means it was turned off in Studio, we need to mark # the exam as inactive (we don't delete!) msg = u'Disabling timed exam {exam_id}'.format( exam_id=exam['id']) log.info(msg) update_exam( exam_id=exam['id'], is_proctored=False, is_active=False, )
def register_special_exams(course_key): """ This is typically called on a course published signal. The course is examined for sequences that are marked as timed exams. Then these are registered with the edx-proctoring subsystem. Likewise, if formerly registered exams are unmarked, then those registered exams are marked as inactive """ if not settings.FEATURES.get("ENABLE_SPECIAL_EXAMS"): # if feature is not enabled then do a quick exit return course = modulestore().get_course(course_key) if not course.enable_proctored_exams and not course.enable_timed_exams: # likewise if course does not have these features turned on # then quickly exit return # get all sequences, since they can be marked as timed/proctored exams _timed_exams = modulestore().get_items( course_key, qualifiers={"category": "sequential"}, settings={"is_time_limited": True} ) # filter out any potential dangling sequences timed_exams = [timed_exam for timed_exam in _timed_exams if is_item_in_course_tree(timed_exam)] # enumerate over list of sequences which are time-limited and # add/update any exam entries in edx-proctoring for timed_exam in timed_exams: msg = "Found {location} as a timed-exam in course structure. Inspecting...".format( location=unicode(timed_exam.location) ) log.info(msg) try: exam = get_exam_by_content_id(unicode(course_key), unicode(timed_exam.location)) # update case, make sure everything is synced update_exam( exam_id=exam["id"], exam_name=timed_exam.display_name, time_limit_mins=timed_exam.default_time_limit_minutes, due_date=timed_exam.due, is_proctored=timed_exam.is_proctored_exam, is_practice_exam=timed_exam.is_practice_exam, is_active=True, ) msg = "Updated timed exam {exam_id}".format(exam_id=exam["id"]) log.info(msg) except ProctoredExamNotFoundException: exam_id = create_exam( course_id=unicode(course_key), content_id=unicode(timed_exam.location), exam_name=timed_exam.display_name, time_limit_mins=timed_exam.default_time_limit_minutes, due_date=timed_exam.due, is_proctored=timed_exam.is_proctored_exam, is_practice_exam=timed_exam.is_practice_exam, is_active=True, ) msg = "Created new timed exam {exam_id}".format(exam_id=exam_id) log.info(msg) # then see which exams we have in edx-proctoring that are not in # our current list. That means the the user has disabled it exams = get_all_exams_for_course(course_key) for exam in exams: if exam["is_active"]: # try to look up the content_id in the sequences location search = [timed_exam for timed_exam in timed_exams if unicode(timed_exam.location) == exam["content_id"]] if not search: # This means it was turned off in Studio, we need to mark # the exam as inactive (we don't delete!) msg = "Disabling timed exam {exam_id}".format(exam_id=exam["id"]) log.info(msg) update_exam(exam_id=exam["id"], is_proctored=False, is_active=False)