def test_gated(self, gated_block_ref, gating_block_ref, expected_blocks_before_completion):
        """
        First, checks that a student cannot 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(8):
            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.
        with patch('gating.api._get_subsection_percentage', Mock(return_value=100)):
            lms_gating_api.evaluate_prerequisite(
                self.course,
                Mock(location=self.blocks[gating_block_ref].location),
                self.user,
            )

        with self.assertNumQueries(8):
            self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL)
    def test_gated(self, gated_block_ref, gating_block_ref, gating_block_child, expected_blocks_before_completion):
        """
        First, checks that a student cannot 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(3):
            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()

        # mock the api that the lms gating api calls to get the score for each block to always return 1 (ie 100%)
        with patch('gating.api.get_module_score', Mock(return_value=1)):

            # this call triggers reevaluation of prerequisites fulfilled by the parent of the
            # block passed in, so we pass in a child of the gating block
            lms_gating_api.evaluate_prerequisite(
                self.course,
                UsageKey.from_string(unicode(self.blocks[gating_block_child].location)),
                self.user.id)
        with self.assertNumQueries(2):
            self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL)
Beispiel #3
0
    def test_invalid_min_score(self, module_score, result, mock_min_score, mock_score):
        self._setup_gating_milestone(None)
        mock_score.return_value = module_score
        mock_min_score.return_value = 100

        evaluate_prerequisite(self.course, self.subsection_grade, self.user)
        self.assertEqual(milestones_api.user_has_milestone(self.user_dict, self.prereq_milestone), result)
Beispiel #4
0
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(
                u"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(
                    u"Gating: Evaluating completion milestone for subsection [%s] and user [%s]",
                    unicode(subsection.location), user.id
                )
                gating_api.evaluate_prerequisite(course, subsection, user)
            except KeyError:
                log.error(u"Gating: Given prerequisite subsection [%s] not found in course structure", block_id)
Beispiel #5
0
    def test_gated(self, gated_block_ref, gating_block_ref, gating_block_child, expected_blocks_before_completion):
        """
        First, checks that a student cannot 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(3):
            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()

        # mock the api that the lms gating api calls to get the score for each block to always return 1 (ie 100%)
        with patch('gating.api.get_module_score', Mock(return_value=1)):

            # this call triggers reevaluation of prerequisites fulfilled by the parent of the
            # block passed in, so we pass in a child of the gating block
            lms_gating_api.evaluate_prerequisite(
                self.course,
                UsageKey.from_string(unicode(self.blocks[gating_block_child].location)),
                self.user.id)
        with self.assertNumQueries(2):
            self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL)
Beispiel #6
0
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]",
                    unicode(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)
Beispiel #7
0
    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_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.
        with patch('openedx.core.lib.gating.api._get_subsection_percentage',
                   Mock(return_value=100)):
            lms_gating_api.evaluate_prerequisite(
                self.course,
                Mock(location=self.blocks['A'].location),
                self.user,
            )
        self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS)
Beispiel #9
0
    def test_min_score_achieved(self, module_score, result, mock_module_score):
        """ Test test_min_score_achieved """

        self._setup_gating_milestone(50)

        mock_module_score.return_value = module_score
        evaluate_prerequisite(self.course, self.prob1.location, self.user.id)
        self.assertEqual(milestones_api.user_has_milestone(self.user_dict, self.prereq_milestone), result)
Beispiel #10
0
    def test_no_gated_content(self, mock_module_score):
        """ Test test_no_gated_content """

        # Setup gating milestones data
        gating_api.add_prerequisite(self.course.id, self.seq1.location)

        evaluate_prerequisite(self.course, self.prob1.location, self.user.id)
        self.assertFalse(mock_module_score.called)
Beispiel #11
0
    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)
Beispiel #12
0
    def test_min_score_achieved(self, module_score, result, mock_module_score):
        """ Test test_min_score_achieved """

        self._setup_gating_milestone(50)

        mock_module_score.return_value = module_score
        evaluate_prerequisite(self.course, self.prob1.location, self.user.id)
        self.assertEqual(milestones_api.user_has_milestone(self.user_dict, self.prereq_milestone), result)
Beispiel #13
0
    def test_no_gated_content(self, mock_module_score):
        """ Test test_no_gated_content """

        # Setup gating milestones data
        gating_api.add_prerequisite(self.course.id, self.seq1.location)

        evaluate_prerequisite(self.course, self.prob1.location, self.user.id)
        self.assertFalse(mock_module_score.called)
Beispiel #14
0
    def test_invalid_min_score(self, module_score, result, mock_module_score, mock_log):
        """ Test test_invalid_min_score """

        self._setup_gating_milestone(None)

        mock_module_score.return_value = module_score
        evaluate_prerequisite(self.course, self.prob1.location, self.user.id)
        self.assertEqual(milestones_api.user_has_milestone(self.user_dict, self.prereq_milestone), result)
        self.assertTrue(mock_log.called)
Beispiel #15
0
    def test_invalid_min_score(self, module_score, result, mock_module_score, mock_log):
        """ Test test_invalid_min_score """

        self._setup_gating_milestone(None)

        mock_module_score.return_value = module_score
        evaluate_prerequisite(self.course, self.prob1.location, self.user.id)
        self.assertEqual(milestones_api.user_has_milestone(self.user_dict, self.prereq_milestone), result)
        self.assertTrue(mock_log.called)
Beispiel #16
0
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'))
Beispiel #17
0
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'))
Beispiel #18
0
def handle_score_changed(**kwargs):
    """
    Receives the PROBLEM_SCORE_CHANGED signal sent by LMS when a student's score has changed
    for a given component and triggers the evaluation of any milestone relationships
    which are attached to the updated content.

    Arguments:
        kwargs (dict): Contains user ID, course key, and content usage key

    Returns:
        None
    """
    course = modulestore().get_course(CourseKey.from_string(kwargs.get('course_id')))
    block = modulestore().get_item(UsageKey.from_string(kwargs.get('usage_id')))
    gating_api.evaluate_prerequisite(course, block, kwargs.get('user_id'))
    gating_api.evaluate_entrance_exam(course, block, kwargs.get('user_id'))
Beispiel #19
0
    def test_content_unlocked(self):
        """
        Test that a sequential/subsection with unmet prereqs correctly indicated that its content is locked
        """
        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_percentage',
                   Mock(return_value=100)):
            lms_gating_api.evaluate_prerequisite(
                self.course,
                Mock(location=self.course_blocks['prerequisite'].location),
                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 present
        unlock_icon = response_content('.fa-unlock')
        self.assertTrue(unlock_icon,
                        "unlock icon is not present, but should be")

        subsection = unlock_icon.parents('.subsection-title')

        # check that subsection-title-name is the display name of gated content section
        gated_subsection_title = self.course_blocks[
            'gated_content'].display_name
        self.assertIn(gated_subsection_title,
                      subsection.children('.subsection-title-name').html())

        # check that it doesn't say prerequisite required
        self.assertNotIn(self.PREREQ_REQUIRED,
                         subsection.children('.subsection-title-name').html())

        # check that there is a screen reader message
        self.assertTrue(subsection.children('.sr'))

        # check that the screen reader message is correct
        self.assertIn(self.UNLOCKED, subsection.children('.sr').html())
    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)
Beispiel #21
0
def handle_subsection_score_changed(**kwargs):
    """
    Receives the SUBSECTION_SCORE_CHANGED signal and triggers the evaluation of
    any milestone relationships which are attached to the updated content.
    Arguments:
        kwargs (dict): Contains user ID, course key, and content usage key
    Returns:
        None
    """
    course = kwargs['course']
    if course.enable_subsection_gating:
        subsection_grade = kwargs['subsection_grade']
        new_score = subsection_grade.graded_total.earned / subsection_grade.graded_total.possible * 100.0
        gating_api.evaluate_prerequisite(
            course,
            kwargs['user'],
            subsection_grade.location,
            new_score,
        )
    def test_content_unlocked(self):
        """
        Test that a sequential/subsection with unmet prereqs correctly indicated that its content is locked
        """
        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_percentage', Mock(return_value=100)):
            lms_gating_api.evaluate_prerequisite(
                self.course,
                Mock(location=self.course_blocks['prerequisite'].location),
                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 present
        unlock_icon = response_content('.fa-unlock')
        self.assertTrue(unlock_icon, "unlock icon is not present, but should be")

        subsection = unlock_icon.parents('.subsection-title')

        # check that subsection-title-name is the display name of gated content section
        gated_subsection_title = self.course_blocks['gated_content'].display_name
        self.assertIn(gated_subsection_title, subsection.children('.subsection-title-name').html())

        # check that it doesn't say prerequisite required
        self.assertNotIn(self.PREREQ_REQUIRED, subsection.children('.subsection-title-name').html())

        # check that there is a screen reader message
        self.assertTrue(subsection.children('.sr'))

        # check that the screen reader message is correct
        self.assertIn(self.UNLOCKED, subsection.children('.sr').html())
Beispiel #23
0
def handle_subsection_score_updated(**kwargs):
    """
    Receives the SCORE_CHANGED signal sent by LMS when a student's score has changed
    for a given component and triggers the evaluation of any milestone relationships
    which are attached to the updated content.

    Arguments:
        kwargs (dict): Contains user ID, course key, and content usage key

    Returns:
        None
    """
    course = kwargs['course']
    if course.enable_subsection_gating:
        subsection_grade = kwargs['subsection_grade']
        new_score = subsection_grade.graded_total.earned / subsection_grade.graded_total.possible * 100.0
        gating_api.evaluate_prerequisite(
            course,
            kwargs['user'],
            subsection_grade.location,
            new_score,
        )
Beispiel #24
0
    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_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_percentage', Mock(return_value=100)):
            lms_gating_api.evaluate_prerequisite(
                self.course,
                Mock(location=self.course_blocks['prerequisite'].location),
                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)
Beispiel #26
0
    def test_no_prerequisites(self, mock_module_score):
        """ Test test_no_prerequisites """

        evaluate_prerequisite(self.course, self.prob1.location, self.user.id)
        self.assertFalse(mock_module_score.called)
Beispiel #27
0
    def test_orphaned_xblock(self, mock_module_score):
        """ Test test_orphaned_xblock """

        evaluate_prerequisite(self.course, self.prob2.location, self.user.id)
        self.assertFalse(mock_module_score.called)
Beispiel #28
0
    def test_orphaned_xblock(self, mock_module_score):
        """ Test test_orphaned_xblock """

        evaluate_prerequisite(self.course, self.prob2.location, self.user.id)
        self.assertFalse(mock_module_score.called)
Beispiel #29
0
    def test_no_prerequisites(self, mock_module_score):
        """ Test test_no_prerequisites """

        evaluate_prerequisite(self.course, self.prob1.location, self.user.id)
        self.assertFalse(mock_module_score.called)
Beispiel #30
0
    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)
        self.assertFalse(mock_score.called)
Beispiel #31
0
 def test_no_prerequisites(self, mock_score):
     evaluate_prerequisite(self.course, self.subsection_grade, self.user)
     self.assertFalse(mock_score.called)