Commit cc8594ac by Christina Roberts

Merge pull request #2807 from edx/christina/containers

Support xblocks with children on the container page.
parents edafb7c0 653df428
...@@ -23,7 +23,6 @@ from xblock.exceptions import NoSuchHandlerError ...@@ -23,7 +23,6 @@ from xblock.exceptions import NoSuchHandlerError
from xblock.fields import Scope from xblock.fields import Scope
from xblock.plugin import PluginMissingError from xblock.plugin import PluginMissingError
from xblock.runtime import Mixologist from xblock.runtime import Mixologist
from xmodule.x_module import prefer_xmodules
from lms.lib.xblock.runtime import unquote_slashes from lms.lib.xblock.runtime import unquote_slashes
...@@ -310,13 +309,20 @@ def container_handler(request, tag=None, package_id=None, branch=None, version_g ...@@ -310,13 +309,20 @@ def container_handler(request, tag=None, package_id=None, branch=None, version_g
old_location, course, xblock, __ = _get_item_in_course(request, locator) old_location, course, xblock, __ = _get_item_in_course(request, locator)
except ItemNotFoundError: except ItemNotFoundError:
return HttpResponseBadRequest() return HttpResponseBadRequest()
parent_xblock = get_parent_xblock(xblock)
ancestor_xblocks = []
parent = get_parent_xblock(xblock)
while parent and parent.category != 'sequential':
ancestor_xblocks.append(parent)
parent = get_parent_xblock(parent)
ancestor_xblocks.reverse()
return render_to_response('container.html', { return render_to_response('container.html', {
'context_course': course, 'context_course': course,
'xblock': xblock, 'xblock': xblock,
'xblock_locator': locator, 'xblock_locator': locator,
'parent_xblock': parent_xblock, 'ancestor_xblocks': ancestor_xblocks,
}) })
else: else:
return HttpResponseBadRequest("Only supports html requests") return HttpResponseBadRequest("Only supports html requests")
......
...@@ -206,12 +206,10 @@ def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, v ...@@ -206,12 +206,10 @@ def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, v
elif view_name == 'student_view' and component.has_children: elif view_name == 'student_view' and component.has_children:
# For non-leaf xblocks on the unit page, show the special rendering # For non-leaf xblocks on the unit page, show the special rendering
# which links to the new container page. # which links to the new container page.
course_location = loc_mapper().translate_locator_to_location(locator, True) html = render_to_string('container_xblock_component.html', {
course = store.get_item(course_location)
html = render_to_string('unit_container_xblock_component.html', {
'course': course,
'xblock': component, 'xblock': component,
'locator': locator 'locator': locator,
'reordering_enabled': True,
}) })
return JsonResponse({ return JsonResponse({
'html': html, 'html': html,
......
...@@ -179,6 +179,8 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): ...@@ -179,6 +179,8 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
} }
if xblock.category == 'vertical': if xblock.category == 'vertical':
template = 'studio_vertical_wrapper.html' template = 'studio_vertical_wrapper.html'
elif xblock.location != context.get('root_xblock').location and xblock.has_children:
template = 'container_xblock_component.html'
else: else:
template = 'studio_xblock_wrapper.html' template = 'studio_xblock_wrapper.html'
html = render_to_string(template, template_context) html = render_to_string(template, template_context)
......
...@@ -26,8 +26,44 @@ class ContainerViewTestCase(CourseTestCase): ...@@ -26,8 +26,44 @@ class ContainerViewTestCase(CourseTestCase):
category="video", display_name="My Video") category="video", display_name="My Video")
def test_container_html(self): def test_container_html(self):
url = xblock_studio_url(self.child_vertical) self._test_html_content(
self.child_vertical,
expected_section_tag='<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/published/block/Child_Vertical"/>',
expected_breadcrumbs=(
r'<a href="/unit/MITx.999.Robot_Super_Course/branch/published/block/Unit"\s*'
r'class="navigation-link navigation-parent">Unit</a>\s*'
r'<a href="#" class="navigation-link navigation-current">Child Vertical</a>'),
)
def test_container_on_container_html(self):
"""
Create the scenario of an xblock with children (non-vertical) on the container page.
This should create a container page that is a child of another container page.
"""
xblock_with_child = ItemFactory.create(parent_location=self.child_vertical.location,
category="wrapper", display_name="Wrapper")
ItemFactory.create(parent_location=xblock_with_child.location,
category="html", display_name="Child HTML")
self._test_html_content(
xblock_with_child,
expected_section_tag='<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/published/block/Wrapper"/>',
expected_breadcrumbs=(
r'<a href="/unit/MITx.999.Robot_Super_Course/branch/published/block/Unit"\s*'
r'class="navigation-link navigation-parent">Unit</a>\s*'
r'<a href="/container/MITx.999.Robot_Super_Course/branch/published/block/Child_Vertical"\s*'
r'class="navigation-link navigation-parent">Child Vertical</a>\s*'
r'<a href="#" class="navigation-link navigation-current">Wrapper</a>'),
)
def _test_html_content(self, xblock, expected_section_tag, expected_breadcrumbs):
"""
Get the HTML for a container page and verify the section tag is correct
and the breadcrumbs trail is correct.
"""
url = xblock_studio_url(xblock, self.course)
resp = self.client.get_html(url) resp = self.client.get_html(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
html = resp.content html = resp.content
self.assertIn('<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/published/block/Child_Vertical"/>', html) self.assertIn(expected_section_tag, html)
# Verify the navigation link at the top of the page is correct.
self.assertRegexpMatches(html, expected_breadcrumbs)
...@@ -132,6 +132,31 @@ class GetItem(ItemTest): ...@@ -132,6 +132,31 @@ class GetItem(ItemTest):
# Verify that the Studio element wrapper has been added # Verify that the Studio element wrapper has been added
self.assertIn('level-element', html) self.assertIn('level-element', html)
def test_get_container_nested_container_fragment(self):
"""
Test the case of the container page containing a link to another container page.
"""
# Add a wrapper with child beneath a child vertical
root_locator = self._create_vertical()
resp = self.create_xblock(parent_locator=root_locator, category="wrapper")
self.assertEqual(resp.status_code, 200)
wrapper_locator = self.response_locator(resp)
resp = self.create_xblock(parent_locator=wrapper_locator, category='problem', boilerplate='multiplechoice.yaml')
self.assertEqual(resp.status_code, 200)
# Get the preview HTML and verify the View -> link is present.
html, __ = self._get_container_preview(root_locator)
self.assertIn('wrapper-xblock', html)
self.assertRegexpMatches(
html,
# The instance of the wrapper class will have an auto-generated ID (wrapperxxx). Allow anything
# for the 3 characters after wrapper.
(r'"/container/MITx.999.Robot_Super_Course/branch/published/block/wrapper.{3}" class="action-button">\s*'
'<span class="action-button-text">View</span>')
)
class DeleteItem(ItemTest): class DeleteItem(ItemTest):
"""Tests for '/xblock' DELETE url.""" """Tests for '/xblock' DELETE url."""
......
...@@ -334,11 +334,12 @@ p, ul, ol, dl { ...@@ -334,11 +334,12 @@ p, ul, ol, dl {
.navigation-link { .navigation-link {
@extend %cont-truncated; @extend %cont-truncated;
display: inline-block; display: inline-block;
max-width: 150px; max-width: 250px;
&.navigation-current { &.navigation-current {
@extend %ui-disabled; @extend %ui-disabled;
color: $gray; color: $gray;
max-width: 250px;
&:before { &:before {
color: $gray; color: $gray;
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
} }
// UI: xblock is collapsible // UI: xblock is collapsible
.wrapper-xblock.is-collapsible { .wrapper-xblock.is-collapsible, .wrapper-xblock.xblock-type-container {
[class^="icon-"] { [class^="icon-"] {
font-style: normal; font-style: normal;
......
...@@ -16,7 +16,7 @@ from django.utils.translation import ugettext as _ ...@@ -16,7 +16,7 @@ from django.utils.translation import ugettext as _
<% <%
xblock_info = { xblock_info = {
'id': str(xblock_locator), 'id': str(xblock_locator),
'display-name': xblock.display_name, 'display-name': xblock.display_name_with_default,
'category': xblock.category, 'category': xblock.category,
}; };
%> %>
...@@ -47,14 +47,16 @@ xblock_info = { ...@@ -47,14 +47,16 @@ xblock_info = {
<header class="mast has-actions has-navigation"> <header class="mast has-actions has-navigation">
<h1 class="page-header"> <h1 class="page-header">
<small class="navigation navigation-parents"> <small class="navigation navigation-parents">
<% % for ancestor in ancestor_xblocks:
parent_url = xblock_studio_url(parent_xblock, context_course) <%
%> ancestor_url = xblock_studio_url(ancestor, context_course)
% if parent_url: %>
<a href="${parent_url}" % if ancestor_url:
class="navigation-link navigation-parent">${parent_xblock.display_name | h}</a> <a href="${ancestor_url}"
% endif class="navigation-link navigation-parent">${ancestor.display_name_with_default | h}</a>
<a href="#" class="navigation-link navigation-current">${xblock.display_name | h}</a> % endif
% endfor
<a href="#" class="navigation-link navigation-current">${xblock.display_name_with_default | h}</a>
</small> </small>
</h1> </h1>
......
...@@ -7,12 +7,12 @@ from contentstore.views.helpers import xblock_studio_url ...@@ -7,12 +7,12 @@ from contentstore.views.helpers import xblock_studio_url
<section class="wrapper-xblock xblock-type-container level-element" data-locator="${locator}"> <section class="wrapper-xblock xblock-type-container level-element" data-locator="${locator}">
<header class="xblock-header"> <header class="xblock-header">
<div class="header-details"> <div class="header-details">
${xblock.display_name} ${xblock.display_name_with_default}
</div> </div>
<div class="header-actions"> <div class="header-actions">
<ul class="actions-list"> <ul class="actions-list">
<li class="action-item action-view"> <li class="action-item action-view">
<a href="${xblock_studio_url(xblock, course)}" class="action-button"> <a href="${xblock_studio_url(xblock)}" class="action-button">
## Translators: this is a verb describing the action of viewing more details ## Translators: this is a verb describing the action of viewing more details
<span class="action-button-text">${_('View')}</span> <span class="action-button-text">${_('View')}</span>
<i class="icon-arrow-right"></i> <i class="icon-arrow-right"></i>
...@@ -21,5 +21,8 @@ from contentstore.views.helpers import xblock_studio_url ...@@ -21,5 +21,8 @@ from contentstore.views.helpers import xblock_studio_url
</ul> </ul>
</div> </div>
</header> </header>
<span data-tooltip="${_("Drag to reorder")}" class="drag-handle"></span> ## We currently support reordering only on the unit page.
% if reordering_enabled:
<span data-tooltip="${_("Drag to reorder")}" class="drag-handle"></span>
% endif
</section> </section>
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<i class="icon-caret-down ui-toggle-expansion"></i> <i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">${_('Expand or Collapse')}</span> <span class="sr">${_('Expand or Collapse')}</span>
</a> </a>
<span>${xblock.display_name | h}</span> <span>${xblock.display_name_with_default | h}</span>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<ul class="actions-list"> <ul class="actions-list">
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
% endif % endif
<header class="xblock-header"> <header class="xblock-header">
<div class="header-details"> <div class="header-details">
${xblock.display_name | h} ${xblock.display_name_with_default | h}
</div> </div>
<div class="header-actions"> <div class="header-actions">
<ul class="actions-list"> <ul class="actions-list">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment