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.
This commit is contained in:
@@ -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)]
|
||||
# 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_ids})
|
||||
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>
|
||||
<li class="component ${css_class}" data-locator="${tab.locator}" data-tab-id="${tab.tab_id}"></li>
|
||||
|
||||
% else:
|
||||
|
||||
<%
|
||||
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}">
|
||||
<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>
|
||||
% if tab.is_collection:
|
||||
|
||||
<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>
|
||||
|
||||
@@ -37,7 +37,7 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
|
||||
# Class property that specifies whether the tab is a collection of other tabs
|
||||
is_collection = False
|
||||
|
||||
def __init__(self, name, tab_id, link_func, tab):
|
||||
def __init__(self, name, tab_id, link_func):
|
||||
"""
|
||||
Initializes class members with values passed in by subclasses.
|
||||
|
||||
@@ -57,8 +57,6 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
|
||||
|
||||
self.link_func = link_func
|
||||
|
||||
self.is_hidden = tab.get('is_hidden', False) if tab else False
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff): # pylint: disable=unused-argument
|
||||
"""
|
||||
Determines whether the tab should be displayed in the UI for the given course and a particular user.
|
||||
@@ -105,8 +103,6 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
|
||||
return self.name
|
||||
elif key == 'type':
|
||||
return self.type
|
||||
elif key == 'is_hidden':
|
||||
return self.is_hidden
|
||||
elif key == 'tab_id':
|
||||
return self.tab_id
|
||||
else:
|
||||
@@ -123,8 +119,6 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
|
||||
self.name = value
|
||||
elif key == 'tab_id':
|
||||
self.tab_id = value
|
||||
elif key == 'is_hidden':
|
||||
self.is_hidden = value
|
||||
else:
|
||||
raise KeyError('Key {0} cannot be set in tab {1}'.format(key, self.to_json()))
|
||||
|
||||
@@ -142,8 +136,8 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
|
||||
# allow tabs without names; if a name is required, its presence was checked in the validator.
|
||||
name_is_eq = (other.get('name') is None or self.name == other['name'])
|
||||
|
||||
# only compare the persisted/serialized members: 'type', 'name', and 'is_hidden'
|
||||
return self.type == other.get('type') and name_is_eq and self.is_hidden == other.get('is_hidden', False)
|
||||
# only compare the persisted/serialized members: 'type' and 'name'
|
||||
return self.type == other.get('type') and name_is_eq
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@@ -152,12 +146,12 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
|
||||
return not (self == other)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab, raise_error=True):
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
"""
|
||||
Validates the given dict-type tab object to ensure it contains the expected keys.
|
||||
This method should be overridden by subclasses that require certain keys to be persisted in the tab.
|
||||
"""
|
||||
return key_checker(['type'])(tab, raise_error)
|
||||
return key_checker(['type'])(tab_dict, raise_error)
|
||||
|
||||
def to_json(self):
|
||||
"""
|
||||
@@ -167,13 +161,10 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
|
||||
Returns:
|
||||
a dictionary with keys for the properties of the CourseTab object.
|
||||
"""
|
||||
to_json_val = {'type': self.type, 'name': self.name}
|
||||
if self.is_hidden:
|
||||
to_json_val.update({'is_hidden': True})
|
||||
return to_json_val
|
||||
return {'type': self.type, 'name': self.name}
|
||||
|
||||
@staticmethod
|
||||
def from_json(tab):
|
||||
def from_json(tab_dict):
|
||||
"""
|
||||
Deserializes a CourseTab from a json-like representation.
|
||||
|
||||
@@ -206,15 +197,15 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
|
||||
'instructor': InstructorTab, # not persisted
|
||||
}
|
||||
|
||||
tab_type = tab.get('type')
|
||||
tab_type = tab_dict.get('type')
|
||||
if tab_type not in sub_class_types:
|
||||
raise InvalidTabsException(
|
||||
'Unknown tab type {0}. Known types: {1}'.format(tab_type, sub_class_types)
|
||||
)
|
||||
|
||||
tab_class = sub_class_types[tab['type']]
|
||||
tab_class.validate(tab)
|
||||
return tab_class(tab=tab)
|
||||
tab_class = sub_class_types[tab_dict['type']]
|
||||
tab_class.validate(tab_dict)
|
||||
return tab_class(tab_dict=tab_dict)
|
||||
|
||||
|
||||
class AuthenticatedCourseTab(CourseTab):
|
||||
@@ -233,6 +224,44 @@ class StaffTab(AuthenticatedCourseTab):
|
||||
return is_user_staff
|
||||
|
||||
|
||||
class HideableTab(CourseTab):
|
||||
"""
|
||||
Abstract class for tabs that are hideable
|
||||
"""
|
||||
is_hideable = True
|
||||
|
||||
def __init__(self, name, tab_id, link_func, tab_dict):
|
||||
super(HideableTab, self).__init__(
|
||||
name=name,
|
||||
tab_id=tab_id,
|
||||
link_func=link_func,
|
||||
)
|
||||
self.is_hidden = tab_dict.get('is_hidden', False) if tab_dict else False
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == 'is_hidden':
|
||||
return self.is_hidden
|
||||
else:
|
||||
return super(HideableTab, self).__getitem__(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == 'is_hidden':
|
||||
self.is_hidden = value
|
||||
else:
|
||||
super(HideableTab, self).__setitem__(key, value)
|
||||
|
||||
def to_json(self):
|
||||
to_json_val = super(HideableTab, self).to_json()
|
||||
if self.is_hidden:
|
||||
to_json_val.update({'is_hidden': True})
|
||||
return to_json_val
|
||||
|
||||
def __eq__(self, other):
|
||||
if not super(HideableTab, self).__eq__(other):
|
||||
return False
|
||||
return self.is_hidden == other.get('is_hidden', False)
|
||||
|
||||
|
||||
class CoursewareTab(CourseTab):
|
||||
"""
|
||||
A tab containing the course content.
|
||||
@@ -241,13 +270,12 @@ class CoursewareTab(CourseTab):
|
||||
type = 'courseware'
|
||||
is_movable = False
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(CoursewareTab, self).__init__(
|
||||
# Translators: 'Courseware' refers to the tab in the courseware that leads to the content of a course
|
||||
name=_('Courseware'), # support fixed name for the courseware tab
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func(self.type),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
|
||||
@@ -259,18 +287,17 @@ class CourseInfoTab(CourseTab):
|
||||
type = 'course_info'
|
||||
is_movable = False
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None):
|
||||
super(CourseInfoTab, self).__init__(
|
||||
# Translators: "Course Info" is the name of the course's information and updates page
|
||||
name=tab['name'] if tab else _('Course Info'),
|
||||
name=tab_dict['name'] if tab_dict else _('Course Info'),
|
||||
tab_id='info',
|
||||
link_func=link_reverse_func('info'),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab, raise_error=True):
|
||||
return super(CourseInfoTab, cls).validate(tab, raise_error) and need_name(tab, raise_error)
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
return super(CourseInfoTab, cls).validate(tab_dict, raise_error) and need_name(tab_dict, raise_error)
|
||||
|
||||
|
||||
class ProgressTab(AuthenticatedCourseTab):
|
||||
@@ -280,46 +307,44 @@ class ProgressTab(AuthenticatedCourseTab):
|
||||
|
||||
type = 'progress'
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None):
|
||||
super(ProgressTab, self).__init__(
|
||||
# Translators: "Progress" is the name of the student's course progress page
|
||||
name=tab['name'] if tab else _('Progress'),
|
||||
name=tab_dict['name'] if tab_dict else _('Progress'),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func(self.type),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff):
|
||||
return not course.hide_progress_tab
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab, raise_error=True):
|
||||
return super(ProgressTab, cls).validate(tab, raise_error) and need_name(tab, raise_error)
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
return super(ProgressTab, cls).validate(tab_dict, raise_error) and need_name(tab_dict, raise_error)
|
||||
|
||||
|
||||
class WikiTab(CourseTab):
|
||||
class WikiTab(HideableTab):
|
||||
"""
|
||||
A tab containing the course wiki.
|
||||
A tab_dict containing the course wiki.
|
||||
"""
|
||||
|
||||
type = 'wiki'
|
||||
is_hideable = True
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None):
|
||||
super(WikiTab, self).__init__(
|
||||
# Translators: "Wiki" is the name of the course's wiki page
|
||||
name=tab['name'] if tab else _('Wiki'),
|
||||
name=tab_dict['name'] if tab_dict else _('Wiki'),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func('course_wiki'),
|
||||
tab=tab,
|
||||
tab_dict=tab_dict,
|
||||
)
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff):
|
||||
return settings.WIKI_ENABLED
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab, raise_error=True):
|
||||
return super(WikiTab, cls).validate(tab, raise_error) and need_name(tab, raise_error)
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
return super(WikiTab, cls).validate(tab_dict, raise_error) and need_name(tab_dict, raise_error)
|
||||
|
||||
|
||||
class DiscussionTab(CourseTab):
|
||||
@@ -329,21 +354,20 @@ class DiscussionTab(CourseTab):
|
||||
|
||||
type = 'discussion'
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None):
|
||||
super(DiscussionTab, self).__init__(
|
||||
# Translators: "Discussion" is the title of the course forum page
|
||||
name=tab['name'] if tab else _('Discussion'),
|
||||
name=tab_dict['name'] if tab_dict else _('Discussion'),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func('django_comment_client.forum.views.forum_form_discussion'),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff):
|
||||
return settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE')
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab, raise_error=True):
|
||||
return super(DiscussionTab, cls).validate(tab, raise_error) and need_name(tab, raise_error)
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
return super(DiscussionTab, cls).validate(tab_dict, raise_error) and need_name(tab_dict, raise_error)
|
||||
|
||||
|
||||
class LinkTab(CourseTab):
|
||||
@@ -352,13 +376,12 @@ class LinkTab(CourseTab):
|
||||
"""
|
||||
link_value = ''
|
||||
|
||||
def __init__(self, name, tab_id, link_value, tab):
|
||||
def __init__(self, name, tab_id, link_value):
|
||||
self.link_value = link_value
|
||||
super(LinkTab, self).__init__(
|
||||
name=name,
|
||||
tab_id=tab_id,
|
||||
link_func=link_value_func(self.link_value),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
@@ -384,8 +407,8 @@ class LinkTab(CourseTab):
|
||||
return self.link_value == other.get('link')
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab, raise_error=True):
|
||||
return super(LinkTab, cls).validate(tab, raise_error) and key_checker(['link'])(tab, raise_error)
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
return super(LinkTab, cls).validate(tab_dict, raise_error) and key_checker(['link'])(tab_dict, raise_error)
|
||||
|
||||
|
||||
class ExternalDiscussionTab(LinkTab):
|
||||
@@ -395,13 +418,12 @@ class ExternalDiscussionTab(LinkTab):
|
||||
|
||||
type = 'external_discussion'
|
||||
|
||||
def __init__(self, tab=None, link_value=None):
|
||||
def __init__(self, tab_dict=None, link_value=None):
|
||||
super(ExternalDiscussionTab, self).__init__(
|
||||
# Translators: 'Discussion' refers to the tab in the courseware that leads to the discussion forums
|
||||
name=_('Discussion'),
|
||||
tab_id='discussion',
|
||||
link_value=tab['link'] if tab else link_value,
|
||||
tab=tab,
|
||||
link_value=tab_dict['link'] if tab_dict else link_value,
|
||||
)
|
||||
|
||||
|
||||
@@ -411,12 +433,11 @@ class ExternalLinkTab(LinkTab):
|
||||
"""
|
||||
type = 'external_link'
|
||||
|
||||
def __init__(self, tab):
|
||||
def __init__(self, tab_dict):
|
||||
super(ExternalLinkTab, self).__init__(
|
||||
name=tab['name'],
|
||||
name=tab_dict['name'],
|
||||
tab_id=None, # External links are never active.
|
||||
link_value=tab['link'],
|
||||
tab=tab,
|
||||
link_value=tab_dict['link'],
|
||||
)
|
||||
|
||||
|
||||
@@ -427,16 +448,15 @@ class StaticTab(CourseTab):
|
||||
type = 'static_tab'
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab, raise_error=True):
|
||||
return super(StaticTab, cls).validate(tab, raise_error) and key_checker(['name', 'url_slug'])(tab, raise_error)
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
return super(StaticTab, cls).validate(tab_dict, raise_error) and key_checker(['name', 'url_slug'])(tab_dict, raise_error)
|
||||
|
||||
def __init__(self, tab=None, name=None, url_slug=None):
|
||||
self.url_slug = tab['url_slug'] if tab else url_slug
|
||||
def __init__(self, tab_dict=None, name=None, url_slug=None):
|
||||
self.url_slug = tab_dict['url_slug'] if tab_dict else url_slug
|
||||
super(StaticTab, self).__init__(
|
||||
name=tab['name'] if tab else name,
|
||||
name=tab_dict['name'] if tab_dict else name,
|
||||
tab_id='static_tab_{0}'.format(self.url_slug),
|
||||
link_func=lambda course, reverse_func: reverse_func(self.type, args=[course.id, self.url_slug]),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
@@ -481,12 +501,12 @@ class TextbookTabsBase(AuthenticatedCourseTab):
|
||||
"""
|
||||
is_collection = True
|
||||
|
||||
def __init__(self, tab_id, tab):
|
||||
def __init__(self, tab_id):
|
||||
# Translators: 'Textbooks' refers to the tab in the course that leads to the course' textbooks
|
||||
super(TextbookTabsBase, self).__init__(
|
||||
name=_("Textbooks"),
|
||||
tab_id=tab_id,
|
||||
link_func=None, tab=tab
|
||||
link_func=None,
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
@@ -504,10 +524,9 @@ class TextbookTabs(TextbookTabsBase):
|
||||
"""
|
||||
type = 'textbooks'
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(TextbookTabs, self).__init__(
|
||||
tab_id=self.type,
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff):
|
||||
@@ -519,7 +538,6 @@ class TextbookTabs(TextbookTabsBase):
|
||||
name=textbook.title,
|
||||
tab_id='textbook/{0}'.format(index),
|
||||
link_func=lambda course, reverse_func: reverse_func('book', args=[course.id, index]),
|
||||
tab=None
|
||||
)
|
||||
|
||||
|
||||
@@ -529,10 +547,9 @@ class PDFTextbookTabs(TextbookTabsBase):
|
||||
"""
|
||||
type = 'pdf_textbooks'
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(PDFTextbookTabs, self).__init__(
|
||||
tab_id=self.type,
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
def items(self, course):
|
||||
@@ -541,7 +558,6 @@ class PDFTextbookTabs(TextbookTabsBase):
|
||||
name=textbook['tab_title'],
|
||||
tab_id='pdftextbook/{0}'.format(index),
|
||||
link_func=lambda course, reverse_func: reverse_func('pdf_book', args=[course.id, index]),
|
||||
tab=None
|
||||
)
|
||||
|
||||
|
||||
@@ -551,10 +567,9 @@ class HtmlTextbookTabs(TextbookTabsBase):
|
||||
"""
|
||||
type = 'html_textbooks'
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(HtmlTextbookTabs, self).__init__(
|
||||
tab_id=self.type,
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
def items(self, course):
|
||||
@@ -563,7 +578,6 @@ class HtmlTextbookTabs(TextbookTabsBase):
|
||||
name=textbook['tab_title'],
|
||||
tab_id='htmltextbook/{0}'.format(index),
|
||||
link_func=lambda course, reverse_func: reverse_func('html_book', args=[course.id, index]),
|
||||
tab=None
|
||||
)
|
||||
|
||||
|
||||
@@ -580,14 +594,13 @@ class StaffGradingTab(StaffTab, GradingTab):
|
||||
"""
|
||||
type = 'staff_grading'
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(StaffGradingTab, self).__init__(
|
||||
# Translators: "Staff grading" appears on a tab that allows
|
||||
# staff to view open-ended problems that require staff grading
|
||||
name=_("Staff grading"),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func(self.type),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
|
||||
@@ -597,14 +610,13 @@ class PeerGradingTab(AuthenticatedCourseTab, GradingTab):
|
||||
"""
|
||||
type = 'peer_grading'
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(PeerGradingTab, self).__init__(
|
||||
# Translators: "Peer grading" appears on a tab that allows
|
||||
# students to view open-ended problems that require grading
|
||||
name=_("Peer grading"),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func(self.type),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
|
||||
@@ -614,14 +626,13 @@ class OpenEndedGradingTab(AuthenticatedCourseTab, GradingTab):
|
||||
"""
|
||||
type = 'open_ended'
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(OpenEndedGradingTab, self).__init__(
|
||||
# Translators: "Open Ended Panel" appears on a tab that, when clicked, opens up a panel that
|
||||
# displays information about open-ended problems that a user has submitted or needs to grade
|
||||
name=_("Open Ended Panel"),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func('open_ended_notifications'),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
|
||||
@@ -634,13 +645,12 @@ class SyllabusTab(CourseTab):
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff):
|
||||
return hasattr(course, 'syllabus_present') and course.syllabus_present
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(SyllabusTab, self).__init__(
|
||||
# Translators: "Syllabus" appears on a tab that, when clicked, opens the syllabus of the course.
|
||||
name=_('Syllabus'),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func(self.type),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
|
||||
@@ -653,17 +663,16 @@ class NotesTab(AuthenticatedCourseTab):
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff):
|
||||
return settings.FEATURES.get('ENABLE_STUDENT_NOTES')
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None):
|
||||
super(NotesTab, self).__init__(
|
||||
name=tab['name'],
|
||||
name=tab_dict['name'],
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func(self.type),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab, raise_error=True):
|
||||
return super(NotesTab, cls).validate(tab, raise_error) and need_name(tab, raise_error)
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
return super(NotesTab, cls).validate(tab_dict, raise_error) and need_name(tab_dict, raise_error)
|
||||
|
||||
|
||||
class InstructorTab(StaffTab):
|
||||
@@ -672,14 +681,13 @@ class InstructorTab(StaffTab):
|
||||
"""
|
||||
type = 'instructor'
|
||||
|
||||
def __init__(self, tab=None):
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(InstructorTab, self).__init__(
|
||||
# Translators: 'Instructor' appears on the tab that leads to the instructor dashboard, which is
|
||||
# a portal where an instructor can get data and perform various actions on their course
|
||||
name=_('Instructor'),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func('instructor_dashboard'),
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
|
||||
@@ -771,7 +779,9 @@ class CourseTabList(List):
|
||||
the given user with the provided access settings.
|
||||
"""
|
||||
for tab in course.tabs:
|
||||
if tab.can_display(course, settings, is_user_authenticated, is_user_staff) and not tab.is_hidden:
|
||||
if tab.can_display(
|
||||
course, settings, is_user_authenticated, is_user_staff
|
||||
) and (not tab.is_hideable or not tab.is_hidden):
|
||||
if tab.is_collection:
|
||||
for item in tab.items(course):
|
||||
yield item
|
||||
@@ -787,11 +797,14 @@ class CourseTabList(List):
|
||||
settings
|
||||
):
|
||||
"""
|
||||
Generator method for iterating through all tabs that can be displayed for the given course and
|
||||
the given user with the provided access settings.
|
||||
Generator method for iterating through all tabs that can be displayed for the given course
|
||||
with the provided settings.
|
||||
"""
|
||||
for tab in course.tabs:
|
||||
if tab.can_display(course, settings, is_user_authenticated=True, is_user_staff=True):
|
||||
if tab.is_collection and not len(list(tab.items(course))):
|
||||
# do not yield collections that have no items
|
||||
continue
|
||||
yield tab
|
||||
|
||||
@classmethod
|
||||
@@ -862,7 +875,7 @@ class CourseTabList(List):
|
||||
Overrides the from_json method to de-serialize the CourseTab objects from a json-like representation.
|
||||
"""
|
||||
self.validate_tabs(values)
|
||||
return [CourseTab.from_json(tab) for tab in values]
|
||||
return [CourseTab.from_json(tab_dict) for tab_dict in values]
|
||||
|
||||
|
||||
#### Link Functions
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user