def get_required_content(course, user):
    """
    Queries milestones subsystem to see if the specified course is gated on one or more milestones,
    and if those milestones can be fulfilled via completion of a particular course content module
    """
    required_content = []
    if settings.FEATURES.get('MILESTONES_APP', False):
        # Get all of the outstanding milestones for this course, for this user
        try:
            milestone_paths = get_course_milestones_fulfillment_paths(
                unicode(course.id),
                serialize_user(user)
            )
        except InvalidMilestoneRelationshipTypeException:
            return required_content

        # For each outstanding milestone, see if this content is one of its fulfillment paths
        for path_key in milestone_paths:
            milestone_path = milestone_paths[path_key]
            if milestone_path.get('content') and len(milestone_path['content']):
                for content in milestone_path['content']:
                    required_content.append(content)

    #local imports to avoid circular reference
    from student.models import EntranceExamConfiguration
    can_skip_entrance_exam = EntranceExamConfiguration.user_can_skip_entrance_exam(user, course.id)
    # check if required_content has any entrance exam and user is allowed to skip it
    # then remove it from required content
    if required_content and getattr(course, 'entrance_exam_enabled', False) and can_skip_entrance_exam:
        descriptors = [modulestore().get_item(UsageKey.from_string(content)) for content in required_content]
        entrance_exam_contents = [unicode(descriptor.location)
                                  for descriptor in descriptors if descriptor.is_entrance_exam]
        required_content = list(set(required_content) - set(entrance_exam_contents))
    return required_content
def get_pre_requisite_courses_not_completed(user, enrolled_courses):
    """
    It would make dict of prerequisite courses not completed by user among courses
    user has enrolled in. It calls the fulfilment api of milestones app and
    iterates over all fulfilment milestones not achieved to make dict of
    prerequisite courses yet to be completed.
    """
    pre_requisite_courses = {}
    if settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES'):
        for course_key in enrolled_courses:
            required_courses = []
            fulfilment_paths = get_course_milestones_fulfillment_paths(course_key, {'id': user.id})
            for milestone_key, milestone_value in fulfilment_paths.items():  # pylint: disable=unused-variable
                for key, value in milestone_value.items():
                    if key == 'courses' and value:
                        for required_course in value:
                            required_course_key = CourseKey.from_string(required_course)
                            required_course_descriptor = modulestore().get_course(required_course_key)
                            required_courses.append({
                                'key': required_course_key,
                                'display': get_course_display_name(required_course_descriptor)
                            })

            # if there are required courses add to dict
            if required_courses:
                pre_requisite_courses[course_key] = {'courses': required_courses}
    return pre_requisite_courses
def get_course_milestones_fulfillment_paths(course_id, user_id):
    """
    Client API operation adapter/wrapper
    """
    if not settings.FEATURES.get('MILESTONES_APP', False):
        return None
    from milestones import api as milestones_api
    return milestones_api.get_course_milestones_fulfillment_paths(
        course_id, user_id)
Beispiel #4
0
def get_course_milestones_fulfillment_paths(course_id, user_id):
    """
    Client API operation adapter/wrapper
    """
    if not ENABLE_MILESTONES_APP.is_enabled():
        return None
    return milestones_api.get_course_milestones_fulfillment_paths(
        course_id,
        user_id
    )
def any_unfulfilled_milestones(course_id, user_id):
    """ Returns a boolean if user has any unfulfilled milestones """
    if not settings.FEATURES.get('MILESTONES_APP'):
        return False

    fulfillment_paths = milestones_api.get_course_milestones_fulfillment_paths(course_id, {'id': user_id})

    # Returns True if any of the milestones is unfulfilled. False if
    # values is empty or all values are.
    return any(fulfillment_paths.values())
def get_course_milestones_fulfillment_paths(course_id, user_id):
    """
    Client API operation adapter/wrapper
    """
    if not settings.FEATURES.get('MILESTONES_APP'):
        return None
    return milestones_api.get_course_milestones_fulfillment_paths(
        course_id,
        user_id
    )
def any_unfulfilled_milestones(course_id, user_id):
    """ Returns a boolean if user has any unfulfilled milestones """
    if not settings.FEATURES.get('MILESTONES_APP'):
        return False

    fulfillment_paths = milestones_api.get_course_milestones_fulfillment_paths(course_id, {'id': user_id})

    # Returns True if any of the milestones is unfulfilled. False if
    # values is empty or all values are.
    return any(fulfillment_paths.values())
Beispiel #8
0
def any_unfulfilled_milestones(course_id, user_id):
    """ Returns a boolean if user has any unfulfilled milestones """
    if not ENABLE_MILESTONES_APP.is_enabled():
        return False

    user_id = None if user_id is None else int(user_id)
    fulfillment_paths = milestones_api.get_course_milestones_fulfillment_paths(course_id, {'id': user_id})

    # Returns True if any of the milestones is unfulfilled. False if
    # values is empty or all values are.
    return any(fulfillment_paths.values())
Beispiel #9
0
def get_pre_requisite_courses_not_completed(user, enrolled_courses):  # pylint: disable=invalid-name
    """
    Makes a dict mapping courses to their unfulfilled milestones using the
    fulfillment API of the milestones app.

    Arguments:
        user (User): the user for whom we are checking prerequisites.
        enrolled_courses (CourseKey): a list of keys for the courses to be
            checked. The given user must be enrolled in all of these courses.

    Returns:
        dict[CourseKey: dict[
            'courses': list[dict['key': CourseKey, 'display': str]]
        ]]
        If a course has no incomplete prerequisites, it will be excluded from the
        dictionary.
    """
    if not settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False):
        return {}

    from milestones import api as milestones_api
    pre_requisite_courses = {}

    for course_key in enrolled_courses:
        required_courses = []
        fulfillment_paths = milestones_api.get_course_milestones_fulfillment_paths(
            course_key, {'id': user.id})
        for __, milestone_value in fulfillment_paths.items():
            for key, value in milestone_value.items():
                if key == 'courses' and value:
                    for required_course in value:
                        required_course_key = CourseKey.from_string(
                            required_course)
                        required_course_overview = CourseOverview.get_from_id(
                            required_course_key)
                        required_courses.append({
                            'key':
                            required_course_key,
                            'display':
                            get_course_display_string(required_course_overview)
                        })
        # If there are required courses, add them to the result dict.
        if required_courses:
            pre_requisite_courses[course_key] = {'courses': required_courses}

    return pre_requisite_courses
Beispiel #10
0
def get_course_tab_list(course, user):
    """
    Retrieves the course tab list from xmodule.tabs and manipulates the set as necessary
    """
    user_is_enrolled = user.is_authenticated() and CourseEnrollment.is_enrolled(user, course.id)
    xmodule_tab_list = CourseTabList.iterate_displayable(
        course,
        settings,
        user.is_authenticated(),
        has_access(user, 'staff', course, course.id),
        user_is_enrolled
    )

    # Entrance Exams Feature
    # If the course has an entrance exam, we'll need to see if the user has not passed it
    # If so, we'll need to hide away all of the tabs except for Courseware and Instructor
    entrance_exam_mode = False
    if settings.FEATURES.get('ENTRANCE_EXAMS', False):
        if getattr(course, 'entrance_exam_enabled', False):
            course_milestones_paths = get_course_milestones_fulfillment_paths(
                unicode(course.id),
                serialize_user(user)
            )
            for __, value in course_milestones_paths.iteritems():
                if len(value.get('content', [])):
                    for content in value['content']:
                        if content == course.entrance_exam_id \
                                and not EntranceExamConfiguration.user_can_skip_entrance_exam(user, course.id):
                            entrance_exam_mode = True
                            break

    # Now that we've loaded the tabs for this course, perform the Entrance Exam mode work
    # Majority case is no entrance exam defined
    course_tab_list = []
    for tab in xmodule_tab_list:
        if entrance_exam_mode:
            # Hide all of the tabs except for 'Courseware' and 'Instructor'
            # Rename 'Courseware' tab to 'Entrance Exam'
            if tab.type not in ['courseware', 'instructor']:
                continue
            if tab.type == 'courseware':
                tab.name = _("Entrance Exam")
        course_tab_list.append(tab)
    return course_tab_list
def get_required_content(course, user):
    """
    Queries milestones subsystem to see if the specified course is gated on one or more milestones,
    and if those milestones can be fulfilled via completion of a particular course content module
    """
    required_content = []
    if settings.FEATURES.get('MILESTONES_APP', False):
        from milestones import api as milestones_api
        from milestones.exceptions import InvalidMilestoneRelationshipTypeException

        # Get all of the outstanding milestones for this course, for this user
        try:
            milestone_paths = milestones_api.get_course_milestones_fulfillment_paths(
                unicode(course.id), serialize_user(user))
        except InvalidMilestoneRelationshipTypeException:
            return required_content

        # For each outstanding milestone, see if this content is one of its fulfillment paths
        for path_key in milestone_paths:
            milestone_path = milestone_paths[path_key]
            if milestone_path.get('content') and len(
                    milestone_path['content']):
                for content in milestone_path['content']:
                    required_content.append(content)

    #local imports to avoid circular reference
    from student.models import EntranceExamConfiguration
    can_skip_entrance_exam = EntranceExamConfiguration.user_can_skip_entrance_exam(
        user, course.id)
    # check if required_content has any entrance exam and user is allowed to skip it
    # then remove it from required content
    if required_content and getattr(course, 'entrance_exam_enabled',
                                    False) and can_skip_entrance_exam:
        descriptors = [
            modulestore().get_item(UsageKey.from_string(content))
            for content in required_content
        ]
        entrance_exam_contents = [
            unicode(descriptor.location) for descriptor in descriptors
            if descriptor.is_entrance_exam
        ]
        required_content = list(
            set(required_content) - set(entrance_exam_contents))
    return required_content
Beispiel #12
0
        def test_contentstore_views_entrance_exam_delete(self):
            """
            Unit Test: test_contentstore_views_entrance_exam_delete
            """
            resp = self.client.post(self.exam_url, {},
                                    http_accept='application/json')
            self.assertEqual(resp.status_code, 201)
            resp = self.client.get(self.exam_url)
            self.assertEqual(resp.status_code, 200)
            resp = self.client.delete(self.exam_url)
            self.assertEqual(resp.status_code, 204)
            resp = self.client.get(self.exam_url)
            self.assertEqual(resp.status_code, 404)

            user = User.objects.create(
                username='******',
                email='*****@*****.**',
                is_active=True,
            )
            user.set_password('test')
            user.save()
            milestones = milestones_api.get_course_milestones(
                unicode(self.course_key))
            self.assertEqual(len(milestones), 1)
            milestone_key = '{}.{}'.format(milestones[0]['namespace'],
                                           milestones[0]['name'])
            paths = milestones_api.get_course_milestones_fulfillment_paths(
                unicode(self.course_key), serialize_user(user))

            # What we have now is a course milestone requirement and no valid fulfillment
            # paths for the specified user.  The LMS is going to have to ignore this situation,
            # because we can't confidently prevent it from occuring at some point in the future.
            # milestone_key_1 =
            self.assertEqual(len(paths[milestone_key]), 0)

            # Re-adding an entrance exam to the course should fix the missing link
            # It wipes out any old entrance exam artifacts and inserts a new exam course chapter/module
            resp = self.client.post(self.exam_url, {},
                                    http_accept='application/json')
            self.assertEqual(resp.status_code, 201)
            resp = self.client.get(self.exam_url)
            self.assertEqual(resp.status_code, 200)
def get_pre_requisite_courses_not_completed(user, enrolled_courses):  # pylint: disable=invalid-name
    """
    Makes a dict mapping courses to their unfulfilled milestones using the
    fulfillment API of the milestones app.

    Arguments:
        user (User): the user for whom we are checking prerequisites.
        enrolled_courses (CourseKey): a list of keys for the courses to be
            checked. The given user must be enrolled in all of these courses.

    Returns:
        dict[CourseKey: dict[
            'courses': list[dict['key': CourseKey, 'display': str]]
        ]]
        If a course has no incomplete prerequisites, it will be excluded from the
        dictionary.
    """
    if not is_prerequisite_courses_enabled():
        return {}

    from milestones import api as milestones_api
    pre_requisite_courses = {}

    for course_key in enrolled_courses:
        required_courses = []
        fulfillment_paths = milestones_api.get_course_milestones_fulfillment_paths(course_key, {'id': user.id})
        for __, milestone_value in fulfillment_paths.items():
            for key, value in milestone_value.items():
                if key == 'courses' and value:
                    for required_course in value:
                        required_course_key = CourseKey.from_string(required_course)
                        required_course_overview = CourseOverview.get_from_id(required_course_key)
                        required_courses.append({
                            'key': required_course_key,
                            'display': get_course_display_string(required_course_overview)
                        })
        # If there are required courses, add them to the result dict.
        if required_courses:
            pre_requisite_courses[course_key] = {'courses': required_courses}

    return pre_requisite_courses
        def test_contentstore_views_entrance_exam_delete(self):
            """
            Unit Test: test_contentstore_views_entrance_exam_delete
            """
            resp = self.client.post(self.exam_url, {}, http_accept='application/json')
            self.assertEqual(resp.status_code, 201)
            resp = self.client.get(self.exam_url)
            self.assertEqual(resp.status_code, 200)
            resp = self.client.delete(self.exam_url)
            self.assertEqual(resp.status_code, 204)
            resp = self.client.get(self.exam_url)
            self.assertEqual(resp.status_code, 404)

            user = User.objects.create(
                username='******',
                email='*****@*****.**',
                is_active=True,
            )
            user.set_password('test')
            user.save()
            milestones = milestones_api.get_course_milestones(unicode(self.course_key))
            self.assertEqual(len(milestones), 1)
            milestone_key = '{}.{}'.format(milestones[0]['namespace'], milestones[0]['name'])
            paths = milestones_api.get_course_milestones_fulfillment_paths(
                unicode(self.course_key),
                serialize_user(user)
            )

            # What we have now is a course milestone requirement and no valid fulfillment
            # paths for the specified user.  The LMS is going to have to ignore this situation,
            # because we can't confidently prevent it from occuring at some point in the future.
            # milestone_key_1 =
            self.assertEqual(len(paths[milestone_key]), 0)

            # Re-adding an entrance exam to the course should fix the missing link
            # It wipes out any old entrance exam artifacts and inserts a new exam course chapter/module
            resp = self.client.post(self.exam_url, {}, http_accept='application/json')
            self.assertEqual(resp.status_code, 201)
            resp = self.client.get(self.exam_url)
            self.assertEqual(resp.status_code, 200)
Beispiel #15
0
def _get_required_content(course, user):
    """
    Queries milestones subsystem to see if the specified course is gated on one or more milestones,
    and if those milestones can be fulfilled via completion of a particular course content module
    """
    required_content = []
    if settings.FEATURES.get("MILESTONES_APP", False):
        # Get all of the outstanding milestones for this course, for this user
        try:
            milestone_paths = milestones_api.get_course_milestones_fulfillment_paths(
                unicode(course.id), serialize_user(user)
            )
        except InvalidMilestoneRelationshipTypeException:
            return required_content

        # For each outstanding milestone, see if this content is one of its fulfillment paths
        for path_key in milestone_paths:
            milestone_path = milestone_paths[path_key]
            if milestone_path.get("content") and len(milestone_path["content"]):
                for content in milestone_path["content"]:
                    required_content.append(content)
    return required_content
Beispiel #16
0
def _get_required_content(course, user):
    """
    Queries milestones subsystem to see if the specified course is gated on one or more milestones,
    and if those milestones can be fulfilled via completion of a particular course content module
    """
    required_content = []
    if settings.FEATURES.get('MILESTONES_APP', False):
        # Get all of the outstanding milestones for this course, for this user
        try:
            milestone_paths = milestones_api.get_course_milestones_fulfillment_paths(
                unicode(course.id), serialize_user(user))
        except InvalidMilestoneRelationshipTypeException:
            return required_content

        # For each outstanding milestone, see if this content is one of its fulfillment paths
        for path_key in milestone_paths:
            milestone_path = milestone_paths[path_key]
            if milestone_path.get('content') and len(
                    milestone_path['content']):
                for content in milestone_path['content']:
                    required_content.append(content)
    return required_content
def get_pre_requisite_courses_not_completed(user, enrolled_courses):
    """
    It would make dict of prerequisite courses not completed by user among courses
    user has enrolled in. It calls the fulfilment api of milestones app and
    iterates over all fulfilment milestones not achieved to make dict of
    prerequisite courses yet to be completed.
    """
    pre_requisite_courses = {}
    if settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False):
        from milestones import api as milestones_api
        for course_key in enrolled_courses:
            required_courses = []
            fulfilment_paths = milestones_api.get_course_milestones_fulfillment_paths(
                course_key, {'id': user.id})
            for milestone_key, milestone_value in fulfilment_paths.items():  # pylint: disable=unused-variable
                for key, value in milestone_value.items():
                    if key == 'courses' and value:
                        for required_course in value:
                            required_course_key = CourseKey.from_string(
                                required_course)
                            required_course_descriptor = modulestore(
                            ).get_course(required_course_key)
                            required_courses.append({
                                'key':
                                required_course_key,
                                'display':
                                get_course_display_name(
                                    required_course_descriptor)
                            })

            # if there are required courses add to dict
            if required_courses:
                pre_requisite_courses[course_key] = {
                    'courses': required_courses
                }
    return pre_requisite_courses
Beispiel #18
0
    def test_milestones_fulfillment_paths_contains_special_characters(self):
        """
        Unit Test: test_get_course_milestones_fulfillment_paths works correctly when milestone have some special
        characters.
        """
        namespace = six.text_type(self.test_course_key)
        name = '�ťÉśt_Àübùr�'
        local_milestone_1 = api.add_milestone({
            'display_name': 'Local Milestone 1',
            'name': name,
            'namespace': namespace,
            'description': 'Local Milestone 1 Description'
        })

        # Specify the milestone requirements
        api.add_course_milestone(
            self.test_course_key,
            self.relationship_types['REQUIRES'],
            local_milestone_1
        )

        # Specify the milestone fulfillments (via course and content)
        api.add_course_milestone(
            self.test_prerequisite_course_key,
            self.relationship_types['FULFILLS'],
            local_milestone_1
        )
        with self.assertNumQueries(4):
            paths = api.get_course_milestones_fulfillment_paths(
                self.test_course_key,
                self.serialized_test_user
            )

        # Set up the key values we'll use to access/assert the response
        milestone_key_1 = '{}.{}'.format(local_milestone_1['namespace'], local_milestone_1['name'])
        self.assertEqual(len(paths[milestone_key_1]['courses']), 1)
Beispiel #19
0
    def test_get_course_milestones_fulfillment_paths(self):  # pylint: disable=too-many-statements
        """
        Unit Test: test_get_course_milestones_fulfillment_paths
        """
        # Create three milestones in order tto cover all logical branches
        namespace = unicode(self.test_course_key)
        local_milestone_1 = api.add_milestone({
            'display_name':
            'Local Milestone 1',
            'name':
            'local_milestone_1',
            'namespace':
            namespace,
            'description':
            'Local Milestone 1 Description'
        })
        local_milestone_2 = api.add_milestone({
            'display_name':
            'Local Milestone 2',
            'name':
            'local_milestone_2',
            'namespace':
            namespace,
            'description':
            'Local Milestone 2 Description'
        })
        local_milestone_3 = api.add_milestone({
            'display_name':
            'Local Milestone 3',
            'name':
            'local_milestone_3',
            'namespace':
            namespace,
            'description':
            'Local Milestone 3 Description'
        })

        # Specify the milestone requirements
        api.add_course_milestone(self.test_course_key,
                                 self.relationship_types['REQUIRES'],
                                 local_milestone_1)
        api.add_course_milestone(self.test_course_key,
                                 self.relationship_types['REQUIRES'],
                                 local_milestone_2)
        api.add_course_milestone(self.test_course_key,
                                 self.relationship_types['REQUIRES'],
                                 local_milestone_3)

        # Specify the milestone fulfillments (via course and content)
        api.add_course_milestone(self.test_prerequisite_course_key,
                                 self.relationship_types['FULFILLS'],
                                 local_milestone_1)
        api.add_course_milestone(self.test_prerequisite_course_key,
                                 self.relationship_types['FULFILLS'],
                                 local_milestone_2)
        api.add_course_content_milestone(
            self.test_course_key,
            UsageKey.from_string('i4x://the/content/key/123456789'),
            self.relationship_types['FULFILLS'], local_milestone_2)
        api.add_course_content_milestone(
            self.test_course_key,
            UsageKey.from_string('i4x://the/content/key/123456789'),
            self.relationship_types['FULFILLS'], local_milestone_3)
        api.add_course_content_milestone(
            self.test_course_key,
            UsageKey.from_string('i4x://the/content/key/987654321'),
            self.relationship_types['FULFILLS'], local_milestone_3)

        # Confirm the starting state for this test (user has no milestones, course requires three)
        self.assertEqual(
            len(
                api.get_user_milestones(self.serialized_test_user,
                                        namespace=namespace)), 0)
        self.assertEqual(
            len(
                api.get_course_required_milestones(self.test_course_key,
                                                   self.serialized_test_user)),
            3)
        # Check the possible fulfillment paths for the milestones for this course
        with self.assertNumQueries(8):
            paths = api.get_course_milestones_fulfillment_paths(
                self.test_course_key, self.serialized_test_user)

        # Set up the key values we'll use to access/assert the response
        milestone_key_1 = '{}.{}'.format(local_milestone_1['namespace'],
                                         local_milestone_1['name'])
        milestone_key_2 = '{}.{}'.format(local_milestone_2['namespace'],
                                         local_milestone_2['name'])
        milestone_key_3 = '{}.{}'.format(local_milestone_3['namespace'],
                                         local_milestone_3['name'])

        # First round of assertions
        self.assertEqual(len(paths[milestone_key_1]['courses']), 1)
        self.assertIsNone(paths[milestone_key_1].get('content'))
        self.assertEqual(len(paths[milestone_key_2]['courses']), 1)
        self.assertEqual(len(paths[milestone_key_2]['content']), 1)
        self.assertIsNone(paths[milestone_key_3].get('courses'))
        self.assertEqual(len(paths[milestone_key_3]['content']), 2)

        # Collect the first milestone (two should remain)
        api.add_user_milestone(self.serialized_test_user, local_milestone_1)
        self.assertEqual(
            len(
                api.get_user_milestones(self.serialized_test_user,
                                        namespace=namespace)), 1)
        self.assertEqual(
            len(
                api.get_course_required_milestones(self.test_course_key,
                                                   self.serialized_test_user)),
            2)
        # Check the remaining fulfillment paths for the milestones for this course
        with self.assertNumQueries(6):
            paths = api.get_course_milestones_fulfillment_paths(
                self.test_course_key, self.serialized_test_user)
        self.assertIsNone(paths.get(milestone_key_1))
        self.assertEqual(len(paths[milestone_key_2]['courses']), 1)
        self.assertEqual(len(paths[milestone_key_2]['content']), 1)
        self.assertIsNone(paths[milestone_key_3].get('courses'))
        self.assertEqual(len(paths[milestone_key_3]['content']), 2)

        # Collect the second milestone (one should remain)
        api.add_user_milestone(self.serialized_test_user, local_milestone_2)
        self.assertEqual(
            len(
                api.get_user_milestones(self.serialized_test_user,
                                        namespace=namespace)), 2)
        self.assertEqual(
            len(
                api.get_course_required_milestones(self.test_course_key,
                                                   self.serialized_test_user)),
            1)
        # Check the remaining fulfillment paths for the milestones for this course
        with self.assertNumQueries(4):
            paths = api.get_course_milestones_fulfillment_paths(
                self.test_course_key, self.serialized_test_user)
        self.assertIsNone(paths.get(milestone_key_1))
        self.assertIsNone(paths.get(milestone_key_2))
        self.assertIsNone(paths[milestone_key_3].get('courses'))
        self.assertEqual(len(paths[milestone_key_3]['content']), 2)

        # Collect the third milestone
        api.add_user_milestone(self.serialized_test_user, local_milestone_3)
        self.assertEqual(
            len(
                api.get_user_milestones(self.serialized_test_user,
                                        namespace=namespace)), 3)
        self.assertEqual(
            len(
                api.get_course_required_milestones(self.test_course_key,
                                                   self.serialized_test_user)),
            0)
        # Check the remaining fulfillment paths for the milestones for this course
        with self.assertNumQueries(2):
            paths = api.get_course_milestones_fulfillment_paths(
                self.test_course_key, self.serialized_test_user)
        self.assertIsNone(paths.get(milestone_key_1))
        self.assertIsNone(paths.get(milestone_key_2))
        self.assertIsNone(paths.get(milestone_key_3))
Beispiel #20
0
    def test_get_course_milestones_fulfillment_paths(self):  # pylint: disable=too-many-statements
        """
        Unit Test: test_get_course_milestones_fulfillment_paths
        """
        # Create three milestones in order tto cover all logical branches
        namespace = six.text_type(self.test_course_key)
        local_milestone_1 = api.add_milestone({
            'display_name': 'Local Milestone 1',
            'name': 'local_milestone_1',
            'namespace': namespace,
            'description': 'Local Milestone 1 Description'
        })
        local_milestone_2 = api.add_milestone({
            'display_name': 'Local Milestone 2',
            'name': 'local_milestone_2',
            'namespace': namespace,
            'description': 'Local Milestone 2 Description'
        })
        local_milestone_3 = api.add_milestone({
            'display_name': 'Local Milestone 3',
            'name': 'local_milestone_3',
            'namespace': namespace,
            'description': 'Local Milestone 3 Description'
        })

        # Specify the milestone requirements
        api.add_course_milestone(
            self.test_course_key,
            self.relationship_types['REQUIRES'],
            local_milestone_1
        )
        api.add_course_milestone(
            self.test_course_key,
            self.relationship_types['REQUIRES'],
            local_milestone_2
        )
        api.add_course_milestone(
            self.test_course_key,
            self.relationship_types['REQUIRES'],
            local_milestone_3
        )

        # Specify the milestone fulfillments (via course and content)
        api.add_course_milestone(
            self.test_prerequisite_course_key,
            self.relationship_types['FULFILLS'],
            local_milestone_1
        )
        api.add_course_milestone(
            self.test_prerequisite_course_key,
            self.relationship_types['FULFILLS'],
            local_milestone_2
        )
        api.add_course_content_milestone(
            self.test_course_key,
            UsageKey.from_string('i4x://the/content/key/123456789'),
            self.relationship_types['FULFILLS'],
            local_milestone_2
        )
        api.add_course_content_milestone(
            self.test_course_key,
            UsageKey.from_string('i4x://the/content/key/123456789'),
            self.relationship_types['FULFILLS'],
            local_milestone_3
        )
        api.add_course_content_milestone(
            self.test_course_key,
            UsageKey.from_string('i4x://the/content/key/987654321'),
            self.relationship_types['FULFILLS'],
            local_milestone_3
        )

        # Confirm the starting state for this test (user has no milestones, course requires three)
        self.assertEqual(
            len(api.get_user_milestones(self.serialized_test_user, namespace=namespace)), 0)
        self.assertEqual(
            len(api.get_course_required_milestones(self.test_course_key, self.serialized_test_user)),
            3
        )
        # Check the possible fulfillment paths for the milestones for this course
        with self.assertNumQueries(8):
            paths = api.get_course_milestones_fulfillment_paths(
                self.test_course_key,
                self.serialized_test_user
            )

        # Set up the key values we'll use to access/assert the response
        milestone_key_1 = '{}.{}'.format(local_milestone_1['namespace'], local_milestone_1['name'])
        milestone_key_2 = '{}.{}'.format(local_milestone_2['namespace'], local_milestone_2['name'])
        milestone_key_3 = '{}.{}'.format(local_milestone_3['namespace'], local_milestone_3['name'])

        # First round of assertions
        self.assertEqual(len(paths[milestone_key_1]['courses']), 1)
        self.assertIsNone(paths[milestone_key_1].get('content'))
        self.assertEqual(len(paths[milestone_key_2]['courses']), 1)
        self.assertEqual(len(paths[milestone_key_2]['content']), 1)
        self.assertIsNone(paths[milestone_key_3].get('courses'))
        self.assertEqual(len(paths[milestone_key_3]['content']), 2)

        # Collect the first milestone (two should remain)
        api.add_user_milestone(self.serialized_test_user, local_milestone_1)
        self.assertEqual(
            len(api.get_user_milestones(self.serialized_test_user, namespace=namespace)), 1)
        self.assertEqual(
            len(api.get_course_required_milestones(self.test_course_key, self.serialized_test_user)),
            2
        )
        # Check the remaining fulfillment paths for the milestones for this course
        with self.assertNumQueries(6):
            paths = api.get_course_milestones_fulfillment_paths(
                self.test_course_key,
                self.serialized_test_user
            )
        self.assertIsNone(paths.get(milestone_key_1))
        self.assertEqual(len(paths[milestone_key_2]['courses']), 1)
        self.assertEqual(len(paths[milestone_key_2]['content']), 1)
        self.assertIsNone(paths[milestone_key_3].get('courses'))
        self.assertEqual(len(paths[milestone_key_3]['content']), 2)

        # Collect the second milestone (one should remain)
        api.add_user_milestone(self.serialized_test_user, local_milestone_2)
        self.assertEqual(
            len(api.get_user_milestones(self.serialized_test_user, namespace=namespace)), 2)
        self.assertEqual(
            len(api.get_course_required_milestones(self.test_course_key, self.serialized_test_user)),
            1
        )
        # Check the remaining fulfillment paths for the milestones for this course
        with self.assertNumQueries(4):
            paths = api.get_course_milestones_fulfillment_paths(
                self.test_course_key,
                self.serialized_test_user
            )
        self.assertIsNone(paths.get(milestone_key_1))
        self.assertIsNone(paths.get(milestone_key_2))
        self.assertIsNone(paths[milestone_key_3].get('courses'))
        self.assertEqual(len(paths[milestone_key_3]['content']), 2)

        # Collect the third milestone
        api.add_user_milestone(self.serialized_test_user, local_milestone_3)
        self.assertEqual(
            len(api.get_user_milestones(self.serialized_test_user, namespace=namespace)), 3)
        self.assertEqual(
            len(api.get_course_required_milestones(self.test_course_key, self.serialized_test_user)),
            0
        )
        # Check the remaining fulfillment paths for the milestones for this course
        with self.assertNumQueries(2):
            paths = api.get_course_milestones_fulfillment_paths(
                self.test_course_key,
                self.serialized_test_user
            )
        self.assertIsNone(paths.get(milestone_key_1))
        self.assertIsNone(paths.get(milestone_key_2))
        self.assertIsNone(paths.get(milestone_key_3))