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' % endfor % endif + % if exclude_units: +
  • + +
  • + % endif + % if not exclude_units: % if gated_content['gated']: <%include file="_gated_content.html" args="prereq_url=gated_content['prereq_url'], prereq_section_name=gated_content['prereq_section_name'], gated_section_name=gated_content['gated_section_name']"/> % else: @@ -89,7 +114,11 @@ % endfor
    % endif +% else: +
    +% endif +% if not exclude_units: +% endif