def setUp(self): """ Initial data setup """ super(TestHandleItemDeleted, self).setUp() self.course = CourseFactory.create() self.course.enable_subsection_gating = True self.course.save() self.chapter = ItemFactory.create( parent=self.course, category="chapter", display_name="Chapter" ) self.open_seq = ItemFactory.create( parent=self.chapter, category='sequential', display_name="Open Sequential" ) self.gated_seq = ItemFactory.create( parent=self.chapter, category='sequential', display_name="Gated Sequential" ) gating_api.add_prerequisite(self.course.id, self.open_seq.location) gating_api.set_required_content(self.course.id, self.gated_seq.location, self.open_seq.location, 100)
def test_is_gate_fulfilled(self, min_score, min_completion, learner_score, learner_completion, is_gate_fulfilled): """ Test if prereq section has any unfulfilled milestones """ student = UserFactory(is_staff=False) gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content( self.course.id, self.seq2.location, self.seq1.location, min_score, min_completion ) milestone = milestones_api.add_milestone(self.generic_milestone) milestones_api.add_course_content_milestone(self.course.id, self.seq1.location, 'fulfills', milestone) self.assertFalse(gating_api.is_gate_fulfilled(self.course.id, self.seq1.location, student.id)) # complete the prerequisite to unlock the gated content # this call triggers reevaluation of prerequisites fulfilled by the gating block. with patch.object(gating_api, 'get_subsection_completion_percentage') as mock_grade: mock_grade.return_value = learner_completion lms_gating_api.evaluate_prerequisite( self.course, Mock(location=self.seq1.location, percent_graded=learner_score / 100.0), student, ) self.assertEqual( gating_api.is_gate_fulfilled(self.course.id, self.seq1.location, student.id), is_gate_fulfilled )
def _setup_gating_milestone(self, min_score): """ Setup a gating milestone for testing """ gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, min_score) self.prereq_milestone = gating_api.get_gating_milestone(self.course.id, self.seq1.location, 'fulfills')
def test_get_gating_milestone_is_none(self): """ Test test_get_gating_milestone_is_none """ gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100) self.assertIsNone(gating_api.get_gating_milestone(self.course.id, self.seq1.location, 'requires')) self.assertIsNone(gating_api.get_gating_milestone(self.course.id, self.seq2.location, 'fulfills'))
def setup_gating_milestone(self, min_score): """ Setup a gating milestone for testing. Gating content: seq1 (must be fulfilled before access to seq2) Gated content: seq2 (requires completion of seq1 before access) """ gating_api.add_prerequisite(self.course.id, str(self.seq1.location)) gating_api.set_required_content(self.course.id, str(self.seq2.location), str(self.seq1.location), min_score) self.prereq_milestone = gating_api.get_gating_milestone(self.course.id, self.seq1.location, 'fulfills')
def setup_gated_section(self, gated_block, gating_block): """ Test helper to create a gating requirement. Args: gated_block: The block that should be inaccessible until gating_block is completed gating_block: The block that must be completed before access is granted """ gating_api.add_prerequisite(self.course.id, unicode(gating_block.location)) gating_api.set_required_content(self.course.id, gated_block.location, gating_block.location, 100)
def setup_gated_section(self, gated_block, gating_block): """ Test helper to create a gating requirement Args: gated_block: The block the that learner will not have access to until they complete the gating block gating_block: (The prerequisite) The block that must be completed to get access to the gated block """ gating_api.add_prerequisite(self.course.id, unicode(gating_block.location)) gating_api.set_required_content(self.course.id, gated_block.location, gating_block.location, 100)
def setup_gated_section(self, gated_block, gating_block): """ Test helper to create a gating requirement Args: gated_block: The block the that learner will not have access to until they complete the gating block gating_block: (The prerequisite) The block that must be completed to get access to the gated block """ gating_api.add_prerequisite(self.course.id, unicode(gating_block.location)) gating_api.set_required_content(self.course.id, gated_block.location, gating_block.location, 100)
def test_find_gating_milestones(self): """ Test test_find_gating_milestones """ gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100) milestone = milestones_api.add_milestone(self.generic_milestone) milestones_api.add_course_content_milestone(self.course.id, self.seq1.location, 'fulfills', milestone) self.assertEqual(len(gating_api.find_gating_milestones(self.course.id, self.seq1.location, 'fulfills')), 1) self.assertEqual(len(gating_api.find_gating_milestones(self.course.id, self.seq1.location, 'requires')), 0) self.assertEqual(len(gating_api.find_gating_milestones(self.course.id, self.seq2.location, 'fulfills')), 0) self.assertEqual(len(gating_api.find_gating_milestones(self.course.id, self.seq2.location, 'requires')), 1)
def setup_gating_milestone(self, min_score): """ Setup a gating milestone for testing. Gating content: seq1 (must be fulfilled before access to seq2) Gated content: seq2 (requires completion of seq1 before access) """ gating_api.add_prerequisite(self.course.id, str(self.seq1.location)) gating_api.set_required_content(self.course.id, str(self.seq2.location), str(self.seq1.location), min_score) self.prereq_milestone = gating_api.get_gating_milestone( self.course.id, self.seq1.location, 'fulfills')
def test_find_gating_milestones(self): """ Test test_find_gating_milestones """ gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100) milestone = milestones_api.add_milestone(self.generic_milestone) milestones_api.add_course_content_milestone(self.course.id, self.seq1.location, 'fulfills', milestone) self.assertEqual(len(gating_api.find_gating_milestones(self.course.id, self.seq1.location, 'fulfills')), 1) self.assertEqual(len(gating_api.find_gating_milestones(self.course.id, self.seq1.location, 'requires')), 0) self.assertEqual(len(gating_api.find_gating_milestones(self.course.id, self.seq2.location, 'fulfills')), 0) self.assertEqual(len(gating_api.find_gating_milestones(self.course.id, self.seq2.location, 'requires')), 1)
def test_get_gating_milestone_is_none(self): """ Test test_get_gating_milestone_is_none """ gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100) self.assertIsNone( gating_api.get_gating_milestone(self.course.id, self.seq1.location, 'requires')) self.assertIsNone( gating_api.get_gating_milestone(self.course.id, self.seq2.location, 'fulfills'))
def test_required_content(self): """ Test test_required_content """ gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100) prereq_content_key, min_score = gating_api.get_required_content(self.course.id, self.seq2.location) self.assertEqual(prereq_content_key, unicode(self.seq1.location)) self.assertEqual(min_score, 100) gating_api.set_required_content(self.course.id, self.seq2.location, None, None) prereq_content_key, min_score = gating_api.get_required_content(self.course.id, self.seq2.location) self.assertIsNone(prereq_content_key) self.assertIsNone(min_score)
def test_get_gated_content(self): """ Test test_get_gated_content """ mock_user = MagicMock() mock_user.id.return_value = 1 self.assertEqual(gating_api.get_gated_content(self.course, mock_user), []) gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100) milestone = milestones_api.get_course_content_milestones(self.course.id, self.seq2.location, 'requires')[0] self.assertEqual(gating_api.get_gated_content(self.course, mock_user), [unicode(self.seq2.location)]) milestones_api.add_user_milestone({'id': mock_user.id}, milestone) self.assertEqual(gating_api.get_gated_content(self.course, mock_user), [])
def test_compute_is_prereq_met(self): """ Test if prereq has been met and force recompute """ student = UserFactory(is_staff=False) gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100, 0) # complete the prerequisite to unlock the gated content # this call triggers reevaluation of prerequisites fulfilled by the gating block. with patch.object(gating_api, 'get_subsection_grade_percentage') as mock_grade: mock_grade.return_value = 75 # don't force recompute prereq_met, prereq_meta_info = gating_api.compute_is_prereq_met( self.seq2.location, student.id, False) self.assertFalse(prereq_met) self.assertIsNone(prereq_meta_info['url']) self.assertIsNone(prereq_meta_info['display_name']) # force recompute prereq_met, prereq_meta_info = gating_api.compute_is_prereq_met( self.seq2.location, student.id, True) self.assertFalse(prereq_met) self.assertIsNotNone(prereq_meta_info['url']) self.assertIsNotNone(prereq_meta_info['display_name']) # change to passing grade mock_grade.return_value = 100 # don't force recompute prereq_met, prereq_meta_info = gating_api.compute_is_prereq_met( self.seq2.location, student.id, False) self.assertFalse(prereq_met) self.assertIsNone(prereq_meta_info['url']) self.assertIsNone(prereq_meta_info['display_name']) # force recompute prereq_met, prereq_meta_info = gating_api.compute_is_prereq_met( self.seq2.location, student.id, True) self.assertTrue(prereq_met) self.assertIsNotNone(prereq_meta_info['url']) self.assertIsNotNone(prereq_meta_info['display_name'])
def test_get_gated_content(self): """ Verify staff bypasses gated content and student gets list of unfulfilled prerequisites. """ staff = UserFactory(is_staff=True) student = UserFactory(is_staff=False) self.assertEqual(gating_api.get_gated_content(self.course, staff), []) self.assertEqual(gating_api.get_gated_content(self.course, student), []) gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100) milestone = milestones_api.get_course_content_milestones(self.course.id, self.seq2.location, 'requires')[0] self.assertEqual(gating_api.get_gated_content(self.course, staff), []) self.assertEqual(gating_api.get_gated_content(self.course, student), [unicode(self.seq2.location)]) milestones_api.add_user_milestone({'id': student.id}, milestone) # pylint: disable=no-member self.assertEqual(gating_api.get_gated_content(self.course, student), [])
def test_required_content(self): """ Test test_required_content """ gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100, 100) prereq_content_key, min_score, min_completion = gating_api.get_required_content( self.course.id, self.seq2.location) assert prereq_content_key == str(self.seq1.location) assert min_score == 100 assert min_completion == 100 gating_api.set_required_content(self.course.id, self.seq2.location, None, None, None) prereq_content_key, min_score, min_completion = gating_api.get_required_content( self.course.id, self.seq2.location) assert prereq_content_key is None assert min_score is None assert min_completion is None
def test_get_gated_content(self): """ Verify staff bypasses gated content and student gets list of unfulfilled prerequisites. """ staff = UserFactory(is_staff=True) student = UserFactory(is_staff=False) self.assertEqual(gating_api.get_gated_content(self.course, staff), []) self.assertEqual(gating_api.get_gated_content(self.course, student), []) gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100) milestone = milestones_api.get_course_content_milestones(self.course.id, self.seq2.location, 'requires')[0] self.assertEqual(gating_api.get_gated_content(self.course, staff), []) self.assertEqual(gating_api.get_gated_content(self.course, student), [unicode(self.seq2.location)]) milestones_api.add_user_milestone({'id': student.id}, milestone) self.assertEqual(gating_api.get_gated_content(self.course, student), [])
def test_is_gate_fulfilled(self): """ Test if prereq section has any unfulfilled milestones """ student = UserFactory(is_staff=False) gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100) milestone = milestones_api.add_milestone(self.generic_milestone) milestones_api.add_course_content_milestone(self.course.id, self.seq1.location, 'fulfills', milestone) self.assertFalse( gating_api.is_gate_fulfilled(self.course.id, self.seq1.location, student.id)) # complete the prerequisite to unlock the gated content # this call triggers reevaluation of prerequisites fulfilled by the gating block. with patch.object(gating_api, '_get_subsection_percentage') as mock_grade: mock_grade.return_value = 75 lms_gating_api.evaluate_prerequisite( self.course, Mock(location=self.seq1.location), student, ) self.assertFalse( gating_api.is_gate_fulfilled(self.course.id, self.seq1.location, student.id)) mock_grade.return_value = 100 lms_gating_api.evaluate_prerequisite( self.course, Mock(location=self.seq1.location), student, ) self.assertTrue( gating_api.is_gate_fulfilled(self.course.id, self.seq1.location, student.id))
def setUp(self): """ Initial data setup """ super(TestHandleItemDeleted, self).setUp() self.course = CourseFactory.create() self.course.enable_subsection_gating = True self.course.save() self.chapter = ItemFactory.create(parent=self.course, category="chapter", display_name="Chapter") self.open_seq = ItemFactory.create(parent=self.chapter, category='sequential', display_name="Open Sequential") self.gated_seq = ItemFactory.create(parent=self.chapter, category='sequential', display_name="Gated Sequential") gating_api.add_prerequisite(self.course.id, self.open_seq.location) gating_api.set_required_content(self.course.id, self.gated_seq.location, self.open_seq.location, 100, 100)
def test_compute_is_prereq_met(self): """ Test if prereq has been met and force recompute """ student = UserFactory(is_staff=False) gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100, 0) # complete the prerequisite to unlock the gated content # this call triggers reevaluation of prerequisites fulfilled by the gating block. with patch.object(gating_api, 'get_subsection_grade_percentage') as mock_grade: mock_grade.return_value = 75 # don't force recompute prereq_met, prereq_meta_info = gating_api.compute_is_prereq_met(self.seq2.location, student.id, False) self.assertFalse(prereq_met) self.assertIsNone(prereq_meta_info['url']) self.assertIsNone(prereq_meta_info['display_name']) # force recompute prereq_met, prereq_meta_info = gating_api.compute_is_prereq_met(self.seq2.location, student.id, True) self.assertFalse(prereq_met) self.assertIsNotNone(prereq_meta_info['url']) self.assertIsNotNone(prereq_meta_info['display_name']) # change to passing grade mock_grade.return_value = 100 # don't force recompute prereq_met, prereq_meta_info = gating_api.compute_is_prereq_met(self.seq2.location, student.id, False) self.assertFalse(prereq_met) self.assertIsNone(prereq_meta_info['url']) self.assertIsNone(prereq_meta_info['display_name']) # force recompute prereq_met, prereq_meta_info = gating_api.compute_is_prereq_met(self.seq2.location, student.id, True) self.assertTrue(prereq_met) self.assertIsNotNone(prereq_meta_info['url']) self.assertIsNotNone(prereq_meta_info['display_name'])
def test_is_gate_fulfilled(self, min_score, min_completion, learner_score, learner_completion, is_gate_fulfilled): """ Test if prereq section has any unfulfilled milestones """ student = UserFactory(is_staff=False) gating_api.add_prerequisite(self.course.id, self.seq1.location) gating_api.set_required_content( self.course.id, self.seq2.location, self.seq1.location, min_score, min_completion ) milestone = milestones_api.add_milestone(self.generic_milestone) milestones_api.add_course_content_milestone(self.course.id, self.seq1.location, 'fulfills', milestone) assert not gating_api.is_gate_fulfilled(self.course.id, self.seq1.location, student.id) # complete the prerequisite to unlock the gated content # this call triggers reevaluation of prerequisites fulfilled by the gating block. with patch.object(gating_api, 'get_subsection_completion_percentage') as mock_grade: mock_grade.return_value = learner_completion lms_gating_api.evaluate_prerequisite( self.course, Mock(location=self.seq1.location, percent_graded=learner_score / 100.0), student, ) assert gating_api.is_gate_fulfilled(self.course.id, self.seq1.location, student.id) == is_gate_fulfilled
def handle_item_deleted(**kwargs): """ Receives the item_deleted signal sent by Studio when an XBlock is removed from the course structure and removes any gating milestone data associated with it or its descendants. Arguments: kwargs (dict): Contains the content usage key of the item deleted Returns: None """ usage_key = kwargs.get('usage_key') if usage_key: # Strip branch info usage_key = usage_key.for_branch(None) course_key = usage_key.course_key deleted_module = modulestore().get_item(usage_key) for module in yield_dynamic_descriptor_descendants(deleted_module, kwargs.get('user_id')): # Remove prerequisite milestone data gating_api.remove_prerequisite(module.location) # Remove any 'requires' course content milestone relationships gating_api.set_required_content(course_key, module.location, None, None, None)
def handle_item_deleted(**kwargs): """ Receives the item_deleted signal sent by Studio when an XBlock is removed from the course structure and removes any gating milestone data associated with it or its descendants. Arguments: kwargs (dict): Contains the content usage key of the item deleted Returns: None """ usage_key = kwargs.get('usage_key') if usage_key: # Strip branch info usage_key = usage_key.for_branch(None) course_key = usage_key.course_key deleted_module = modulestore().get_item(usage_key) for module in yield_dynamic_descriptor_descendants(deleted_module, kwargs.get('user_id')): # Remove prerequisite milestone data gating_api.remove_prerequisite(module.location) # Remove any 'requires' course content milestone relationships gating_api.set_required_content(course_key, module.location, None, None)