diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index c6f6ce48ab..895e92277a 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -518,3 +518,49 @@ def is_self_paced(course):
Returns True if course is self-paced, False otherwise.
"""
return course and course.self_paced
+
+
+def get_sibling_urls(subsection):
+ """
+ Given a subsection, returns the urls for the next and previous units.
+
+ (the first unit of the next subsection or section, and
+ the last unit of the previous subsection/section)
+ """
+ section = subsection.get_parent()
+ prev_url = next_url = ''
+ prev_loc = next_loc = None
+ last_block = None
+ siblings = list(section.get_children())
+ for i, block in enumerate(siblings):
+ if block.location == subsection.location:
+ if last_block:
+ try:
+ prev_loc = last_block.get_children()[0].location
+ except IndexError:
+ pass
+ try:
+ next_loc = siblings[i + 1].get_children()[0].location
+ except IndexError:
+ pass
+ break
+ last_block = block
+ if not prev_loc:
+ sections = section.get_parent().get_children()
+ try:
+ prev_section = sections[sections.index(section) - 1]
+ prev_loc = prev_section.get_children()[-1].get_children()[-1].location
+ except IndexError:
+ pass
+ if not next_loc:
+ sections = section.get_parent().get_children()
+ try:
+ next_section = sections[sections.index(section) + 1]
+ next_loc = next_section.get_children()[0].get_children()[0].location
+ except IndexError:
+ pass
+ if prev_loc:
+ prev_url = reverse_usage_url('container_handler', prev_loc)
+ if next_loc:
+ next_url = reverse_usage_url('container_handler', next_loc)
+ return prev_url, next_url
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 33bac6c67c..a0f865d2fd 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -1,8 +1,11 @@
+"""
+Studio component views
+"""
from __future__ import absolute_import
import logging
-import six
+import six
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
@@ -11,17 +14,18 @@ from django.utils.translation import ugettext as _
from django.views.decorators.http import require_GET
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey
+from six.moves.urllib.parse import quote_plus # pylint: disable=import-error
from xblock.core import XBlock
from xblock.django.request import django_to_webob_request, webob_to_django_response
from xblock.exceptions import NoSuchHandlerError
from xblock.plugin import PluginMissingError
from xblock.runtime import Mixologist
-from contentstore.utils import get_lms_link_for_item, reverse_course_url
+from contentstore.utils import get_lms_link_for_item, get_sibling_urls, reverse_course_url
from contentstore.views.helpers import get_parent_xblock, is_unit, xblock_type_display_name
from contentstore.views.item import StudioEditModuleRuntime, add_container_page_publishing_info, create_xblock_info
from edxmako.shortcuts import render_to_response
-from openedx.core.lib.xblock_utils import is_xblock_aside, get_aside_from_xblock
+from openedx.core.lib.xblock_utils import get_aside_from_xblock, is_xblock_aside
from student.auth import has_course_author_access
from xblock_django.api import authorable_xblocks, disabled_xblocks
from xblock_django.models import XBlockStudioConfigurationFlag
@@ -123,11 +127,16 @@ def container_handler(request, usage_key_string):
is_unit_page = is_unit(xblock)
unit = xblock if is_unit_page else None
- while parent and parent.category != 'course':
+ is_first = True
+ while parent:
if unit is None and is_unit(parent):
unit = parent
- ancestor_xblocks.append(parent)
+ elif parent.category != 'sequential':
+ current_block = {'block': parent, 'children': parent.get_children(), 'is_last': is_first}
+ is_first = False
+ ancestor_xblocks.append(current_block)
parent = get_parent_xblock(parent)
+
ancestor_xblocks.reverse()
assert unit is not None, "Could not determine unit page"
@@ -137,6 +146,13 @@ def container_handler(request, usage_key_string):
section = get_parent_xblock(subsection)
assert section is not None, "Could not determine ancestor section from unit " + six.text_type(unit.location)
+ # for the sequence navigator
+ prev_url, next_url = get_sibling_urls(subsection)
+ # these are quoted here because they'll end up in a query string on the page,
+ # and quoting with mako will trigger the xss linter...
+ prev_url = quote_plus(prev_url) if prev_url else None
+ next_url = quote_plus(next_url) if next_url else None
+
# Fetch the XBlock info for use by the container page. Note that it includes information
# about the block's ancestors and siblings for use by the Unit Outline.
xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page)
@@ -162,6 +178,9 @@ def container_handler(request, usage_key_string):
'is_unit_page': is_unit_page,
'subsection': subsection,
'section': section,
+ 'position': index,
+ 'prev_url': prev_url,
+ 'next_url': next_url,
'new_unit_category': 'vertical',
'outline_url': '{url}?format=concise'.format(url=reverse_course_url('course_handler', course.id)),
'ancestor_xblocks': ancestor_xblocks,
@@ -392,7 +411,7 @@ def get_component_templates(courselike, library=False):
u"Improper format for course advanced keys! %s",
course_advanced_keys
)
- if len(advanced_component_templates['templates']) > 0:
+ if advanced_component_templates['templates']:
component_templates.insert(0, advanced_component_templates)
return component_templates
diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py
index 66e7c0e3ae..8d5fd0d140 100644
--- a/cms/djangoapps/contentstore/views/item.py
+++ b/cms/djangoapps/contentstore/views/item.py
@@ -72,7 +72,7 @@ from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundErr
from xmodule.modulestore.inheritance import own_metadata
from xmodule.services import ConfigurationService, SettingsService
from xmodule.tabs import CourseTabList
-from xmodule.x_module import DEPRECATION_VSCOMPAT_EVENT, PREVIEW_VIEWS, STUDENT_VIEW, STUDIO_VIEW
+from xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW, STUDIO_VIEW
from edx_proctoring.api import get_exam_configuration_dashboard_url, does_backend_support_onboarding
from cms.djangoapps.contentstore.config.waffle import SHOW_REVIEW_RULES_FLAG
@@ -273,6 +273,7 @@ class StudioEditModuleRuntime(object):
(i.e. whenever we're not using PreviewModuleSystem.) This is required to make information
about the current user (especially permissions) available via services as needed.
"""
+
def __init__(self, user):
self._user = user
@@ -381,16 +382,18 @@ def xblock_view_handler(request, usage_key_string, view_name):
force_render = request.GET.get('force_render', None)
# Set up the context to be passed to each XBlock's render method.
- context = {
- 'is_pages_view': is_pages_view, # This setting disables the recursive wrapping of xblocks
+ context = request.GET.dict()
+ context.update({
+ # This setting disables the recursive wrapping of xblocks
+ 'is_pages_view': is_pages_view or view_name == AUTHOR_VIEW,
'is_unit_page': is_unit(xblock),
'can_edit': can_edit,
'root_xblock': xblock if (view_name == 'container_preview') else None,
'reorderable_items': reorderable_items,
'paging': paging,
'force_render': force_render,
- }
-
+ 'item_url': '/container/{usage_key}',
+ })
fragment = get_preview_fragment(request, xblock, context)
# Note that the container view recursively adds headers into the preview fragment,
@@ -998,7 +1001,11 @@ def _get_xblock(usage_key, user):
except ItemNotFoundError:
if usage_key.block_type in CREATE_IF_NOT_FOUND:
# Create a new one for certain categories only. Used for course info handouts.
- return store.create_item(user.id, usage_key.course_key, usage_key.block_type, block_id=usage_key.block_id)
+ return store.create_item(
+ user.id,
+ usage_key.course_key,
+ usage_key.block_type,
+ block_id=usage_key.block_id)
else:
raise
except InvalidLocationError:
@@ -1052,7 +1059,7 @@ def _get_gating_info(course, xblock):
if not hasattr(course, 'gating_prerequisites'):
# Cache gating prerequisites on course module so that we are not
# hitting the database for every xblock in the course
- setattr(course, 'gating_prerequisites', gating_api.get_prerequisites(course.id))
+ course.gating_prerequisites = gating_api.get_prerequisites(course.id)
info["is_prereq"] = gating_api.is_prerequisite(course.id, xblock.location)
info["prereqs"] = [
p for p in course.gating_prerequisites if text_type(xblock.location) not in p['namespace']
@@ -1163,7 +1170,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
'has_children': xblock.has_children
}
if is_concise:
- if child_info and len(child_info.get('children', [])) > 0:
+ if child_info and child_info.get('children', []):
xblock_info['child_info'] = child_info
# Groups are labelled with their internal ids, rather than with the group name. Replace id with display name.
group_display_name = get_split_group_display_name(xblock, course)
@@ -1334,7 +1341,8 @@ class VisibilityState(object):
unscheduled - the block and all of its descendants have no release date (excluding staff only items)
Note: it is valid for items to be published with no release date in which case they are still unscheduled.
- needs_attention - the block or its descendants are not fully live, ready or unscheduled (excluding staff only items)
+ needs_attention - the block or its descendants are not fully live, ready or unscheduled
+ (excluding staff only items)
For example: one subsection has draft content, or there's both unreleased and released content in one section.
staff_only - all of the block's content is to be shown to staff only
diff --git a/cms/djangoapps/contentstore/views/tests/test_container_page.py b/cms/djangoapps/contentstore/views/tests/test_container_page.py
index fddf3bf6e8..b9e3f82aa5 100644
--- a/cms/djangoapps/contentstore/views/tests/test_container_page.py
+++ b/cms/djangoapps/contentstore/views/tests/test_container_page.py
@@ -63,13 +63,10 @@ class ContainerPageTestCase(StudioPageTestCase, LibraryTestCase):
u'data-locator="{0}" data-course-key="{0.course_key}">'.format(self.child_container.location)
),
expected_breadcrumbs=(
- u'\\s*Week 1\\s*\\s*'
- u'\\s*Lesson 1\\s*\\s*'
- u'\\s*Unit\\s*'
+ u'