def task_evaluate_subsection_completion_milestones(course_id, block_id, user_id): """ Updates users' milestones related to completion of a subsection. Args: course_id(str): Course id which triggered a completion event block_id(str): Id of the completed block user_id(int): Id of the user who completed a block """ store = modulestore() course_key = CourseKey.from_string(course_id) with store.bulk_operations(course_key): course = store.get_course(course_key) if not course or not course.enable_subsection_gating: log.debug( "Gating: ignoring evaluation of completion milestone because it disabled for course [%s]", course_id) else: try: user = User.objects.get(id=user_id) course_structure = get_course_blocks( user, store.make_course_usage_key(course_key)) completed_block_usage_key = UsageKey.from_string( block_id).map_into_course(course.id) subsection_block = _get_subsection_of_block( completed_block_usage_key, course_structure) subsection = course_structure[subsection_block] log.debug( "Gating: Evaluating completion milestone for subsection [%s] and user [%s]", str(subsection.location), user.id) gating_api.evaluate_prerequisite(course, subsection, user) except KeyError: log.error( "Gating: Given prerequisite subsection [%s] not found in course structure", block_id)
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 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 test_min_score_achieved(self, min_score, min_completion, module_score, module_completion, result, mock_completion): self._setup_gating_milestone(min_score, min_completion) mock_completion.return_value = module_completion self.subsection_grade.percent_graded = module_score / 100.0 evaluate_prerequisite(self.course, self.subsection_grade, self.user) assert milestones_api.user_has_milestone( self.user_dict, self.prereq_milestone) == result
def test_invalid_min_score(self, module_score, module_completion, result, mock_min_score, mock_completion): self._setup_gating_milestone(None, None) mock_completion.return_value = module_completion self.subsection_grade.percent_graded = module_score / 100.0 mock_min_score.return_value = 100, 100 evaluate_prerequisite(self.course, self.subsection_grade, self.user) self.assertEqual( milestones_api.user_has_milestone(self.user_dict, self.prereq_milestone), result)
def evaluate_subsection_gated_milestones(**kwargs): """ Receives the SUBSECTION_SCORE_CHANGED signal and triggers the evaluation of any milestone relationships which are attached to the subsection. Arguments: kwargs (dict): Contains user, course, course_structure, subsection_grade Returns: None """ subsection_grade = kwargs['subsection_grade'] gating_api.evaluate_prerequisite(kwargs['course'], subsection_grade, kwargs.get('user'))
def test_content_unlocked(self): """ Test that a sequential/subsection with met prereqs correctly indicated that its content is unlocked """ course = self.course self.setup_gated_section(self.course_blocks['gated_content'], self.course_blocks['prerequisite']) # complete the prerequisite to unlock the gated content # this call triggers reevaluation of prerequisites fulfilled by the gating block. with patch( 'openedx.core.lib.gating.api.get_subsection_completion_percentage', Mock(return_value=100)): lms_gating_api.evaluate_prerequisite( self.course, Mock(location=self.course_blocks['prerequisite'].location, percent_graded=1.0), self.user, ) response = self.client.get(course_home_url(course)) self.assertEqual(response.status_code, 200) response_content = pq(response.content) # check unlock icon is not present unlock_icon = response_content('.fa-unlock') self.assertFalse(unlock_icon, "unlock icon is present, yet shouldn't be.") gated_subsection_title = self.course_blocks[ 'gated_content'].display_name every_subsection_on_outline = response_content('.subsection-title') subsection_has_gated_text = False says_prerequisite_required = False for subsection_contents in every_subsection_on_outline.contents(): subsection_has_gated_text = gated_subsection_title in subsection_contents says_prerequisite_required = "Prerequisite:" in subsection_contents # check that subsection-title-name is the display name of gated content section self.assertTrue(subsection_has_gated_text) self.assertFalse(says_prerequisite_required)
def test_special_exams(self): """ When the block structure transformers are set to allow users to view special exams, ensure that we can see the special exams and not any of the otherwise gated blocks. """ self.transformers = BlockStructureTransformers( [self.TRANSFORMER_CLASS_TO_TEST(True)]) self.course.enable_subsection_gating = True self.setup_gated_section(self.blocks['H'], self.blocks['A']) expected_blocks = ('course', 'A', 'B', 'C', 'ProctoredExam', 'D', 'E', 'PracticeExam', 'F', 'G', 'TimedExam', 'J', 'K', 'H', 'I') self.get_blocks_and_check_against_expected(self.user, expected_blocks) # clear the request cache to simulate a new request self.clear_caches() # this call triggers reevaluation of prerequisites fulfilled by the gating block. lms_gating_api.evaluate_prerequisite( self.course, Mock(location=self.blocks['A'].location, percent_graded=1.0), self.user, ) self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS)
def test_gated(self, gated_block_ref, gating_block_ref, expected_blocks_before_completion): """ Students should be able to see gated content blocks before and after they have completed the prerequisite for it. First, checks that a student can see the gated block when it is gated by the gating block and no attempt has been made to complete the gating block. Then, checks that the student can see the gated block after the gating block has been completed. expected_blocks_before_completion is the set of blocks we expect to be visible to the student before the student has completed the gating block. The test data includes one special exam and one non-special block as the gating blocks. """ self.course.enable_subsection_gating = True self.setup_gated_section(self.blocks[gated_block_ref], self.blocks[gating_block_ref]) with self.assertNumQueries(6): self.get_blocks_and_check_against_expected( self.user, expected_blocks_before_completion) # clear the request cache to simulate a new request self.clear_caches() # this call triggers reevaluation of prerequisites fulfilled by the gating block. lms_gating_api.evaluate_prerequisite( self.course, Mock(location=self.blocks[gating_block_ref].location, percent_graded=1.0), self.user, ) with self.assertNumQueries(6): self.get_blocks_and_check_against_expected( self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL)
def test_no_gated_content(self, mock_score): gating_api.add_prerequisite(self.course.id, self.seq1.location) evaluate_prerequisite(self.course, self.subsection_grade, self.user) assert not mock_score.called
def test_no_prerequisites(self, mock_score): evaluate_prerequisite(self.course, self.subsection_grade, self.user) assert not mock_score.called