Commit 433b3c64 by Don Mitchell

Merge remote-tracking branch 'origin/feature/cdodge/static-tab-edit'

into courseinfo

Conflicts:
	cms/templates/widgets/header.html
	cms/urls.py
parents cafe9c47 7ca49f3a
......@@ -33,6 +33,31 @@ def get_course_location_for_item(location):
return location
def get_course_for_item(location):
'''
cdodge: for a given Xmodule, return the course that it belongs to
NOTE: This makes a lot of assumptions about the format of the course location
Also we have to assert that this module maps to only one course item - it'll throw an
assert if not
'''
item_loc = Location(location)
# @hack! We need to find the course location however, we don't
# know the 'name' parameter in this context, so we have
# to assume there's only one item in this query even though we are not specifying a name
course_search_location = ['i4x', item_loc.org, item_loc.course, 'course', None]
courses = modulestore().get_items(course_search_location)
# make sure we found exactly one match on this above course search
found_cnt = len(courses)
if found_cnt == 0:
raise BaseException('Could not find course at {0}'.format(course_search_location))
if found_cnt > 1:
raise BaseException('Found more than one course at {0}. There should only be one!!! Dump = {1}'.format(course_search_location, courses))
return courses[0]
def get_lms_link_for_item(location, preview=False):
location = Location(location)
......
......@@ -55,7 +55,7 @@ from cache_toolbox.core import set_cached_content, get_cached_content, del_cache
from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role
from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group
from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME, create_all_course_groups
from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, get_date_display, UnitState
from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, get_date_display, UnitState, get_course_for_item
from xmodule.templates import all_templates
from xmodule.modulestore.xml_importer import import_from_xml
......@@ -66,7 +66,10 @@ log = logging.getLogger(__name__)
COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video']
DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential']
DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info']
# cdodge: these are categories which should not be parented, they are detached from the hierarchy
DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
def _modulestore(location):
......@@ -619,6 +622,17 @@ def save_item(request):
# commit to datastore
store.update_metadata(item_location, existing_item.metadata)
# cdodge: special case logic for updating static_tabs
# unfortunately tabs are enumerated in the course policy data structure, so if we change the display name of
# the tab, we need to update the course policy (which has a nice .tabs property on it)
item_loc = Location(item_location)
if item_loc.category == 'static_tab':
# VS[compat] Rework when we can stop having to support tabs lists in the policy
tag_module = store.get_item(item_location)
course = get_course_for_item(item_location)
course.update_tab_reference(tag_module)
modulestore('direct').update_metadata(course.location, course.metadata)
return HttpResponse()
......@@ -692,7 +706,16 @@ def clone_item(request):
new_item.metadata['display_name'] = display_name
_modulestore(template).update_metadata(new_item.location.url(), new_item.own_metadata)
_modulestore(parent.location).update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()])
if new_item.location.category not in DETACHED_CATEGORIES:
_modulestore(parent.location).update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()])
elif new_item.location.category == 'static_tab':
# static tabs - in our data model - are described in the course policy
# VS[compat]: Rework when we can stop having to support tabs lists in the policy
if parent.location.category != 'course':
raise BaseException('adding a new static_tab must be on a course object')
parent.add_tab_reference(new_item)
_modulestore(parent.location).update_metadata(parent.location.url(), parent.metadata)
return HttpResponse(json.dumps({'id': dest_location.url()}))
......@@ -873,6 +896,25 @@ def edit_static(request, org, course, coursename):
return render_to_response('edit-static-page.html', {})
def edit_tabs(request, org, course, coursename):
location = ['i4x', org, course, 'course', coursename]
course_item = modulestore().get_item(location)
static_tabs_loc = Location('i4x', org, course, 'static_tab', None)
static_tabs = modulestore('direct').get_items(static_tabs_loc)
components = [
static_tab.location.url()
for static_tab
in static_tabs
]
return render_to_response('edit-tabs.html', {
'active_tab': 'pages',
'context_course':course_item,
'components': components
})
def not_found(request):
return render_to_response('error.html', {'error': '404'})
......@@ -1007,6 +1049,17 @@ def create_new_course(request):
# set a default start date to now
new_course.metadata['start'] = stringify_time(time.gmtime())
# set up the default tabs
# I've added this because when we add static tabs, the LMS either expects a None for the tabs list or
# at least a list populated with the minimal times
# @TODO: I don't like the fact that the presentation tier is away of these data related constraints, let's find a better
# place for this. Also rather than using a simple list of dictionaries a nice class model would be helpful here
new_course.tabs = [{"type": "courseware"},
{"type": "course_info", "name": "Course Info"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"}]
modulestore('direct').update_metadata(new_course.location.url(), new_course.own_metadata)
create_all_course_groups(request.user, new_course.location)
......
class CMS.Views.TabsEdit extends Backbone.View
events:
'click .new-tab': 'addNewTab'
initialize: =>
@$('.component').each((idx, element) =>
new CMS.Views.ModuleEdit(
el: element,
onDelete: @deleteTab,
model: new CMS.Models.Module(
id: $(element).data('id'),
)
)
)
@$('.components').sortable(
handle: '.drag-handle'
update: (event, ui) => alert 'not yet implemented!'
helper: 'clone'
opacity: '0.5'
placeholder: 'component-placeholder'
forcePlaceholderSize: true
axis: 'y'
items: '> .component'
)
addNewTab: (event) =>
event.preventDefault()
editor = new CMS.Views.ModuleEdit(
onDelete: @deleteTab
model: new CMS.Models.Module()
)
$('.new-component-item').before(editor.$el)
editor.cloneTemplate(
@model.get('id'),
'i4x://edx/templates/static_tab/Empty'
)
deleteTab: (event) =>
if not confirm 'Are you sure you want to delete this component? This action cannot be undone.'
return
$component = $(event.currentTarget).parents('.component')
$.post('/delete_item', {
id: $component.data('id')
}, =>
$component.remove()
)
../../../common/static/sass/_mixins.scss
\ No newline at end of file
../../../common/static/sass/_mixins.scss
\ No newline at end of file
../../../common/static/sass/bourbon/
\ No newline at end of file
../../../common/static/sass/bourbon/
\ No newline at end of file
<%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %>
<%block name="title">Tabs</%block>
<%block name="bodyclass">static-pages</%block>
<%block name="jsextra">
<script type='text/javascript'>
new CMS.Views.TabsEdit({
el: $('.main-wrapper'),
model: new CMS.Models.Module({
id: '${context_course.location}'
})
});
</script>
</%block>
<%block name="content">
<div class="main-wrapper">
<div class="inner-wrapper">
<div>
<h1>Static Tabs</h1>
</div>
<div class="main-column">
<article class="unit-body window">
<div class="tab-list">
<ol class='components'>
% for id in components:
<li class="component" data-id="${id}"/>
% endfor
<li class="new-component-item">
<a href="#" class="new-component-button new-tab">
<span class="plus-icon"></span>New Tab
</a>
</li>
</ol>
</div>
</article>
</div>
</div>
</div>
</%block>
\ No newline at end of file
......@@ -11,7 +11,7 @@
<ul class="class-nav">
<li><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseware-tab'>Courseware</a></li>
<li><a href="${reverse('course_info', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseinfo-tab'>Course Info</a></li>
<li><a href="${reverse('static_pages', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}" id='pages-tab' style="display:none">Pages</a></li>
<li><a href="${reverse('edit_tabs', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}" id='pages-tab'>Tabs</a></li>
<li><a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='assets-tab'>Assets</a></li>
<li><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}" id='users-tab'>Users</a></li>
<li><a href="${reverse('import_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='import-tab'>Import</a></li>
......
......@@ -39,6 +39,7 @@ urlpatterns = ('',
url(r'^edit_static/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.edit_static',
name='edit_static'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', 'contentstore.views.course_info', name='course_info'),
url(r'^edit_tabs/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.edit_tabs', name='edit_tabs'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/assets/(?P<name>[^/]+)$', 'contentstore.views.asset_index', name='asset_index'),
# temporary landing page for a course
......
......@@ -35,10 +35,10 @@ setup(
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"videosequence = xmodule.seq_module:SequenceDescriptor",
"discussion = xmodule.discussion_module:DiscussionDescriptor",
"course_info = xmodule.html_module:HtmlDescriptor",
"static_tab = xmodule.html_module:HtmlDescriptor",
"course_info = xmodule.html_module:CourseInfoDescriptor",
"static_tab = xmodule.html_module:StaticTabDescriptor",
"custom_tag_template = xmodule.raw_module:RawDescriptor",
"about = xmodule.html_module:HtmlDescriptor"
"about = xmodule.html_module:AboutDescriptor"
]
}
)
......@@ -266,6 +266,33 @@ class CourseDescriptor(SequenceDescriptor):
"""
return self.metadata.get('tabs')
@tabs.setter
def tabs(self, value):
self.metadata['tabs'] = value
def add_tab_reference(self, tab_module):
'''
Since in CMS we can add static tabs via data, the current data model expects that
the list of tabs be encapsulated in the course.
VS[compat] we can rework this to avoid pointers to static tab modules once we fully deprecate filesystem support
'''
existing_tabs = self.tabs or []
existing_tabs.append({'type':'static_tab', 'name' : tab_module.metadata.get('display_name'), 'url_slug' : tab_module.location.name})
self.tabs = existing_tabs
def update_tab_reference(self, tab_module):
'''
Since in CMS we can add static tabs via data, the current data model expects that
the list of tabs be encapsulated in the course.
VS[compat] we can rework this to avoid pointers to static tab modules once we fully deprecate filesystem support
'''
existing_tabs = self.tabs
for tab in existing_tabs:
if tab.get('url_slug') == tab_module.location.name:
tab['name'] = tab_module.metadata.get('display_name')
self.tabs = existing_tabs
@property
def show_calculator(self):
return self.metadata.get("show_calculator", None) == "Yes"
......
......@@ -170,3 +170,25 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor):
elt = etree.Element('html')
elt.set("filename", relname)
return elt
class AboutDescriptor(HtmlDescriptor):
"""
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
in order to be able to create new ones
"""
template_dir_name = "about"
class StaticTabDescriptor(HtmlDescriptor):
"""
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
in order to be able to create new ones
"""
template_dir_name = "statictab"
class CourseInfoDescriptor(HtmlDescriptor):
"""
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
in order to be able to create new ones
"""
template_dir_name = "courseinfo"
---
metadata:
display_name: Empty
data: "This is where you can add additional information about your course."
children: []
\ No newline at end of file
---
metadata:
display_name: Empty
data: "This is where you can add additional information about your course."
children: []
\ No newline at end of file
---
metadata:
display_name: Empty
data: "This is where you can add additional pages to your courseware. Click the 'edit' button to begin editing."
children: []
\ No newline at end of file
roots/2012_Fall.xml
\ No newline at end of file
<course org="edX" course="graded" url_name="2012_Fall"/>
\ No newline at end of file
roots/2012_Fall.xml
\ No newline at end of file
<course org="edX" course="test_start_date" url_name="2012_Fall"/>
\ No newline at end of file
roots/2012_Fall.xml
\ No newline at end of file
<course org="edX" course="toy" url_name="2012_Fall"/>
\ No newline at end of file
../../../common/static/images/edge-logo-small.png
\ No newline at end of file
../../../common/static/images/edge-logo-small.png
\ No newline at end of file
../../../../common/static/sass/_mixins.scss
\ No newline at end of file
../../../../common/static/sass/_mixins.scss
\ No newline at end of file
../../../common/static/sass/bourbon/
\ No newline at end of file
../../../common/static/sass/bourbon/
\ No newline at end of file
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