def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument """ Receives publishing signal and performs publishing related workflows, such as registering proctored exams, building up credit requirements, and performing search indexing """ # first is to registered exams, the credit subsystem will assume that # all proctored exams have already been registered, so we have to do that first try: register_special_exams(course_key) # pylint: disable=broad-except except Exception as exception: log.exception(exception) # then call into the credit subsystem (in /openedx/djangoapps/credit) # to perform any 'on_publish' workflow on_course_publish(course_key) # Finally call into the course search subsystem # to kick off an indexing action if CoursewareSearchIndexer.indexing_is_enabled(): # import here, because signal is registered at startup, but items in tasks are not yet able to be loaded from contentstore.tasks import update_search_index update_search_index.delay(unicode(course_key), datetime.now(UTC).isoformat())
def test_proctored_exam_requirements(self): """ Make sure that proctored exams are being registered as requirements """ self.add_credit_course(self.course.id) create_exam( course_id=unicode(self.course.id), content_id='foo', exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=True ) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) # just inspect the proctored exam requirement requirements = [ requirement for requirement in get_credit_requirements(self.course.id) if requirement['namespace'] == 'proctored_exam' ] self.assertEqual(len(requirements), 1) self.assertEqual(requirements[0]['namespace'], 'proctored_exam') self.assertEqual(requirements[0]['name'], 'proctored_exam_id:1') self.assertEqual(requirements[0]['display_name'], 'A Proctored Exam') self.assertEqual(requirements[0]['criteria'], {})
def test_course_details_with_enabled_setting(self): """ Test that credit eligibility requirements are present in response if the feature flag 'ENABLE_CREDIT_ELIGIBILITY' is enabled. """ # verify that credit eligibility requirements block don't show if the # course is not set as credit course response = self.client.get_html(self.course_details_url) self.assertEqual(response.status_code, 200) self.assertNotContains(response, "Course Credit Requirements") self.assertNotContains(response, "Steps required to earn course credit") # verify that credit eligibility requirements block shows if the # course is set as credit course and it has eligibility requirements credit_course = CreditCourse(course_key=unicode(self.course.id), enabled=True) credit_course.save() self.assertEqual(len(get_credit_requirements(self.course.id)), 0) # test that after publishing course, minimum grade requirement is added on_course_publish(self.course.id) self.assertEqual(len(get_credit_requirements(self.course.id)), 1) response = self.client.get_html(self.course_details_url) self.assertEqual(response.status_code, 200) self.assertContains(response, "Course Credit Requirements") self.assertContains(response, "Steps required to earn course credit")
def test_proctored_exam_requirements(self): """ Make sure that proctored exams are being registered as requirements """ self.add_credit_course(self.course.id) create_exam( course_id=six.text_type(self.course.id), content_id=six.text_type(self.subsection.location), exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=True ) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 2) self.assertEqual(requirements[1]['namespace'], 'proctored_exam') self.assertEqual(requirements[1]['name'], six.text_type(self.subsection.location)) self.assertEqual(requirements[1]['display_name'], 'A Proctored Exam') self.assertEqual(requirements[1]['criteria'], {})
def test_task_adding_requirements_invalid_course(self): """ Test that credit requirements cannot be added for non credit course. """ requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0)
def test_task_adding_icrv_requirements(self): """Make sure that the receiver correctly fires off the task when invoked by signal. """ self.add_credit_course(self.course.id) self.add_icrv_xblock() requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 2)
def test_task_adding_requirements(self): """Test that credit requirements are added properly for credit course. Make sure that the receiver correctly fires off the task when invoked by signal. """ self.add_credit_course(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 1)
def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument """ Receives publishing signal and performs publishing related workflows, such as registering proctored exams, building up credit requirements, and performing search indexing """ # first is to registered exams, the credit subsystem will assume that # all proctored exams have already been registered, so we have to do that first register_special_exams(course_key) # then call into the credit subsystem (in /openedx/djangoapps/credit) # to perform any 'on_publish' workflow on_course_publish(course_key)
def test_retry(self): """Test that adding credit requirements is retried when 'InvalidCreditRequirements' exception is raised. Make sure that the receiver correctly fires off the task when invoked by signal """ self.add_credit_course(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0)
def test_proctored_exam_filtering(self): """ Make sure that timed or inactive exams do not end up in the requirements table """ self.add_credit_course(self.course.id) create_exam( course_id=unicode(self.course.id), content_id='foo', exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=False, is_active=True ) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 1) # make sure we don't have a proctoring requirement self.assertFalse([ requirement for requirement in requirements if requirement['namespace'] == 'proctored_exam' ]) create_exam( course_id=unicode(self.course.id), content_id='foo2', exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=False ) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 1) # make sure we don't have a proctoring requirement self.assertFalse([ requirement for requirement in requirements if requirement['namespace'] == 'proctored_exam' ])
def test_credit_requirement_blocks_ordering(self): """ Test ordering of the proctoring and ICRV blocks are in proper order. """ self.add_credit_course(self.course.id) subsection = ItemFactory.create(parent=self.section, category='sequential', display_name='Dummy Subsection') create_exam(course_id=unicode(self.course.id), content_id=unicode(subsection.location), exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=True) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 2) self.assertEqual(requirements[1]['namespace'], 'proctored_exam') self.assertEqual(requirements[1]['name'], unicode(subsection.location)) self.assertEqual(requirements[1]['display_name'], 'A Proctored Exam') self.assertEqual(requirements[1]['criteria'], {}) # Create multiple ICRV blocks start = datetime.now(UTC) self.add_icrv_xblock(related_assessment_name="Midterm A", start_date=start) start = start - timedelta(days=1) self.add_icrv_xblock(related_assessment_name="Midterm B", start_date=start) # Primary sort is based on start date on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) # grade requirement is added on publish of the requirements self.assertEqual(len(requirements), 4) # check requirements are added in the desired order # 1st Minimum grade then the blocks with start date than other blocks self.assertEqual(requirements[0]["display_name"], "Minimum Grade") self.assertEqual(requirements[1]["display_name"], "A Proctored Exam") self.assertEqual(requirements[2]["display_name"], "Midterm B") self.assertEqual(requirements[3]["display_name"], "Midterm A")
def test_icrv_requirement_ordering(self): self.add_credit_course(self.course.id) # Create multiple ICRV blocks start = datetime.now(UTC) self.add_icrv_xblock(related_assessment_name="Midterm A", start_date=start) start = start - timedelta(days=1) self.add_icrv_xblock(related_assessment_name="Midterm B", start_date=start) # Primary sort is based on start date on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id, namespace="reverification") self.assertEqual(len(requirements), 2) self.assertEqual(requirements[0]["display_name"], "Midterm B") self.assertEqual(requirements[1]["display_name"], "Midterm A") # Add two additional ICRV blocks that have no start date # and the same name. start = datetime.now(UTC) first_block = self.add_icrv_xblock( related_assessment_name="Midterm Start Date") start = start + timedelta(days=1) second_block = self.add_icrv_xblock( related_assessment_name="Midterm Start Date") on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id, namespace="reverification") self.assertEqual(len(requirements), 4) # Since we are now primarily sorting on start_date and display_name if # start_date is present otherwise we are just sorting on display_name. self.assertEqual(requirements[0]["display_name"], "Midterm B") self.assertEqual(requirements[1]["display_name"], "Midterm A") self.assertEqual(requirements[2]["display_name"], "Midterm Start Date") self.assertEqual(requirements[3]["display_name"], "Midterm Start Date") # Since the last two requirements have the same display name, # we need to also check that their internal names (locations) are the same. self.assertEqual(requirements[2]["name"], first_block.get_credit_requirement_name()) self.assertEqual(requirements[3]["name"], second_block.get_credit_requirement_name())
def test_remove_icrv_requirement(self): self.add_credit_course(self.course.id) self.add_icrv_xblock() on_course_publish(self.course.id) # There should be one ICRV requirement requirements = get_credit_requirements(self.course.id, namespace="reverification") self.assertEqual(len(requirements), 1) # Delete the parent section containing the ICRV block with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, self.course.id): self.store.delete_item(self.subsection.location, ModuleStoreEnum.UserID.test) # Check that the ICRV block is no longer visible in the requirements on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id, namespace="reverification") self.assertEqual(len(requirements), 0)
def test_credit_requirement_blocks_ordering(self): """ Test ordering of the proctoring and ICRV blocks are in proper order. """ self.add_credit_course(self.course.id) subsection = ItemFactory.create(parent=self.section, category='sequential', display_name='Dummy Subsection') create_exam( course_id=unicode(self.course.id), content_id=unicode(subsection.location), exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=True ) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 2) self.assertEqual(requirements[1]['namespace'], 'proctored_exam') self.assertEqual(requirements[1]['name'], unicode(subsection.location)) self.assertEqual(requirements[1]['display_name'], 'A Proctored Exam') self.assertEqual(requirements[1]['criteria'], {}) # Create multiple ICRV blocks start = datetime.now(UTC) self.add_icrv_xblock(related_assessment_name="Midterm A", start_date=start) start = start - timedelta(days=1) self.add_icrv_xblock(related_assessment_name="Midterm B", start_date=start) # Primary sort is based on start date on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) # grade requirement is added on publish of the requirements self.assertEqual(len(requirements), 4) # check requirements are added in the desired order # 1st Minimum grade then the blocks with start date than other blocks self.assertEqual(requirements[0]["display_name"], "Minimum Grade") self.assertEqual(requirements[1]["display_name"], "A Proctored Exam") self.assertEqual(requirements[2]["display_name"], "Midterm B") self.assertEqual(requirements[3]["display_name"], "Midterm A")
def test_proctored_exam_filtering(self): """ Make sure that timed or inactive exams do not end up in the requirements table """ self.add_credit_course(self.course.id) create_exam(course_id=unicode(self.course.id), content_id='foo', exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=False, is_active=True) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 1) # make sure we don't have a proctoring requirement self.assertFalse([ requirement for requirement in requirements if requirement['namespace'] == 'proctored_exam' ]) create_exam(course_id=unicode(self.course.id), content_id='foo2', exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=False) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 1) # make sure we don't have a proctoring requirement self.assertFalse([ requirement for requirement in requirements if requirement['namespace'] == 'proctored_exam' ])
def update_special_exams_and_publish(course_key_str): """ Registers special exams for a given course and calls publishing flow. on_course_publish expects that the edx-proctoring subsystem has been refreshed before being executed, so both functions are called here synchronously. """ from cms.djangoapps.contentstore.proctoring import register_special_exams from openedx.core.djangoapps.credit.signals import on_course_publish course_key = CourseKey.from_string(course_key_str) LOGGER.info('Attempting to register exams for course %s', course_key_str) try: register_special_exams(course_key) LOGGER.info('Successfully registered exams for course %s', course_key_str) # pylint: disable=broad-except except Exception as exception: LOGGER.exception(exception) LOGGER.info('Publishing course %s', course_key_str) on_course_publish(course_key)
def test_icrv_requirement_ordering(self): self.add_credit_course(self.course.id) # Create multiple ICRV blocks start = datetime.now(UTC) self.add_icrv_xblock(related_assessment_name="Midterm A", start_date=start) start = start - timedelta(days=1) self.add_icrv_xblock(related_assessment_name="Midterm B", start_date=start) # Primary sort is based on start date on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id, namespace="reverification") self.assertEqual(len(requirements), 2) self.assertEqual(requirements[0]["display_name"], "Midterm B") self.assertEqual(requirements[1]["display_name"], "Midterm A") # Add two additional ICRV blocks that have no start date # and the same name. start = datetime.now(UTC) first_block = self.add_icrv_xblock(related_assessment_name="Midterm Start Date") start = start + timedelta(days=1) second_block = self.add_icrv_xblock(related_assessment_name="Midterm Start Date") on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id, namespace="reverification") self.assertEqual(len(requirements), 4) # Since we are now primarily sorting on start_date and display_name if # start_date is present otherwise we are just sorting on display_name. self.assertEqual(requirements[0]["display_name"], "Midterm B") self.assertEqual(requirements[1]["display_name"], "Midterm A") self.assertEqual(requirements[2]["display_name"], "Midterm Start Date") self.assertEqual(requirements[3]["display_name"], "Midterm Start Date") # Since the last two requirements have the same display name, # we need to also check that their internal names (locations) are the same. self.assertEqual(requirements[2]["name"], first_block.get_credit_requirement_name()) self.assertEqual(requirements[3]["name"], second_block.get_credit_requirement_name())
def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument """ Receives publishing signal and performs publishing related workflows, such as registering proctored exams, building up credit requirements, and performing search indexing """ # first is to registered exams, the credit subsystem will assume that # all proctored exams have already been registered, so we have to do that first register_proctored_exams(course_key) # then call into the credit subsystem (in /openedx/djangoapps/credit) # to perform any 'on_publish' workflow on_course_publish(course_key) # Finally call into the course search subsystem # to kick off an indexing action if CoursewareSearchIndexer.indexing_is_enabled(): # import here, because signal is registered at startup, but items in tasks are not yet able to be loaded from .tasks import update_search_index update_search_index.delay(unicode(course_key), datetime.now(UTC).isoformat())
def test_credit_requirement_blocks_ordering(self): """ Test ordering of proctoring blocks. """ self.add_credit_course(self.course.id) subsection = ItemFactory.create(parent=self.section, category='sequential', display_name='Dummy Subsection') create_exam(course_id=six.text_type(self.course.id), content_id=six.text_type(subsection.location), exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=True) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 2) self.assertEqual(requirements[1]['namespace'], 'proctored_exam') self.assertEqual(requirements[1]['name'], six.text_type(subsection.location)) self.assertEqual(requirements[1]['display_name'], 'A Proctored Exam') self.assertEqual(requirements[1]['criteria'], {}) # Primary sort is based on start date on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) # grade requirement is added on publish of the requirements self.assertEqual(len(requirements), 2) # check requirements are added in the desired order # 1st Minimum grade then the blocks with start date than other blocks self.assertEqual(requirements[0]["display_name"], "Minimum Grade") self.assertEqual(requirements[1]["display_name"], "A Proctored Exam")
def test_credit_requirement_blocks_ordering(self): """ Test ordering of proctoring blocks. """ self.add_credit_course(self.course.id) subsection = ItemFactory.create(parent=self.section, category='sequential', display_name='Dummy Subsection') create_exam( course_id=str(self.course.id), content_id=str(subsection.location), exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=True ) requirements = get_credit_requirements(self.course.id) assert len(requirements) == 0 on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) assert len(requirements) == 2 assert requirements[1]['namespace'] == 'proctored_exam' assert requirements[1]['name'] == str(subsection.location) assert requirements[1]['display_name'] == 'A Proctored Exam' assert requirements[1]['criteria'] == {} # Primary sort is based on start date on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) # grade requirement is added on publish of the requirements assert len(requirements) == 2 # check requirements are added in the desired order # 1st Minimum grade then the blocks with start date than other blocks assert requirements[0]['display_name'] == 'Minimum Grade' assert requirements[1]['display_name'] == 'A Proctored Exam'
def test_credit_requirement_blocks_ordering(self): """ Test ordering of proctoring blocks. """ self.add_credit_course(self.course.id) subsection = ItemFactory.create(parent=self.section, category='sequential', display_name='Dummy Subsection') create_exam( course_id=six.text_type(self.course.id), content_id=six.text_type(subsection.location), exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=True ) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 2) self.assertEqual(requirements[1]['namespace'], 'proctored_exam') self.assertEqual(requirements[1]['name'], six.text_type(subsection.location)) self.assertEqual(requirements[1]['display_name'], 'A Proctored Exam') self.assertEqual(requirements[1]['criteria'], {}) # Primary sort is based on start date on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) # grade requirement is added on publish of the requirements self.assertEqual(len(requirements), 2) # check requirements are added in the desired order # 1st Minimum grade then the blocks with start date than other blocks self.assertEqual(requirements[0]["display_name"], "Minimum Grade") self.assertEqual(requirements[1]["display_name"], "A Proctored Exam")
def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument """ Receives publishing signal and performs publishing related workflows, such as registering proctored exams, building up credit requirements, and performing search indexing """ is_enabled = ENABLE_ASYNC_REGISTER_EXAMS.is_enabled(course_key) if is_enabled: from cms.djangoapps.contentstore.tasks import update_special_exams_and_publish course_key_str = str(course_key) update_special_exams_and_publish.delay(course_key_str) else: # first is to registered exams, the credit subsystem will assume that # all proctored exams have already been registered, so we have to do that first try: register_special_exams(course_key) # pylint: disable=broad-except except Exception as exception: log.exception(exception) # then call into the credit subsystem (in /openedx/djangoapps/credit) # to perform any 'on_publish' workflow on_course_publish(course_key) # import here, because signal is registered at startup, but items in tasks are not yet able to be loaded from cms.djangoapps.contentstore.tasks import update_outline_from_modulestore_task, update_search_index if key_supports_outlines(course_key): # Push the course outline to learning_sequences asynchronously. update_outline_from_modulestore_task.delay(str(course_key)) # Finally call into the course search subsystem # to kick off an indexing action if CoursewareSearchIndexer.indexing_is_enabled( ) and CourseAboutSearchIndexer.indexing_is_enabled(): update_search_index.delay(str(course_key), datetime.now(UTC).isoformat())
def test_query_counts(self): self.add_credit_course(self.course.id) self.add_icrv_xblock() with check_mongo_calls_range(max_finds=7): on_course_publish(self.course.id)
def test_proctored_exam_filtering(self): """ Make sure that timed or inactive exams do not end up in the requirements table Also practice protored exams are not a requirement """ self.add_credit_course(self.course.id) create_exam( course_id=str(self.course.id), content_id='foo', exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=False, is_active=True ) requirements = get_credit_requirements(self.course.id) assert len(requirements) == 0 on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) assert len(requirements) == 1 # make sure we don't have a proctoring requirement assert not [requirement for requirement in requirements if requirement['namespace'] == 'proctored_exam'] create_exam( course_id=str(self.course.id), content_id='foo2', exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=False ) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) assert len(requirements) == 1 # make sure we don't have a proctoring requirement assert not [requirement for requirement in requirements if requirement['namespace'] == 'proctored_exam'] # practice proctored exams aren't requirements create_exam( course_id=str(self.course.id), content_id='foo3', exam_name='A Proctored Exam', time_limit_mins=10, is_proctored=True, is_active=True, is_practice_exam=True ) on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) assert len(requirements) == 1 # make sure we don't have a proctoring requirement assert not [requirement for requirement in requirements if requirement['namespace'] == 'proctored_exam']
def test_query_counts(self): self.add_credit_course(self.course.id) self.add_icrv_xblock() with check_mongo_calls_range(max_finds=11): on_course_publish(self.course.id)