Commit 4eb234e7 by Piotr Mitros

Merge pull request #4174 from edx/pmitros/chromeless-xblocks

XBlocks can disable navigation chrome.

This allows for full-screen XBlocks (such as code IDEs), as well as allowing us to somewhat hackishly build top-level XBlocks. This is the first step on the path to non-hackish top-level XBlocks. 
parents 2b1712af 87a462e2
...@@ -147,6 +147,14 @@ class MongoContentStore(ContentStore): ...@@ -147,6 +147,14 @@ class MongoContentStore(ContentStore):
for asset in assets: for asset in assets:
asset_location = AssetLocation._from_deprecated_son(asset['_id'], course_key.run) # pylint: disable=protected-access asset_location = AssetLocation._from_deprecated_son(asset['_id'], course_key.run) # pylint: disable=protected-access
# TODO: On 6/19/14, I had to put a try/except around this
# to export a course. The course failed on JSON files in
# the /static/ directory placed in it with an import.
#
# If this hasn't been looked at in a while, remove this comment.
#
# When debugging course exports, this might be a good place
# to look. -- pmitros
self.export(asset_location, output_directory) self.export(asset_location, output_directory)
for attr, value in asset.iteritems(): for attr, value in asset.iteritems():
if attr not in ['_id', 'md5', 'uploadDate', 'length', 'chunkSize']: if attr not in ['_id', 'md5', 'uploadDate', 'length', 'chunkSize']:
......
...@@ -28,17 +28,38 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -28,17 +28,38 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.test_course = CourseFactory.create(display_name='Robot_Sub_Course') self.test_course = CourseFactory.create(display_name='Robot_Sub_Course')
self.course = CourseFactory.create(display_name='Robot_Super_Course') self.course = CourseFactory.create(display_name='Robot_Super_Course')
self.chapter0 = ItemFactory.create(parent_location=self.course.location, self.chapter0 = ItemFactory.create(parent=self.course,
display_name='Overview') display_name='Overview')
self.chapter9 = ItemFactory.create(parent_location=self.course.location, self.chapter9 = ItemFactory.create(parent=self.course,
display_name='factory_chapter') display_name='factory_chapter')
self.section0 = ItemFactory.create(parent_location=self.chapter0.location, self.section0 = ItemFactory.create(parent=self.chapter0,
display_name='Welcome') display_name='Welcome')
self.section9 = ItemFactory.create(parent_location=self.chapter9.location, self.section9 = ItemFactory.create(parent=self.chapter9,
display_name='factory_section') display_name='factory_section')
self.unit0 = ItemFactory.create(parent_location=self.section0.location, self.unit0 = ItemFactory.create(parent=self.section0,
display_name='New Unit') display_name='New Unit')
self.chapterchrome = ItemFactory.create(parent=self.course,
display_name='Chrome')
self.chromelesssection = ItemFactory.create(parent=self.chapterchrome,
display_name='chromeless',
chrome='none')
self.accordionsection = ItemFactory.create(parent=self.chapterchrome,
display_name='accordion',
chrome='accordion')
self.tabssection = ItemFactory.create(parent=self.chapterchrome,
display_name='tabs',
chrome='tabs')
self.defaultchromesection = ItemFactory.create(parent=self.chapterchrome,
display_name='defaultchrome')
self.fullchromesection = ItemFactory.create(parent=self.chapterchrome,
display_name='fullchrome',
chrome='accordion,tabs')
self.tabtest = ItemFactory.create(parent=self.chapterchrome,
display_name='progress_tab',
default_tab = 'progress')
# Create student accounts and activate them. # Create student accounts and activate them.
for i in range(len(self.STUDENT_INFO)): for i in range(len(self.STUDENT_INFO)):
email, password = self.STUDENT_INFO[i] email, password = self.STUDENT_INFO[i]
...@@ -48,6 +69,58 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -48,6 +69,58 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.staff_user = GlobalStaffFactory() self.staff_user = GlobalStaffFactory()
def assertTabActive(self, tabname, response):
''' Check if the progress tab is active in the tab set '''
for line in response.content.split('\n'):
if tabname in line and 'active' in line:
return
raise AssertionError("assertTabActive failed: "+tabname+" not active")
def assertTabInactive(self, tabname, response):
''' Check if the progress tab is active in the tab set '''
for line in response.content.split('\n'):
if tabname in line and 'active' in line:
raise AssertionError("assertTabInactive failed: "+tabname+" active")
return
def test_chrome_settings(self):
'''
Test settings for disabling and modifying navigation chrome in the courseware:
- Accordion enabled, or disabled
- Navigation tabs enabled, disabled, or redirected
'''
email, password = self.STUDENT_INFO[0]
self.login(email, password)
self.enroll(self.course, True)
test_data = (
('tabs', False, True),
('none', False, False),
('fullchrome', True, True),
('accordion', True, False),
('fullchrome', True, True)
)
for (displayname, accordion, tabs) in test_data:
response = self.client.get(reverse('courseware_section', kwargs={
'course_id': self.course.id.to_deprecated_string(),
'chapter': 'Chrome',
'section': displayname,
}))
self.assertEquals('open_close_accordion' in response.content, accordion)
self.assertEquals('course-tabs' in response.content, tabs)
self.assertTabInactive('progress', response)
self.assertTabActive('courseware', response)
response = self.client.get(reverse('courseware_section', kwargs={
'course_id': self.course.id.to_deprecated_string(),
'chapter': 'Chrome',
'section': 'progress_tab',
}))
self.assertTabActive('progress', response)
self.assertTabInactive('courseware', response)
@override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1) @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1)
def test_inactive_session_timeout(self): def test_inactive_session_timeout(self):
""" """
......
...@@ -339,6 +339,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -339,6 +339,7 @@ def index(request, course_id, chapter=None, section=None,
if section is not None: if section is not None:
section_descriptor = chapter_descriptor.get_child_by(lambda m: m.location.name == section) section_descriptor = chapter_descriptor.get_child_by(lambda m: m.location.name == section)
if section_descriptor is None: if section_descriptor is None:
# Specifically asked-for section doesn't exist # Specifically asked-for section doesn't exist
if masq == 'student': # if staff is masquerading as student be kinder, don't 404 if masq == 'student': # if staff is masquerading as student be kinder, don't 404
...@@ -346,6 +347,17 @@ def index(request, course_id, chapter=None, section=None, ...@@ -346,6 +347,17 @@ def index(request, course_id, chapter=None, section=None,
return redirect(reverse('courseware', args=[course.id.to_deprecated_string()])) return redirect(reverse('courseware', args=[course.id.to_deprecated_string()]))
raise Http404 raise Http404
## Allow chromeless operation
if section_descriptor.chrome:
chrome = [s.strip() for s in section_descriptor.chrome.lower().split(",")]
if 'accordion' not in chrome:
context['disable_accordion'] = True
if 'tabs' not in chrome:
context['disable_tabs'] = True
if section_descriptor.default_tab:
context['default_tab'] = section_descriptor.default_tab
# cdodge: this looks silly, but let's refetch the section_descriptor with depth=None # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None
# which will prefetch the children more efficiently than doing a recursive load # which will prefetch the children more efficiently than doing a recursive load
section_descriptor = modulestore().get_item(section_descriptor.location, depth=None) section_descriptor = modulestore().get_item(section_descriptor.location, depth=None)
......
...@@ -18,5 +18,20 @@ class LmsBlockMixin(XBlockMixin): ...@@ -18,5 +18,20 @@ class LmsBlockMixin(XBlockMixin):
"grader to apply, and what to show in the TOC)", "grader to apply, and what to show in the TOC)",
scope=Scope.settings, scope=Scope.settings,
) )
chrome = String(
help="Which chrome to show. Options: \n"
"chromeless -- No chrome\n"
"tabs -- just tabs\n"
"accordion -- just accordion\n"
"tabs,accordion -- Full Chrome",
scope=Scope.settings,
default=None,
)
default_tab = String(
help="Override which tab is selected. "
"If not set, courseware tab is shown.",
scope=Scope.settings,
default=None,
)
source_file = String(help="source file name (eg for latex)", scope=Scope.settings) source_file = String(help="source file name (eg for latex)", scope=Scope.settings)
ispublic = Boolean(help="Whether this course is open to the public, or only to admins", scope=Scope.settings) ispublic = Boolean(help="Whether this course is open to the public, or only to admins", scope=Scope.settings)
...@@ -20,12 +20,13 @@ def url_class(is_active): ...@@ -20,12 +20,13 @@ def url_class(is_active):
<%! from courseware.views import notification_image_for_tab %> <%! from courseware.views import notification_image_for_tab %>
<% import waffle %> <% import waffle %>
% if disable_tabs is UNDEFINED or not disable_tabs:
<nav class="${active_page} course-material"> <nav class="${active_page} course-material">
<div class="inner-wrapper"> <div class="inner-wrapper">
<ol class="course-tabs"> <ol class="course-tabs">
% for tab in CourseTabList.iterate_displayable(course, settings, user.is_authenticated(), has_access(user, 'staff', course, course.id)): % for tab in CourseTabList.iterate_displayable(course, settings, user.is_authenticated(), has_access(user, 'staff', course, course.id)):
<% <%
tab_is_active = (tab.tab_id == active_page) tab_is_active = (tab.tab_id == active_page) or (tab.tab_id == default_tab)
tab_image = notification_image_for_tab(tab, user, course) tab_image = notification_image_for_tab(tab, user, course)
%> %>
% if waffle.flag_is_active(request, 'visual_treatment'): % if waffle.flag_is_active(request, 'visual_treatment'):
...@@ -55,6 +56,7 @@ def url_class(is_active): ...@@ -55,6 +56,7 @@ def url_class(is_active):
</ol> </ol>
</div> </div>
</nav> </nav>
%endif
% if masquerade is not UNDEFINED: % if masquerade is not UNDEFINED:
% if staff_access and masquerade is not None: % if staff_access and masquerade is not None:
......
...@@ -179,15 +179,17 @@ ${fragment.foot_html()} ...@@ -179,15 +179,17 @@ ${fragment.foot_html()}
</div> </div>
% endif % endif
% if accordion: <%include file="/dashboard/_dashboard_prompt_midcourse_reverify.html" />
<%include file="/dashboard/_dashboard_prompt_midcourse_reverify.html" /> % if default_tab:
<%include file="/courseware/course_navigation.html" />
% else:
<%include file="/courseware/course_navigation.html" args="active_page='courseware'" /> <%include file="/courseware/course_navigation.html" args="active_page='courseware'" />
% endif % endif
<div class="container"> <div class="container">
<div class="course-wrapper"> <div class="course-wrapper">
% if accordion: % if disable_accordion is UNDEFINED or not disable_accordion:
<div class="course-index" role="navigation"> <div class="course-index" role="navigation">
<header id="open_close_accordion"> <header id="open_close_accordion">
<a href="#">${_("close")}</a> <a href="#">${_("close")}</a>
......
...@@ -107,18 +107,14 @@ ...@@ -107,18 +107,14 @@
<%include file="mathjax_accessible.html" /> <%include file="mathjax_accessible.html" />
% if not suppress_toplevel_navigation:
<%include file="${header_file}" /> <%include file="${header_file}" />
%endif
<div class="content-wrapper" id="content"> <div class="content-wrapper" id="content">
${self.body()} ${self.body()}
<%block name="bodyextra"/> <%block name="bodyextra"/>
</div> </div>
% if not suppress_toplevel_navigation:
<%include file="${footer_file}" /> <%include file="${footer_file}" />
% endif
<script>window.baseUrl = "${settings.STATIC_URL}";</script> <script>window.baseUrl = "${settings.STATIC_URL}";</script>
<%static:js group='application'/> <%static:js group='application'/>
......
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