Merge pull request #2807 from edx/christina/containers
Support xblocks with children on the container page.
This commit is contained in:
@@ -23,7 +23,6 @@ from xblock.exceptions import NoSuchHandlerError
|
||||
from xblock.fields import Scope
|
||||
from xblock.plugin import PluginMissingError
|
||||
from xblock.runtime import Mixologist
|
||||
from xmodule.x_module import prefer_xmodules
|
||||
|
||||
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
|
||||
old_location, course, xblock, __ = _get_item_in_course(request, locator)
|
||||
except ItemNotFoundError:
|
||||
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', {
|
||||
'context_course': course,
|
||||
'xblock': xblock,
|
||||
'xblock_locator': locator,
|
||||
'parent_xblock': parent_xblock,
|
||||
'ancestor_xblocks': ancestor_xblocks,
|
||||
})
|
||||
else:
|
||||
return HttpResponseBadRequest("Only supports html requests")
|
||||
|
||||
@@ -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:
|
||||
# For non-leaf xblocks on the unit page, show the special rendering
|
||||
# which links to the new container page.
|
||||
course_location = loc_mapper().translate_locator_to_location(locator, True)
|
||||
course = store.get_item(course_location)
|
||||
html = render_to_string('unit_container_xblock_component.html', {
|
||||
'course': course,
|
||||
html = render_to_string('container_xblock_component.html', {
|
||||
'xblock': component,
|
||||
'locator': locator
|
||||
'locator': locator,
|
||||
'reordering_enabled': True,
|
||||
})
|
||||
return JsonResponse({
|
||||
'html': html,
|
||||
|
||||
@@ -179,6 +179,8 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
|
||||
}
|
||||
if xblock.category == 'vertical':
|
||||
template = 'studio_vertical_wrapper.html'
|
||||
elif xblock.location != context.get('root_xblock').location and xblock.has_children:
|
||||
template = 'container_xblock_component.html'
|
||||
else:
|
||||
template = 'studio_xblock_wrapper.html'
|
||||
html = render_to_string(template, template_context)
|
||||
|
||||
@@ -26,8 +26,44 @@ class ContainerViewTestCase(CourseTestCase):
|
||||
category="video", display_name="My Video")
|
||||
|
||||
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)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
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):
|
||||
# Verify that the Studio element wrapper has been added
|
||||
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):
|
||||
"""Tests for '/xblock' DELETE url."""
|
||||
|
||||
@@ -334,11 +334,12 @@ p, ul, ol, dl {
|
||||
.navigation-link {
|
||||
@extend %cont-truncated;
|
||||
display: inline-block;
|
||||
max-width: 150px;
|
||||
max-width: 250px;
|
||||
|
||||
&.navigation-current {
|
||||
@extend %ui-disabled;
|
||||
color: $gray;
|
||||
max-width: 250px;
|
||||
|
||||
&:before {
|
||||
color: $gray;
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
}
|
||||
|
||||
// UI: xblock is collapsible
|
||||
.wrapper-xblock.is-collapsible {
|
||||
.wrapper-xblock.is-collapsible, .wrapper-xblock.xblock-type-container {
|
||||
|
||||
[class^="icon-"] {
|
||||
font-style: normal;
|
||||
|
||||
@@ -16,7 +16,7 @@ from django.utils.translation import ugettext as _
|
||||
<%
|
||||
xblock_info = {
|
||||
'id': str(xblock_locator),
|
||||
'display-name': xblock.display_name,
|
||||
'display-name': xblock.display_name_with_default,
|
||||
'category': xblock.category,
|
||||
};
|
||||
%>
|
||||
@@ -47,14 +47,16 @@ xblock_info = {
|
||||
<header class="mast has-actions has-navigation">
|
||||
<h1 class="page-header">
|
||||
<small class="navigation navigation-parents">
|
||||
<%
|
||||
parent_url = xblock_studio_url(parent_xblock, context_course)
|
||||
%>
|
||||
% if parent_url:
|
||||
<a href="${parent_url}"
|
||||
class="navigation-link navigation-parent">${parent_xblock.display_name | h}</a>
|
||||
% endif
|
||||
<a href="#" class="navigation-link navigation-current">${xblock.display_name | h}</a>
|
||||
% for ancestor in ancestor_xblocks:
|
||||
<%
|
||||
ancestor_url = xblock_studio_url(ancestor, context_course)
|
||||
%>
|
||||
% if ancestor_url:
|
||||
<a href="${ancestor_url}"
|
||||
class="navigation-link navigation-parent">${ancestor.display_name_with_default | h}</a>
|
||||
% endif
|
||||
% endfor
|
||||
<a href="#" class="navigation-link navigation-current">${xblock.display_name_with_default | h}</a>
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ from contentstore.views.helpers import xblock_studio_url
|
||||
<section class="wrapper-xblock xblock-type-container level-element" data-locator="${locator}">
|
||||
<header class="xblock-header">
|
||||
<div class="header-details">
|
||||
${xblock.display_name}
|
||||
${xblock.display_name_with_default}
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<ul class="actions-list">
|
||||
<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
|
||||
<span class="action-button-text">${_('View')}</span>
|
||||
<i class="icon-arrow-right"></i>
|
||||
@@ -21,5 +21,8 @@ from contentstore.views.helpers import xblock_studio_url
|
||||
</ul>
|
||||
</div>
|
||||
</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>
|
||||
@@ -8,7 +8,7 @@
|
||||
<i class="icon-caret-down ui-toggle-expansion"></i>
|
||||
<span class="sr">${_('Expand or Collapse')}</span>
|
||||
</a>
|
||||
<span>${xblock.display_name | h}</span>
|
||||
<span>${xblock.display_name_with_default | h}</span>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<ul class="actions-list">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
% endif
|
||||
<header class="xblock-header">
|
||||
<div class="header-details">
|
||||
${xblock.display_name | h}
|
||||
${xblock.display_name_with_default | h}
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<ul class="actions-list">
|
||||
|
||||
Reference in New Issue
Block a user