Commit 1bd6792a by Nimisha Asthagiri

Support for "View Live" on Pages page.

Settings for ENABLE_STUDENT_NOTES and ENABLE_TEXTBOOK in CMS.
Do not display empty collection tabs.
Updated Changelog.
parent 3b0f7148
......@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
Studio: Add ability to reorder Pages and hide the Wiki page. STUD-1375
Blades: Added template for iFrames. BLD-611.
Studio: Support for viewing built-in tabs on the Pages page. STUD-1193
......
......@@ -27,26 +27,26 @@ Feature: CMS.Pages
@skip_safari
Scenario: Users can reorder static pages
Given I have created two different static pages
When I reorder the static pages
Then the static pages are in the reverse order
When I drag the first static page to the last
Then the static pages are switched
And I reload the page
Then the static pages are in the reverse order
Then the static pages are switched
Scenario: Users can reorder built-in pages
Given I have opened the pages page in a new course
Then the built-in pages are in the default order
When I reorder the pages
Then the built-in pages are in the reverse order
When I drag the first page to the last
Then the built-in pages are switched
And I reload the page
Then the built-in pages are in the reverse order
Then the built-in pages are switched
Scenario: Users can reorder built-in pages amongst static pages
Given I have created two different static pages
Then the pages are in the default order
When I reorder the pages
Then the pages are in the reverse order
When I drag the first page to the last
Then the pages are switched
And I reload the page
Then the pages are in the reverse order
Then the pages are switched
Scenario: Users can toggle visibility on hideable pages
Given I have opened the pages page in a new course
......
......@@ -6,6 +6,9 @@ from lettuce import world, step
from nose.tools import assert_equal, assert_in # pylint: disable=E0611
CSS_FOR_TAB_ELEMENT = "li[data-tab-id='{0}'] input.toggle-checkbox"
@step(u'I go to the pages page$')
def go_to_static(step):
menu_css = 'li.nav-course-courseware'
......@@ -51,24 +54,24 @@ def change_name(step, new_name):
world.css_click(save_button)
@step(u'I reorder the static pages')
def reorder_static_pages(_step):
reorder_pages_with_css_class('.component')
@step(u'I drag the first static page to the last$')
def drag_first_static_page_to_last(step):
drag_first_to_last_with_css('.component')
@step(u'I have created a static page')
@step(u'I have created a static page$')
def create_static_page(step):
step.given('I have opened the pages page in a new course')
step.given('I add a new static page')
@step(u'I have opened the pages page in a new course')
@step(u'I have opened the pages page in a new course$')
def open_pages_page_in_new_course(step):
step.given('I have opened a new course in Studio')
step.given('I go to the pages page')
@step(u'I have created two different static pages')
@step(u'I have created two different static pages$')
def create_two_pages(step):
step.given('I have created a static page')
step.given('I "edit" the static page')
......@@ -78,8 +81,8 @@ def create_two_pages(step):
_verify_page_names('First', 'Empty')
@step(u'the static pages are in the reverse order')
def static_pages_in_reverse_order(step):
@step(u'the static pages are switched$')
def static_pages_are_switched(step):
_verify_page_names('Empty', 'First')
......@@ -90,51 +93,51 @@ def _verify_page_names(first, second):
timeout_msg="Timed out waiting for two pages to be present"
)
pages = world.css_find('.xmodule_StaticTabModule')
assert pages[0].text == first
assert pages[1].text == second
assert_equal(pages[0].text, first)
assert_equal(pages[1].text, second)
@step(u'the built-in pages are in the default order')
@step(u'the built-in pages are in the default order$')
def built_in_pages_in_default_order(step):
expected_pages = ['Courseware', 'Course Info', 'Discussion', 'Wiki', 'Progress']
see_pages_in_expected_order(expected_pages)
@step(u'the built-in pages are in the reverse order')
def built_in_pages_in_reverse_order(step):
@step(u'the built-in pages are switched$')
def built_in_pages_switched(step):
expected_pages = ['Courseware', 'Course Info', 'Wiki', 'Progress', 'Discussion']
see_pages_in_expected_order(expected_pages)
@step(u'the pages are in the default order')
@step(u'the pages are in the default order$')
def pages_in_default_order(step):
expected_pages = ['Courseware', 'Course Info', 'Discussion', 'Wiki', 'Progress', 'First', 'Empty']
see_pages_in_expected_order(expected_pages)
@step(u'the pages are in the reverse order')
def pages_in_reverse_order(step):
@step(u'the pages are switched$$')
def pages_are_switched(step):
expected_pages = ['Courseware', 'Course Info', 'Wiki', 'Progress', 'First', 'Empty', 'Discussion']
see_pages_in_expected_order(expected_pages)
@step(u'I reorder the pages')
def reorder_pages(step):
reorder_pages_with_css_class('.sortable-tab')
@step(u'I drag the first page to the last$')
def drag_first_page_to_last(step):
drag_first_to_last_with_css('.is-movable')
@step(u'I should see the "([^"]*)" page as "(visible|hidden)"$')
def page_is_visible_or_hidden(step, page_id, visible_or_hidden):
hidden = visible_or_hidden == "hidden"
assert world.css_find("li[data-tab-id='{0}'] input.toggle-checkbox".format(page_id)).checked == hidden
assert_equal(world.css_find(CSS_FOR_TAB_ELEMENT.format(page_id)).checked, hidden)
@step(u'I toggle the visibility of the "([^"]*)" page')
@step(u'I toggle the visibility of the "([^"]*)" page$')
def page_toggle_visibility(step, page_id):
world.css_find("li[data-tab-id='{0}'] input.toggle-checkbox".format(page_id))[0].click()
world.css_find(CSS_FOR_TAB_ELEMENT.format(page_id))[0].click()
def reorder_pages_with_css_class(css_class):
def drag_first_to_last_with_css(css_class):
# For some reason, the drag_and_drop method did not work in this case.
draggables = world.css_find(css_class + ' .drag-handle')
source = draggables.first
......@@ -149,4 +152,3 @@ def see_pages_in_expected_order(page_names_in_expected_order):
assert_equal(len(page_names_in_expected_order), len(pages))
for i, page_name in enumerate(page_names_in_expected_order):
assert_in(page_name, pages[i].text)
......@@ -150,7 +150,7 @@ class TestGitExport(CourseTestCase):
'--format=%an|%ae'], cwd=cwd)
self.assertEqual(expect_string, git_log)
# Make changes to course so there is something commit
# Make changes to course so there is something to commit
self.populate_course()
git_export_utils.export_to_git(
self.course.id,
......
......@@ -409,9 +409,19 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
else:
built_in_tabs.append(tab)
tab_ids = [{'tab_id': tab.tab_id} for tab in (built_in_tabs + reverse_static_tabs)]
self.client.ajax_post(new_location.url_reverse('tabs'), {'tabs': tab_ids})
# create the requested tab_id_locators list
tab_id_locators = [
{
'tab_id': tab.tab_id
} for tab in built_in_tabs
]
tab_id_locators.extend([
{
'tab_locator': unicode(self._get_tab_locator(course, tab))
} for tab in reverse_static_tabs
])
self.client.ajax_post(new_location.url_reverse('tabs'), {'tabs': tab_id_locators})
course = module_store.get_item(course_location)
......
......@@ -918,9 +918,9 @@ def textbooks_detail_handler(request, tid, tag=None, package_id=None, branch=Non
if not textbook:
return JsonResponse(status=404)
i = course.pdf_textbooks.index(textbook)
new_textbooks = course.pdf_textbooks[0:i]
new_textbooks.extend(course.pdf_textbooks[i + 1:])
course.pdf_textbooks = new_textbooks
remaining_textbooks = course.pdf_textbooks[0:i]
remaining_textbooks.extend(course.pdf_textbooks[i + 1:])
course.pdf_textbooks = remaining_textbooks
store.update_item(course, request.user.id)
return JsonResponse()
......
......@@ -16,7 +16,7 @@ from xmodule.modulestore.django import loc_mapper
from xmodule.modulestore.locator import BlockUsageLocator
from xmodule.tabs import CourseTabList, StaticTab, CourseTab, InvalidTabsException
from ..utils import get_modulestore
from ..utils import get_modulestore, get_lms_link_for_item
__all__ = ['tabs_handler']
......@@ -69,16 +69,16 @@ def tabs_handler(request, tag=None, package_id=None, branch=None, version_guid=N
if isinstance(tab, StaticTab):
# static tab needs its locator information to render itself as an xmodule
static_tab_loc = old_location.replace(category='static_tab', name=tab.url_slug)
static_tab = modulestore('direct').get_item(static_tab_loc)
tab.locator = loc_mapper().translate_location(
course_item.location.course_id, static_tab.location, False, True
course_item.location.course_id, static_tab_loc, False, True
)
tabs_to_render.append(tab)
return render_to_response('edit-tabs.html', {
'context_course': course_item,
'tabs_to_render': tabs_to_render,
'course_locator': locator
'course_locator': locator,
'lms_link': get_lms_link_for_item(course_item.location),
})
else:
return HttpResponseNotFound()
......@@ -93,14 +93,14 @@ def reorder_tabs_handler(course_item, request):
# The locators are used to identify static tabs since they are xmodules.
# Although all tabs have tab_ids, newly created static tabs do not know
# their tab_ids since the xmodule editor uses only locators to identify new objects.
ids_locators_of_new_tab_order = request.json['tabs']
requested_tab_id_locators = request.json['tabs']
# original tab list in original order
old_tab_list = course_item.tabs
# create a new list in the new order
new_tab_list = []
for tab_id_locator in ids_locators_of_new_tab_order:
for tab_id_locator in requested_tab_id_locators:
tab = get_tab_by_tab_id_locator(old_tab_list, tab_id_locator)
if tab is None:
return JsonResponse(
......
......@@ -48,14 +48,17 @@ class TabsPageTests(CourseTestCase):
with self.assertRaises(NotImplementedError):
self.client.ajax_post(
self.url,
data={'tab_id': WikiTab.type, 'unsupported_request': None}
data=json.dumps({
'tab_id_locator': {'tab_id': WikiTab.type},
'unsupported_request': None,
}),
)
# invalid JSON POST request
with self.assertRaises(NotImplementedError):
self.client.ajax_post(
self.url,
data={'invalid_request': None}
data={'invalid_request': None},
)
def test_view_index(self):
......@@ -63,7 +66,7 @@ class TabsPageTests(CourseTestCase):
resp = self.client.get_html(self.url)
self.assertEqual(resp.status_code, 200)
self.assertIn('course-nav-tab-list', resp.content)
self.assertIn('course-nav-list', resp.content)
def test_reorder_tabs(self):
"""Test re-ordering of tabs"""
......@@ -87,7 +90,7 @@ class TabsPageTests(CourseTestCase):
# post the request
resp = self.client.ajax_post(
self.url,
data={'tabs': [{'tab_id': tab_id} for tab_id in tab_ids]}
data={'tabs': [{'tab_id': tab_id} for tab_id in tab_ids]},
)
self.assertEqual(resp.status_code, 204)
......@@ -109,7 +112,7 @@ class TabsPageTests(CourseTestCase):
# post the request
resp = self.client.ajax_post(
self.url,
data={'tabs': [{'tab_id': tab_id} for tab_id in tab_ids]}
data={'tabs': [{'tab_id': tab_id} for tab_id in tab_ids]},
)
self.assertEqual(resp.status_code, 400)
resp_content = json.loads(resp.content)
......@@ -123,7 +126,7 @@ class TabsPageTests(CourseTestCase):
# post the request
resp = self.client.ajax_post(
self.url,
data={'tabs': [{'tab_id': tab_id} for tab_id in invalid_tab_ids]}
data={'tabs': [{'tab_id': tab_id} for tab_id in invalid_tab_ids]},
)
self.check_invalid_tab_id_response(resp)
......@@ -141,7 +144,7 @@ class TabsPageTests(CourseTestCase):
self.url,
data=json.dumps({
'tab_id_locator': {'tab_id': old_tab.tab_id},
'is_hidden': new_is_hidden_setting
'is_hidden': new_is_hidden_setting,
}),
)
self.assertEqual(resp.status_code, 204)
......
......@@ -43,9 +43,11 @@ FEATURES = {
'GITHUB_PUSH': False,
# for consistency in user-experience, keep the value of this setting in sync with the
# one in lms/envs/common.py
# for consistency in user-experience, keep the value of the following 3 settings
# in sync with the ones in lms/envs/common.py
'ENABLE_DISCUSSION_SERVICE': True,
'ENABLE_TEXTBOOK': True,
'ENABLE_STUDENT_NOTES': True,
'AUTH_USE_CERTIFICATES': False,
......
......@@ -19,7 +19,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
@options.mast.find('.new-tab').on('click', @addNewTab)
$('.add-pages .new-tab').on('click', @addNewTab)
$('.toggle-checkbox').on('click', @toggleVisibilityOfTab)
@$('.course-nav-tab-list').sortable(
@$('.course-nav-list').sortable(
handle: '.drag-handle'
update: @tabMoved
helper: 'clone'
......@@ -27,7 +27,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
placeholder: 'component-placeholder'
forcePlaceholderSize: true
axis: 'y'
items: '> .sortable-tab'
items: '> .is-movable'
)
toggleVisibilityOfTab: (event, ui) =>
......@@ -85,7 +85,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
)
$('.new-component-item').before(editor.$el)
editor.$el.addClass('course-tab sortable-tab')
editor.$el.addClass('course-tab is-movable')
editor.$el.addClass('new')
setTimeout(=>
editor.$el.removeClass('new')
......
......@@ -40,7 +40,10 @@
<h3 class="sr">${_("Page Actions")}</h3>
<ul>
<li class="nav-item">
<a href="#" class="button new-button new-tab"><i class="icon-plus"></i> ${_("New Page")}</a>
<a href="#" class="button new-button new-tab"><i class="icon-plus"></i> ${_("New Page")}</a>
</li>
<li class="nav-item">
<a href="${lms_link}" rel="external" class="button view-button view-live-button">${_("View Live")}</a>
</li>
</ul>
</nav>
......@@ -58,39 +61,34 @@
<ol class="course-nav-list course components">
% for tab in tabs_to_render:
<%
css_class = "course-tab"
if tab.is_movable:
css_class = css_class + " is-movable"
elif (not tab.is_movable) and (not tab.is_hideable):
css_class = css_class + " is-fixed"
%>
% if isinstance(tab, StaticTab):
<li class="component course-tab sortable-tab" data-locator="${tab.locator}" data-tab-id="${tab.tab_id}"></li>
% else:
<li class="component ${css_class}" data-locator="${tab.locator}" data-tab-id="${tab.tab_id}"></li>
<%
tab_name = _(tab.name)
item_names_formatted = ""
item_names = []
num_items = 0
if tab.is_collection:
item_names = [_(item.name) for item in tab.items(context_course)]
num_items = sum(1 for item in tab.items(context_course))
css_class = "course-nav-item course-nav-tab course-tab"
if tab.is_movable:
css_class = css_class + " sortable-tab"
%>
% if tab.is_hideable or tab.is_movable:
<li class="${css_class}" data-tab-id="${tab.tab_id}">
% else:
<li class="course-nav-item ${css_class}" data-tab-id="${tab.tab_id}">
<div class="course-nav-item-header">
% if tab.is_collection:
<h3 class="title-sub">${tab_name}</h3>
<ul class="course-nav-item-children">
% for item_name in item_names:
<li class="course-nav-item-child title">
${item_name}
</li>
% endfor
</ul>
<h3 class="title-sub">${_(tab.name)}</h3>
<ul class="course-nav-item-children">
% for item in tab.items(context_course):
<li class="course-nav-item-child title">
${_(item.name)}
</li>
% endfor
</ul>
% else:
<h3 class="title">${tab_name}</h3>
<h3 class="title">${_(tab.name)}</h3>
% endif
</div>
......@@ -99,13 +97,13 @@
% if tab.is_hideable:
<li class="action-item action-visible">
<label><span class="sr">${_("Show this page")}</span></label>
% if tab.is_hidden:
<input type="checkbox" class="toggle-checkbox" data-tooltip="${_('Show/hide page')}" checked />
% else:
<input type="checkbox" class="toggle-checkbox" data-tooltip="${_('Show/hide page')}" />
% endif
<div class="action-button"><i class="icon-eye-open"></i><i class="icon-eye-close"></i></div>
<label><span class="sr">${_("Show this page")}</span></label>
% if tab.is_hidden:
<input type="checkbox" class="toggle-checkbox" data-tooltip="${_('Show/hide page')}" checked />
% else:
<input type="checkbox" class="toggle-checkbox" data-tooltip="${_('Show/hide page')}" />
% endif
<div class="action-button"><i class="icon-eye-open"></i><i class="icon-eye-close"></i></div>
</li>
% endif
......@@ -123,30 +121,7 @@
% endif
</li>
% else:
<li class="course-nav-item course_tab is-fixed" data-tab-id="${tab.tab_id}">
<div class="course-nav-item-header">
<h3 class="title">${tab_name}</h3>
% if tab.is_collection:
<ul class="course-nav-item-children">
% for item_name in item_names:
<li class="course-nav-item-child">
${item_name}
</li>
% endfor
</ul>
% endif
</ul>
</div>
<div class="drag-handle is-fixed" data-tooltip="${_('This page cannot be reordered')}">
<span class="sr">${_("This page cannot be reordered")}</span>
</div>
</li>
% endif
% endif
% endif
% endfor
<li class="new-component-item"></li>
......
......@@ -17,7 +17,7 @@ class TabTestCase(unittest.TestCase):
self.books = None
def set_up_books(self, num_books):
"""initializes the textbooks in the course and adds the given number of books to each textbook"""
"""Initializes the textbooks in the course and adds the given number of books to each textbook"""
self.books = [MagicMock() for _ in range(num_books)]
for book_index, book in enumerate(self.books):
book.title = 'Book{0}'.format(book_index)
......@@ -76,7 +76,7 @@ class TabTestCase(unittest.TestCase):
return tab
def check_tab_equality(self, tab, dict_tab):
"""tests the equality methods on the given tab"""
"""Tests the equality methods on the given tab"""
self.assertEquals(tab, dict_tab) # test __eq__
ne_dict_tab = dict_tab
ne_dict_tab['type'] = 'fake_type'
......@@ -84,13 +84,13 @@ class TabTestCase(unittest.TestCase):
self.assertNotEquals(tab, {'fake_key': 'fake_value'}) # test __ne__: missing type
def check_tab_json_methods(self, tab):
"""tests the json from and to methods on the given tab"""
"""Tests the json from and to methods on the given tab"""
serialized_tab = tab.to_json()
deserialized_tab = tab.from_json(serialized_tab)
self.assertEquals(serialized_tab, deserialized_tab)
def check_can_display_results(self, tab, expected_value=True, for_authenticated_users_only=False, for_staff_only=False):
"""checks can display results for various users"""
"""Checks can display results for various users"""
if for_staff_only:
self.assertEquals(
expected_value,
......@@ -108,7 +108,7 @@ class TabTestCase(unittest.TestCase):
)
def check_get_and_set_methods(self, tab):
"""test __getitem__ and __setitem__ calls"""
"""Test __getitem__ and __setitem__ calls"""
self.assertEquals(tab['type'], tab.type)
self.assertEquals(tab['tab_id'], tab.tab_id)
with self.assertRaises(KeyError):
......@@ -120,7 +120,7 @@ class TabTestCase(unittest.TestCase):
tab['invalid_key'] = 'New Value'
def check_get_and_set_method_for_key(self, tab, key):
"""test __getitem__ and __setitem__ for the given key"""
"""Test __getitem__ and __setitem__ for the given key"""
old_value = tab[key]
new_value = 'New Value'
tab[key] = new_value
......@@ -540,7 +540,7 @@ class CourseTabListTestCase(TabListTestCase):
)):
self.assertEquals(tab.type, self.course.tabs[i].type)
# enumerate the tabs and verify textbooks and the instructor tab
# enumerate the tabs and verify textbooks and the instructor tab
for i, tab in enumerate(tabs.CourseTabList.iterate_displayable(
self.course,
self.settings,
......@@ -555,8 +555,21 @@ class CourseTabListTestCase(TabListTestCase):
# all other tabs must match the expected type
self.assertEquals(tab.type, self.course.tabs[i].type)
# test including non-empty collections
self.assertIn(
tabs.HtmlTextbookTabs(),
list(tabs.CourseTabList.iterate_displayable_cms(self.course, self.settings)),
)
# test not including empty collections
self.course.html_textbooks = []
self.assertNotIn(
tabs.HtmlTextbookTabs(),
list(tabs.CourseTabList.iterate_displayable_cms(self.course, self.settings)),
)
def test_get_tab_by_methods(self):
"""tests the get_tab methods in CourseTabList"""
"""Tests the get_tab methods in CourseTabList"""
self.course.tabs = self.all_valid_tab_list
for tab in self.course.tabs:
......@@ -587,7 +600,7 @@ class DiscussionLinkTestCase(TabTestCase):
@staticmethod
def _reverse(course):
"""custom reverse function"""
"""Custom reverse function"""
def reverse_discussion_link(viewname, args):
"""reverse lookup for discussion link"""
if viewname == "django_comment_client.forum.views.forum_form_discussion" and args == [course.id]:
......
......@@ -76,10 +76,11 @@ FEATURES = {
'FORCE_UNIVERSITY_DOMAIN': False, # set this to the university domain to use, as an override to HTTP_HOST
# set to None to do no university selection
'ENABLE_TEXTBOOK': True,
# for consistency in user-experience, keep the value of this setting in sync with the one in cms/envs/common.py
# for consistency in user-experience, keep the value of the following 3 settings
# in sync with the corresponding ones in cms/envs/common.py
'ENABLE_DISCUSSION_SERVICE': True,
'ENABLE_TEXTBOOK': True,
'ENABLE_STUDENT_NOTES': True, # enables the student notes API and UI.
# discussion home panel, which includes a subscription on/off setting for discussion digest emails.
# this should remain off in production until digest notifications are online.
......@@ -146,9 +147,6 @@ FEATURES = {
# segment.io for LMS--need to explicitly turn it on for production.
'SEGMENT_IO_LMS': False,
# Enables the student notes API and UI.
'ENABLE_STUDENT_NOTES': True,
# Provide a UI to allow users to submit feedback from the LMS (left-hand help modal)
'ENABLE_FEEDBACK_SUBMISSION': False,
......
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