From 3b31270e07f2c324ce01d910f004edd540a1a646 Mon Sep 17 00:00:00 2001 From: Robert Raposa <rraposa@edx.org> Date: Tue, 25 Apr 2017 14:23:38 -0400 Subject: [PATCH] Fix Start Course vs Resume Course using Course Blocks. --- common/lib/xmodule/xmodule/modulestore/tests/django_utils.py | 1 - lms/djangoapps/course_blocks/transformers/library_content.py | 31 +++---------------------------- lms/djangoapps/course_blocks/utils.py | 32 ++++++++++++++++++++++++++++++++ lms/djangoapps/courseware/courses.py | 25 ------------------------- lms/djangoapps/courseware/views/views.py | 27 ++++++++++++++++++++++++--- openedx/features/course_experience/templates/course_experience/course-home-fragment.html | 20 +++++++++++--------- openedx/features/course_experience/templates/course_experience/course-outline-fragment.html | 6 +++--- openedx/features/course_experience/tests/views/test_course_outline.py | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------- openedx/features/course_experience/utils.py | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ openedx/features/course_experience/views/course_home.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++------- openedx/features/course_experience/views/course_outline.py | 42 +++++++----------------------------------- 11 files changed, 277 insertions(+), 126 deletions(-) create mode 100644 lms/djangoapps/course_blocks/utils.py create mode 100644 openedx/features/course_experience/utils.py diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 870e342..1a76094 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -23,7 +23,6 @@ from xmodule.modulestore.django import modulestore, clear_existing_modulestores, from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST from xmodule.modulestore.tests.factories import XMODULE_FACTORY_LOCK -from openedx.core.djangoapps.bookmarks.signals import trigger_update_xblocks_cache_task from openedx.core.djangolib.testing.utils import CacheIsolationMixin, CacheIsolationTestCase diff --git a/lms/djangoapps/course_blocks/transformers/library_content.py b/lms/djangoapps/course_blocks/transformers/library_content.py index 44f7958..9743fc2 100644 --- a/lms/djangoapps/course_blocks/transformers/library_content.py +++ b/lms/djangoapps/course_blocks/transformers/library_content.py @@ -12,6 +12,8 @@ from xmodule.modulestore.django import modulestore from eventtracking import tracker from track import contexts +from ..utils import get_student_module_as_dict + class ContentLibraryTransformer(FilteringTransformerMixin, BlockStructureTransformer): """ @@ -78,12 +80,7 @@ class ContentLibraryTransformer(FilteringTransformerMixin, BlockStructureTransfo max_count = block_structure.get_xblock_field(block_key, 'max_count') # Retrieve "selected" json from LMS MySQL database. - module = self._get_student_module(usage_info.user, usage_info.course_key, block_key) - if module: - state_dict = json.loads(module.state) - else: - state_dict = {} - + state_dict = get_student_module_as_dict(usage_info.user, usage_info.course_key, block_key) for selected_block in state_dict.get('selected', []): # Add all selected entries for this user for this # library module to the selected list. @@ -135,28 +132,6 @@ class ContentLibraryTransformer(FilteringTransformerMixin, BlockStructureTransfo return [block_structure.create_removal_filter(check_child_removal)] - @classmethod - def _get_student_module(cls, user, course_key, block_key): - """ - Get the student module for the given user for the given block. - - Arguments: - user (User) - course_key (CourseLocator) - block_key (BlockUsageLocator) - - Returns: - StudentModule if exists, or None. - """ - try: - return StudentModule.objects.get( - student=user, - course_id=course_key, - module_state_key=block_key, - ) - except StudentModule.DoesNotExist: - return None - def _publish_events(self, block_structure, location, previous_count, max_count, block_keys, user_id): """ Helper method to publish events for analytics purposes diff --git a/lms/djangoapps/course_blocks/utils.py b/lms/djangoapps/course_blocks/utils.py new file mode 100644 index 0000000..33097b7 --- /dev/null +++ b/lms/djangoapps/course_blocks/utils.py @@ -0,0 +1,32 @@ +""" +Common utilities for use along with the course blocks. +""" +import json +from courseware.models import StudentModule + + +def get_student_module_as_dict(user, course_key, block_key): + """ + Get the student module as a dict for the given user for the given block. + + Arguments: + user (User) + course_key (CourseLocator) + block_key (BlockUsageLocator) + + Returns: + StudentModule as a (possibly empty) dict. + """ + try: + student_module = StudentModule.objects.get( + student=user, + course_id=course_key, + module_state_key=block_key, + ) + except StudentModule.DoesNotExist: + student_module = None + + if student_module: + return json.loads(student_module.state) + else: + return {} diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index fb9a3da..bfbae0f 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -518,28 +518,3 @@ def get_current_child(xmodule, min_depth=None, requested_child=None): child = _get_default_child_module(children) return child - - -def get_last_accessed_courseware(course, request, user): - """ - Returns a tuple containing the courseware module (URL, id) that the user last accessed, - or (None, None) if it cannot be found. - """ - # TODO: convert this method to use the Course Blocks API - field_data_cache = FieldDataCache.cache_for_descriptor_descendents( - course.id, request.user, course, depth=2 - ) - course_module = get_module_for_descriptor( - user, request, course, field_data_cache, course.id, course=course - ) - chapter_module = get_current_child(course_module) - if chapter_module is not None: - section_module = get_current_child(chapter_module) - if section_module is not None: - url = reverse('courseware_section', kwargs={ - 'course_id': unicode(course.id), - 'chapter': chapter_module.url_name, - 'section': section_module.url_name - }) - return (url, section_module.url_name) - return (None, None) diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 5e24a54..ad0ba49 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -66,7 +66,7 @@ from courseware.courses import ( get_course_by_id, get_course_overview_with_access, get_course_with_access, - get_last_accessed_courseware, + get_current_child, get_permission_for_course_about, get_studio_url, sort_by_announcement, @@ -97,7 +97,6 @@ from openedx.features.course_experience import ( UNIFIED_COURSE_VIEW_FLAG, ) from openedx.features.enterprise_support.api import data_sharing_consent_required -from shoppingcart.models import CourseRegistrationCode from shoppingcart.utils import is_shopping_cart_enabled from student.models import UserTestGroup, CourseEnrollment from survey.utils import must_answer_survey @@ -249,6 +248,28 @@ def course_info(request, course_id): Assumes the course_id is in a valid format. """ + def get_last_accessed_courseware(course, request, user): + """ + Returns the courseware module URL that the user last accessed, or None if it cannot be found. + """ + field_data_cache = FieldDataCache.cache_for_descriptor_descendents( + course.id, request.user, course, depth=2 + ) + course_module = get_module_for_descriptor( + user, request, course, field_data_cache, course.id, course=course + ) + chapter_module = get_current_child(course_module) + if chapter_module is not None: + section_module = get_current_child(chapter_module) + if section_module is not None: + url = reverse('courseware_section', kwargs={ + 'course_id': unicode(course.id), + 'chapter': chapter_module.url_name, + 'section': section_module.url_name + }) + return url + return None + # If the unified course experience is enabled, redirect to the "Course" tab if waffle.flag_is_active(request, UNIFIED_COURSE_EXPERIENCE_FLAG): return redirect(reverse(course_home_url_name(request), args=[course_id])) @@ -343,7 +364,7 @@ def course_info(request, course_id): # Get the URL of the user's last position in order to display the 'where you were last' message context['last_accessed_courseware_url'] = None if SelfPacedConfiguration.current().enable_course_home_improvements: - context['last_accessed_courseware_url'], _ = get_last_accessed_courseware(course, request, user) + context['last_accessed_courseware_url'] = get_last_accessed_courseware(course, request, user) now = datetime.now(UTC()) effective_start = _adjust_start_date_for_beta_testers(user, course, course_key) diff --git a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html index 553662c..45ee944 100644 --- a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html +++ b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html @@ -50,17 +50,19 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG % endif <div class="form-actions"> % if not waffle.flag_is_active(request, UNIFIED_COURSE_EXPERIENCE_FLAG): - <a class="btn action-show-bookmarks" href="${reverse('openedx.course_bookmarks.home', args=[course.id])}"> + <a class="btn action-show-bookmarks" href="${reverse('openedx.course_bookmarks.home', args=[course_key])}"> ${_("Bookmarks")} </a> % endif - <a class="btn btn-brand action-resume-course" href="${reverse('courseware', kwargs={'course_id': unicode(course.id.to_deprecated_string())})}"> - % if has_visited_course: - ${_("Resume Course")} - % else: - ${_("Start Course")} - % endif - </a> + % if resume_course_url: + <a class="btn btn-brand action-resume-course" href="${resume_course_url}"> + % if has_visited_course: + ${_("Resume Course")} + % else: + ${_("Start Course")} + % endif + </a> + % endif </div> </div> </header> @@ -75,7 +77,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG <h3 class="hd-6">${_("Course Tools")}</h3> <ul class="list-unstyled"> <li> - <a class="action-show-bookmarks" href="${reverse('openedx.course_bookmarks.home', args=[course.id])}"> + <a class="action-show-bookmarks" href="${reverse('openedx.course_bookmarks.home', args=[course_key])}"> <span class="icon fa fa-bookmark" aria-hidden="true"></span> ${_("Bookmarks")} </a> diff --git a/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html b/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html index 21bfa19..269ce54 100644 --- a/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html +++ b/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html @@ -27,9 +27,9 @@ from openedx.core.djangolib.markup import HTML, Text <h3>${ section['display_name'] }</h3> </div> <ol class="outline-item focusable" role="group" tabindex="0"> - % for subsection in section.get('children') or []: + % for subsection in section.get('children', []): <li - class="subsection ${ 'current' if subsection['current'] else '' }" + class="subsection ${ 'current' if subsection['last_accessed'] else '' }" role="treeitem" tabindex="-1" aria-expanded="true" @@ -106,7 +106,7 @@ from openedx.core.djangolib.markup import HTML, Text </div> <!-- /subsection-text --> <div class="subsection-actions"> ## Resume button (if last visited section) - % if subsection['current']: + % if subsection['last_accessed']: <span class="sr-only">${ _("This is your last visited course section.") }</span> <span class="resume-right"> <b>${ _("Resume Course") }</b> diff --git a/openedx/features/course_experience/tests/views/test_course_outline.py b/openedx/features/course_experience/tests/views/test_course_outline.py index 9e8bbaf..5acc410 100644 --- a/openedx/features/course_experience/tests/views/test_course_outline.py +++ b/openedx/features/course_experience/tests/views/test_course_outline.py @@ -3,10 +3,10 @@ Tests for the Course Outline view and supporting views. """ import datetime import ddt -from mock import patch import json from django.core.urlresolvers import reverse +from pyquery import PyQuery as pq from courseware.tests.factories import StaffFactory from student.models import CourseEnrollment @@ -37,9 +37,10 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): with cls.store.bulk_operations(course.id): chapter = ItemFactory.create(category='chapter', parent_location=course.location) section = ItemFactory.create(category='sequential', parent_location=chapter.location) - ItemFactory.create(category='vertical', parent_location=section.location) - course.last_accessed = section.url_name - + vertical = ItemFactory.create(category='vertical', parent_location=section.location) + course.children = [chapter] + chapter.children = [section] + section.children = [vertical] cls.courses.append(course) course = CourseFactory.create() @@ -47,10 +48,12 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): chapter = ItemFactory.create(category='chapter', parent_location=course.location) section = ItemFactory.create(category='sequential', parent_location=chapter.location) section2 = ItemFactory.create(category='sequential', parent_location=chapter.location) - ItemFactory.create(category='vertical', parent_location=section.location) - ItemFactory.create(category='vertical', parent_location=section2.location) - course.last_accessed = None - + vertical = ItemFactory.create(category='vertical', parent_location=section.location) + vertical2 = ItemFactory.create(category='vertical', parent_location=section2.location) + course.children = [chapter] + chapter.children = [section, section2] + section.children = [vertical] + section2.children = [vertical2] cls.courses.append(course) course = CourseFactory.create() @@ -63,8 +66,10 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): graded=True, format='Homework', ) - ItemFactory.create(category='vertical', parent_location=section.location) - course.last_accessed = section.url_name + vertical = ItemFactory.create(category='vertical', parent_location=section.location) + course.children = [chapter] + chapter.children = [section] + section.children = [vertical] cls.courses.append(course) @classmethod @@ -81,26 +86,79 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): super(TestCourseOutlinePage, self).setUp() self.client.login(username=self.user.username, password=TEST_PASSWORD) - @patch('openedx.features.course_experience.views.course_outline.get_last_accessed_courseware') - def test_render(self, patched_get_last_accessed): + def test_outline_details(self): for course in self.courses: - patched_get_last_accessed.return_value = (None, course.last_accessed) + url = course_home_url(course) response = self.client.get(url) self.assertEqual(response.status_code, 200) response_content = response.content.decode("utf-8") - self.assertIn('Resume Course', response_content) + self.assertTrue(course.children) for chapter in course.children: self.assertIn(chapter.display_name, response_content) + self.assertTrue(chapter.children) for section in chapter.children: self.assertIn(section.display_name, response_content) if section.graded: - self.assertIn(section.due, response_content) + self.assertIn(section.due.strftime('%Y-%m-%d %H:%M:%S'), response_content) self.assertIn(section.format, response_content) + self.assertTrue(section.children) for vertical in section.children: self.assertNotIn(vertical.display_name, response_content) + def test_start_course(self): + """ + Tests that the start course button appears when the course has never been accessed. + + Technically, this is a course home test, and not a course outline test, but checking the counts of + start/resume course should be done together to not get a false positive. + + """ + course = self.courses[0] + + response = self.client.get(course_home_url(course)) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, 'Start Course', count=1) + self.assertContains(response, 'Resume Course', count=0) + + content = pq(response.content) + self.assertTrue(content('.action-resume-course').attr('href').endswith('/course/' + course.url_name)) + + def test_resume_course(self): + """ + Tests that two resume course buttons appear when the course has been accessed. + + Technically, this is a mix of a course home and course outline test, but checking the counts of start/resume + course should be done together to not get a false positive. + + """ + course = self.courses[0] + + # first navigate to a section to make it the last accessed + chapter = course.children[0] + section = chapter.children[0] + last_accessed_url = reverse( + 'courseware_section', + kwargs={ + 'course_id': course.id.to_deprecated_string(), + 'chapter': chapter.url_name, + 'section': section.url_name, + } + ) + self.assertEqual(200, self.client.get(last_accessed_url).status_code) + + # check resume course buttons + response = self.client.get(course_home_url(course)) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, 'Start Course', count=0) + self.assertContains(response, 'Resume Course', count=2) + + content = pq(response.content) + self.assertTrue(content('.action-resume-course').attr('href').endswith('/sequential/' + section.url_name)) + class TestCourseOutlinePreview(SharedModuleStoreTestCase): """ diff --git a/openedx/features/course_experience/utils.py b/openedx/features/course_experience/utils.py new file mode 100644 index 0000000..5b10356 --- /dev/null +++ b/openedx/features/course_experience/utils.py @@ -0,0 +1,78 @@ +""" +Common utilities for the course experience, including course outline. +""" +from lms.djangoapps.course_api.blocks.api import get_blocks +from lms.djangoapps.course_blocks.utils import get_student_module_as_dict +from opaque_keys.edx.keys import CourseKey +from openedx.core.lib.cache_utils import memoized +from xmodule.modulestore.django import modulestore + + +@memoized +def get_course_outline_block_tree(request, course_id): + """ + Returns the root block of the course outline, with children as blocks. + """ + + def populate_children(block, all_blocks): + """ + Replace each child id with the full block for the child. + + Given a block, replaces each id in its children array with the full + representation of that child, which will be looked up by id in the + passed all_blocks dict. Recursively do the same replacement for children + of those children. + """ + children = block.get('children', []) + + for i in range(len(children)): + child_id = block['children'][i] + child_detail = populate_children(all_blocks[child_id], all_blocks) + block['children'][i] = child_detail + + return block + + def set_lasted_accessed_default(block): + """ + Set default of False for last_accessed on all blocks. + """ + block['last_accessed'] = False + for child in block.get('children', []): + set_lasted_accessed_default(child) + + def mark_lasted_accessed(user, course_key, block): + """ + Recursively marks the branch to the last accessed block. + """ + block_key = block.serializer.instance + student_module_dict = get_student_module_as_dict(user, course_key, block_key) + last_accessed_child_position = student_module_dict.get('position') + if last_accessed_child_position and block.get('children'): + block['last_accessed'] = True + if len(block['children']) <= last_accessed_child_position: + last_accessed_child_block = block['children'][last_accessed_child_position - 1] + last_accessed_child_block['last_accessed'] = True + mark_lasted_accessed(user, course_key, last_accessed_child_block) + else: + # We should be using an id in place of position for last accessed. However, while using position, if + # the child block is no longer accessible we'll use the last child. + block['children'][-1]['last_accessed'] = True + + course_key = CourseKey.from_string(course_id) + course_usage_key = modulestore().make_course_usage_key(course_key) + + all_blocks = get_blocks( + request, + course_usage_key, + user=request.user, + nav_depth=3, + requested_fields=['children', 'display_name', 'type', 'due', 'graded', 'special_exam_info', 'format'], + block_types_filter=['course', 'chapter', 'sequential'] + ) + + course_outline_root_block = all_blocks['blocks'][all_blocks['root']] + populate_children(course_outline_root_block, all_blocks['blocks']) + set_lasted_accessed_default(course_outline_root_block) + mark_lasted_accessed(request.user, course_key, course_outline_root_block) + + return course_outline_root_block diff --git a/openedx/features/course_experience/views/course_home.py b/openedx/features/course_experience/views/course_home.py index 6be37fd..8baa859 100644 --- a/openedx/features/course_experience/views/course_home.py +++ b/openedx/features/course_experience/views/course_home.py @@ -9,14 +9,15 @@ from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_control from django.views.decorators.csrf import ensure_csrf_cookie -from courseware.courses import get_course_info_section, get_course_with_access, get_last_accessed_courseware +from courseware.courses import get_course_info_section, get_course_with_access from lms.djangoapps.courseware.views.views import CourseTabView from opaque_keys.edx.keys import CourseKey from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from util.views import ensure_valid_course_key from web_fragments.fragment import Fragment -from course_outline import CourseOutlineFragmentView +from .course_outline import CourseOutlineFragmentView +from ..utils import get_course_outline_block_tree class CourseHomeView(CourseTabView): @@ -43,29 +44,67 @@ class CourseHomeFragmentView(EdxFragmentView): """ A fragment to render the home page for a course. """ + + def _get_resume_course_info(self, request, course_id): + """ + Returns information relevant to resume course functionality. + + Returns a tuple: (has_visited_course, resume_course_url) + has_visited_course: True if the user has ever visted the course, False otherwise. + resume_course_url: The URL of the last accessed block if the user has visited the course, + otherwise the URL of the course root. + + """ + + def get_last_accessed_block(block): + """ + Gets the deepest block marked as 'last_accessed'. + """ + if not block['last_accessed']: + return None + if not block.get('children'): + return block + for child in block['children']: + last_accessed_block = get_last_accessed_block(child) + if last_accessed_block: + return last_accessed_block + return block + + course_outline_root_block = get_course_outline_block_tree(request, course_id) + last_accessed_block = get_last_accessed_block(course_outline_root_block) + has_visited_course = bool(last_accessed_block) + if last_accessed_block: + resume_course_url = last_accessed_block['lms_web_url'] + else: + resume_course_url = course_outline_root_block['lms_web_url'] + + return (has_visited_course, resume_course_url) + def render_to_fragment(self, request, course_id=None, **kwargs): """ Renders the course's home page as a fragment. """ course_key = CourseKey.from_string(course_id) - course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) # Render the outline as a fragment outline_fragment = CourseOutlineFragmentView().render_to_fragment(request, course_id=course_id, **kwargs) - # Get the last accessed courseware - last_accessed_url, __ = get_last_accessed_courseware(course, request, request.user) + # Get resume course information + has_visited_course, resume_course_url = self._get_resume_course_info(request, course_id) # Get the handouts + # TODO: Use get_course_overview_with_access and blocks api + course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) handouts_html = get_course_info_section(request, request.user, course, 'handouts') # Render the course home fragment context = { 'csrf': csrf(request)['csrf_token'], - 'course': course, + 'course_key': course_key, 'outline_fragment': outline_fragment, 'handouts_html': handouts_html, - 'has_visited_course': last_accessed_url is not None, + 'has_visited_course': has_visited_course, + 'resume_course_url': resume_course_url, 'disable_courseware_js': True, 'uses_pattern_library': True, } diff --git a/openedx/features/course_experience/views/course_outline.py b/openedx/features/course_experience/views/course_outline.py index d8b06cb..2c908a5 100644 --- a/openedx/features/course_experience/views/course_outline.py +++ b/openedx/features/course_experience/views/course_outline.py @@ -5,12 +5,12 @@ Views to show a course outline. from django.core.context_processors import csrf from django.template.loader import render_to_string -from courseware.courses import get_course_with_access, get_last_accessed_courseware -from lms.djangoapps.course_api.blocks.api import get_blocks +from courseware.courses import get_course_overview_with_access from opaque_keys.edx.keys import CourseKey from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from web_fragments.fragment import Fragment -from xmodule.modulestore.django import modulestore + +from ..utils import get_course_outline_block_tree class CourseOutlineFragmentView(EdxFragmentView): @@ -18,47 +18,19 @@ class CourseOutlineFragmentView(EdxFragmentView): Course outline fragment to be shown in the unified course view. """ - def populate_children(self, block, all_blocks, course_position): - """ - For a passed block, replace each id in its children array with the full representation of that child, - which will be looked up by id in the passed all_blocks dict. - Recursively do the same replacement for children of those children. - """ - children = block.get('children') or [] - - for i in range(len(children)): - child_id = block['children'][i] - child_detail = self.populate_children(all_blocks[child_id], all_blocks, course_position) - - block['children'][i] = child_detail - block['children'][i]['current'] = course_position == child_detail['block_id'] - - return block - def render_to_fragment(self, request, course_id=None, page_context=None, **kwargs): """ Renders the course outline as a fragment. """ course_key = CourseKey.from_string(course_id) - course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) - _, course_position = get_last_accessed_courseware(course, request, request.user) - course_usage_key = modulestore().make_course_usage_key(course_key) - all_blocks = get_blocks( - request, - course_usage_key, - user=request.user, - nav_depth=3, - requested_fields=['children', 'display_name', 'type', 'due', 'graded', 'special_exam_info', 'format'], - block_types_filter=['course', 'chapter', 'sequential'] - ) + course_overview = get_course_overview_with_access(request.user, 'load', course_key, check_if_enrolled=True) - course_block_tree = all_blocks['blocks'][all_blocks['root']] # Get the root of the block tree + course_block_tree = get_course_outline_block_tree(request, course_id) context = { 'csrf': csrf(request)['csrf_token'], - 'course': course, - # Recurse through the block tree, fleshing out each child object - 'blocks': self.populate_children(course_block_tree, all_blocks['blocks'], course_position) + 'course': course_overview, + 'blocks': course_block_tree } html = render_to_string('course_experience/course-outline-fragment.html', context) return Fragment(html) -- libgit2 0.26.0