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_disable_credit_requirements(self): self.add_credit_course() requirements = [{ "namespace": "grade", "name": "grade", "criteria": { "min_grade": 0.8 } }, { "namespace": "grade", "name": "grade", "criteria": { "min_grade": 0.8 } }] set_credit_requirements(self.course_key, requirements) self.assertEqual(len(get_credit_requirements(self.course_key)), 1) requirements = [{ "namespace": "reverification", "name": "midterm", "criteria": {} }] set_credit_requirements(self.course_key, requirements) self.assertEqual(len(get_credit_requirements(self.course_key)), 1) grade_req = CreditRequirement.objects.filter(namespace="grade", name="grade") self.assertEqual(len(grade_req), 1) self.assertEqual(grade_req[0].active, False)
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_disable_credit_requirements(self): self.add_credit_course() requirements = [{ "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }] api.set_credit_requirements(self.course_key, requirements) self.assertEqual(len(api.get_credit_requirements(self.course_key)), 1) requirements = [{ "namespace": "reverification", "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "Assessment 1", "criteria": {}, }] api.set_credit_requirements(self.course_key, requirements) self.assertEqual(len(api.get_credit_requirements(self.course_key)), 1) grade_req = CreditRequirement.objects.filter(namespace="grade", name="grade") self.assertEqual(len(grade_req), 1) self.assertEqual(grade_req[0].active, False)
def test_set_get_credit_requirements(self): """Test that if same requirement is added multiple times then it is added only one time and update for next all iterations. """ self.add_credit_course() requirements = [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 } }, { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.9 } } ] set_credit_requirements(self.course_key, requirements) self.assertEqual(len(get_credit_requirements(self.course_key)), 1) # now verify that the saved requirement has values of last requirement # from all same requirements self.assertEqual(get_credit_requirements(self.course_key)[0], requirements[1])
def test_disable_credit_requirements(self): self.add_credit_course() requirements = [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 } } ] set_credit_requirements(self.course_key, requirements) self.assertEqual(len(get_credit_requirements(self.course_key)), 1) requirements = [ { "namespace": "reverification", "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "Assessment 1", "criteria": {} } ] set_credit_requirements(self.course_key, requirements) self.assertEqual(len(get_credit_requirements(self.course_key)), 1) grade_req = CreditRequirement.objects.filter(namespace="grade", name="grade") self.assertEqual(len(grade_req), 1) self.assertEqual(grade_req[0].active, False)
def test_disable_credit_requirements(self): self.add_credit_course() requirements = [ { "namespace": "grade", "name": "grade", "criteria": { "min_grade": 0.8 } }, { "namespace": "grade", "name": "grade", "criteria": { "min_grade": 0.8 } } ] set_credit_requirements(self.course_key, requirements) self.assertEqual(len(get_credit_requirements(self.course_key)), 1) requirements = [ { "namespace": "reverification", "name": "midterm", "criteria": {} } ] set_credit_requirements(self.course_key, requirements) self.assertEqual(len(get_credit_requirements(self.course_key)), 1) grade_req = CreditRequirement.objects.filter(namespace="grade", name="grade") self.assertEqual(len(grade_req), 1) self.assertEqual(grade_req[0].active, False)
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=six.text_type(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=str(self.course.id), content_id=str(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) 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(self.subsection.location) assert requirements[1]['display_name'] == 'A Proctored Exam' assert requirements[1]['criteria'] == {}
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_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_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_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) listen_for_course_publish(self, self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0)
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) assert len(requirements) == 0 on_course_publish(self.course.id) requirements = get_credit_requirements(self.course.id) assert len(requirements) == 0
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_requirements_invalid_course(self): """ Make sure that the receiver correctly fires off the task when invoked by signal """ requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 0) listen_for_course_publish(self, 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 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) listen_for_course_publish(self, self.course.id) requirements = get_credit_requirements(self.course.id) self.assertEqual(len(requirements), 1)
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) listen_for_course_publish(self, 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_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_remove_credit_requirement_status(self): self.add_credit_course() requirements = [ {"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.8}}, { "namespace": "reverification", "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "Assessment 1", "criteria": {}, }, ] api.set_credit_requirements(self.course_key, requirements) course_requirements = api.get_credit_requirements(self.course_key) self.assertEqual(len(course_requirements), 2) # before setting credit_requirement_status api.remove_credit_requirement_status("staff", self.course_key, "grade", "grade") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertIsNone(req_status[0]["status"]) self.assertIsNone(req_status[0]["status_date"]) self.assertIsNone(req_status[0]["reason"]) # Set the requirement to "satisfied" and check that it's actually set api.set_credit_requirement_status("staff", self.course_key, "grade", "grade") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertEqual(len(req_status), 1) self.assertEqual(req_status[0]["status"], "satisfied") # remove the credit requirement status and check that it's actually removed api.remove_credit_requirement_status("staff", self.course_key, "grade", "grade") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertIsNone(req_status[0]["status"]) self.assertIsNone(req_status[0]["status_date"]) self.assertIsNone(req_status[0]["reason"])
def test_disable_existing_requirement(self): self.add_credit_course() # Set initial requirements requirements = [{ "namespace": "reverification", "name": "midterm", "display_name": "Midterm", "criteria": {}, }, { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }] api.set_credit_requirements(self.course_key, requirements) # Update the requirements, removing an existing requirement api.set_credit_requirements(self.course_key, requirements[1:]) # Expect that now only the grade requirement is returned visible_reqs = api.get_credit_requirements(self.course_key) self.assertEqual(len(visible_reqs), 1) self.assertEqual(visible_reqs[0]["namespace"], "grade")
def test_disable_existing_requirement(self): self.add_credit_course() # Set initial requirements requirements = [ { "namespace": "reverification", "name": "midterm", "display_name": "Midterm", "criteria": {}, }, { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, } ] api.set_credit_requirements(self.course_key, requirements) # Update the requirements, removing an existing requirement api.set_credit_requirements(self.course_key, requirements[1:]) # Expect that now only the grade requirement is returned visible_reqs = api.get_credit_requirements(self.course_key) self.assertEqual(len(visible_reqs), 1) self.assertEqual(visible_reqs[0]["namespace"], "grade")
def test_set_credit_requirement_status(self): self.add_credit_course() requirements = [ {"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.8}}, { "namespace": "reverification", "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "Assessment 1", "criteria": {}, }, ] api.set_credit_requirements(self.course_key, requirements) course_requirements = api.get_credit_requirements(self.course_key) self.assertEqual(len(course_requirements), 2) # Initially, the status should be None req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertEqual(req_status[0]["status"], None) # Set the requirement to "satisfied" and check that it's actually set api.set_credit_requirement_status("staff", self.course_key, "grade", "grade") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertEqual(req_status[0]["status"], "satisfied") # Set the requirement to "failed" and check that it's actually set api.set_credit_requirement_status("staff", self.course_key, "grade", "grade", status="failed") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertEqual(req_status[0]["status"], "failed")
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_set_get_credit_requirements(self): # Test that if same requirement is added multiple times self.add_credit_course() requirements = [ {"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.8}}, {"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.9}}, ] api.set_credit_requirements(self.course_key, requirements) self.assertEqual(len(api.get_credit_requirements(self.course_key)), 1)
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_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_credit_requirement_status(self): self.add_credit_course() requirements = [{ "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, { "namespace": "reverification", "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "Assessment 1", "criteria": {}, }] api.set_credit_requirements(self.course_key, requirements) course_requirements = api.get_credit_requirements(self.course_key) self.assertEqual(len(course_requirements), 2) # before setting credit_requirement_status api.remove_credit_requirement_status("staff", self.course_key, "grade", "grade") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertIsNone(req_status[0]["status"]) self.assertIsNone(req_status[0]["status_date"]) self.assertIsNone(req_status[0]["reason"]) # Set the requirement to "satisfied" and check that it's actually set api.set_credit_requirement_status("staff", self.course_key, "grade", "grade") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertEqual(len(req_status), 1) self.assertEqual(req_status[0]["status"], "satisfied") # remove the credit requirement status and check that it's actually removed api.remove_credit_requirement_status("staff", self.course_key, "grade", "grade") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertIsNone(req_status[0]["status"]) self.assertIsNone(req_status[0]["status_date"]) self.assertIsNone(req_status[0]["reason"])
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 test_set_credit_requirement_status(self): self.add_credit_course() requirements = [{ "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, { "namespace": "reverification", "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "Assessment 1", "criteria": {}, }] api.set_credit_requirements(self.course_key, requirements) course_requirements = api.get_credit_requirements(self.course_key) self.assertEqual(len(course_requirements), 2) # Initially, the status should be None req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertEqual(req_status[0]["status"], None) # Set the requirement to "satisfied" and check that it's actually set api.set_credit_requirement_status("staff", self.course_key, "grade", "grade") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertEqual(req_status[0]["status"], "satisfied") # Set the requirement to "failed" and check that it's actually set api.set_credit_requirement_status("staff", self.course_key, "grade", "grade", status="failed") req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade") self.assertEqual(req_status[0]["status"], "failed")
def test_set_get_credit_requirements(self): self.add_credit_course() requirements = [{ "namespace": "grade", "name": "grade", "criteria": { "min_grade": 0.8 } }, { "namespace": "grade", "name": "grade", "criteria": { "min_grade": 0.8 } }] set_credit_requirements(self.course_key, requirements) self.assertEqual(len(get_credit_requirements(self.course_key)), 1)
def listen_for_grade_calculation(sender, username, grade_summary, course_key, deadline, **kwargs): # pylint: disable=unused-argument """Receive 'MIN_GRADE_REQUIREMENT_STATUS' signal and update minimum grade requirement status. Args: sender: None username(string): user name grade_summary(dict): Dict containing output from the course grader course_key(CourseKey): The key for the course deadline(datetime): Course end date or None Kwargs: kwargs : None """ # This needs to be imported here to avoid a circular dependency # that can cause syncdb to fail. from openedx.core.djangoapps.credit import api course_id = CourseKey.from_string(unicode(course_key)) is_credit = api.is_credit_course(course_id) if is_credit: requirements = api.get_credit_requirements(course_id, namespace='grade') if requirements: criteria = requirements[0].get('criteria') if criteria: min_grade = criteria.get('min_grade') if grade_summary['percent'] >= min_grade: reason_dict = {'final_grade': grade_summary['percent']} api.set_credit_requirement_status(username, course_id, 'grade', 'grade', status="satisfied", reason=reason_dict) elif deadline and deadline < timezone.now(): api.set_credit_requirement_status(username, course_id, 'grade', 'grade', status="failed", reason={})
def test_set_get_credit_requirements(self): # Test that if same requirement is added multiple times self.add_credit_course() requirements = [{ "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.9 }, }] api.set_credit_requirements(self.course_key, requirements) self.assertEqual(len(api.get_credit_requirements(self.course_key)), 1)
def test_set_get_credit_requirements(self): self.add_credit_course() requirements = [ { "namespace": "grade", "name": "grade", "criteria": { "min_grade": 0.8 } }, { "namespace": "grade", "name": "grade", "criteria": { "min_grade": 0.8 } } ] set_credit_requirements(self.course_key, requirements) self.assertEqual(len(get_credit_requirements(self.course_key)), 1)
def listen_for_grade_calculation(sender, username, grade_summary, course_key, deadline, **kwargs): # pylint: disable=unused-argument """Receive 'MIN_GRADE_REQUIREMENT_STATUS' signal and update minimum grade requirement status. Args: sender: None username(string): user name grade_summary(dict): Dict containing output from the course grader course_key(CourseKey): The key for the course deadline(datetime): Course end date or None Kwargs: kwargs : None """ # This needs to be imported here to avoid a circular dependency # that can cause syncdb to fail. from openedx.core.djangoapps.credit import api course_id = CourseKey.from_string(unicode(course_key)) is_credit = api.is_credit_course(course_id) if is_credit: requirements = api.get_credit_requirements(course_id, namespace='grade') if requirements: criteria = requirements[0].get('criteria') if criteria: min_grade = criteria.get('min_grade') if grade_summary['percent'] >= min_grade: reason_dict = {'final_grade': grade_summary['percent']} api.set_credit_requirement_status( username, course_id, 'grade', 'grade', status="satisfied", reason=reason_dict ) elif deadline and deadline < timezone.now(): api.set_credit_requirement_status( username, course_id, 'grade', 'grade', status="failed", reason={} )
def listen_for_grade_calculation(sender, user, course_grade, course_key, deadline, **kwargs): # pylint: disable=unused-argument """Receive 'MIN_GRADE_REQUIREMENT_STATUS' signal and update minimum grade requirement status. Args: sender: None user(User): User Model object course_grade(CourseGrade): CourseGrade object course_key(CourseKey): The key for the course deadline(datetime): Course end date or None Kwargs: kwargs : None """ # This needs to be imported here to avoid a circular dependency # that can cause syncdb to fail. from openedx.core.djangoapps.credit import api course_id = CourseKey.from_string(unicode(course_key)) is_credit = api.is_credit_course(course_id) if is_credit: requirements = api.get_credit_requirements(course_id, namespace='grade') if requirements: criteria = requirements[0].get('criteria') if criteria: min_grade = criteria.get('min_grade') passing_grade = course_grade.percent >= min_grade now = timezone.now() status = None reason = None if (deadline and now < deadline) or not deadline: # Student completed coursework on-time if passing_grade: # Student received a passing grade status = 'satisfied' reason = {'final_grade': course_grade.percent} else: # Submission after deadline if passing_grade: # Grade was good, but submission arrived too late status = 'failed' reason = { 'current_date': now, 'deadline': deadline } else: # Student failed to receive minimum grade status = 'failed' reason = { 'final_grade': course_grade.percent, 'minimum_grade': min_grade } # We do not record a status if the user has not yet earned the minimum grade, but still has # time to do so. if status and reason: api.set_credit_requirement_status( user, course_id, 'grade', 'grade', status=status, reason=reason )
def test_set_credit_requirement_status(self): username = "******" credit_course = self.add_credit_course() requirements = [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, { "namespace": "reverification", "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "Assessment 1", "criteria": {}, } ] api.set_credit_requirements(self.course_key, requirements) course_requirements = api.get_credit_requirements(self.course_key) self.assertEqual(len(course_requirements), 2) # Initially, the status should be None self.assert_grade_requirement_status(None, 0) # Requirement statuses cannot be changed if a CreditRequest exists credit_request = CreditRequest.objects.create( course=credit_course, provider=CreditProvider.objects.first(), username=username, ) api.set_credit_requirement_status(username, self.course_key, "grade", "grade") self.assert_grade_requirement_status(None, 0) credit_request.delete() # Set the requirement to "satisfied" and check that it's actually set api.set_credit_requirement_status(username, self.course_key, "grade", "grade") self.assert_grade_requirement_status('satisfied', 0) # Set the requirement to "failed" and check that it's actually set api.set_credit_requirement_status(username, self.course_key, "grade", "grade", status="failed") self.assert_grade_requirement_status('failed', 0) req_status = api.get_credit_requirement_status(self.course_key, "staff") self.assertEqual(req_status[0]["status"], "failed") self.assertEqual(req_status[0]["order"], 0) # make sure the 'order' on the 2nd requirement is set correctly (aka 1) self.assertEqual(req_status[1]["status"], None) self.assertEqual(req_status[1]["order"], 1) # Set the requirement to "declined" and check that it's actually set api.set_credit_requirement_status( username, self.course_key, "reverification", "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", status="declined" ) req_status = api.get_credit_requirement_status( self.course_key, username, namespace="reverification", name="i4x://edX/DemoX/edx-reverification-block/assessment_uuid" ) self.assertEqual(req_status[0]["status"], "declined")
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 listen_for_grade_calculation(sender, user, grade_summary, course_key, deadline, **kwargs): # pylint: disable=unused-argument """Receive 'MIN_GRADE_REQUIREMENT_STATUS' signal and update minimum grade requirement status. Args: sender: None user(User): User Model object grade_summary(dict): Dict containing output from the course grader course_key(CourseKey): The key for the course deadline(datetime): Course end date or None Kwargs: kwargs : None """ # This needs to be imported here to avoid a circular dependency # that can cause syncdb to fail. from openedx.core.djangoapps.credit import api course_id = CourseKey.from_string(unicode(course_key)) is_credit = api.is_credit_course(course_id) if is_credit: requirements = api.get_credit_requirements(course_id, namespace='grade') if requirements: criteria = requirements[0].get('criteria') if criteria: min_grade = criteria.get('min_grade') passing_grade = grade_summary['percent'] >= min_grade now = timezone.now() status = None reason = None if (deadline and now < deadline) or not deadline: # Student completed coursework on-time if passing_grade: # Student received a passing grade status = 'satisfied' reason = {'final_grade': grade_summary['percent']} else: # Submission after deadline if passing_grade: # Grade was good, but submission arrived too late status = 'failed' reason = {'current_date': now, 'deadline': deadline} else: # Student failed to receive minimum grade status = 'failed' reason = { 'final_grade': grade_summary['percent'], 'minimum_grade': min_grade } # We do not record a status if the user has not yet earned the minimum grade, but still has # time to do so. if status and reason: api.set_credit_requirement_status(user, course_id, 'grade', 'grade', status=status, reason=reason)