Commit 25544132 by Calen Pennington

Hook up link to create modules from templates

parent 912d6cd6
from xmodule.templates import update_templates
update_templates()
......@@ -20,6 +20,8 @@ from xmodule.modulestore.django import modulestore
from xmodule_modifiers import replace_static_urls, wrap_xmodule
from xmodule.exceptions import NotFoundError
from functools import partial
from itertools import groupby
from operator import attrgetter
log = logging.getLogger(__name__)
......@@ -128,6 +130,33 @@ def edit_item(request):
})
@login_required
def new_item(request):
"""
Display a page where the user can create a new item from a template
Expects a GET request with the parameter 'parent_location', which is the element to add
the newly created item to as a child.
parent_location: A Location URL
"""
parent_location = request.GET['parent_location']
if not has_access(request.user, parent_location):
raise Http404
parent = modulestore().get_item(parent_location)
templates = modulestore().get_items(Location('i4x', 'edx', 'templates'))
templates.sort(key=attrgetter('location.category', 'display_name'))
return render_to_response('new_item.html', {
'parent_name': parent.display_name,
'parent_location': parent.location.url(),
'templates': groupby(templates, attrgetter('location.category')),
})
def user_author_string(user):
'''Get an author string for commits by this user. Format:
first last <email@email.com>.
......@@ -262,7 +291,7 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
module = descriptor.xmodule_constructor(system)(instance_state, shared_state)
module.get_html = replace_static_urls(
wrap_xmodule(module.get_html, module, "xmodule_display.html"),
module.metadata['data_dir']
module.metadata.get('data_dir', module.location.course)
)
save_preview_state(request, preview_id, descriptor.location.url(),
module.get_instance_state(), module.get_shared_state())
......@@ -326,3 +355,24 @@ def save_item(request):
preview_html = get_module_previews(request, descriptor)
return HttpResponse(json.dumps(preview_html))
@login_required
@expect_json
def clone_item(request):
parent_location = Location(request.POST['parent_location'])
template = Location(request.POST['template'])
display_name = request.POST['name']
if not has_access(request.user, parent_location):
raise Http404 # TODO (vshnayder): better error
parent = modulestore().get_item(parent_location)
dest_location = parent_location._replace(category=template.category, name=Location.clean_for_url_name(display_name))
new_item = modulestore().clone_item(template, dest_location)
new_item.metadata['display_name'] = display_name
modulestore().update_metadata(new_item.location.url(), new_item.own_metadata)
modulestore().update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()])
return HttpResponse()
......@@ -210,7 +210,7 @@ for dir_ in (js_file_dir, css_file_dir):
js_fragments = set()
css_fragments = defaultdict(set)
for descriptor in XModuleDescriptor.load_classes() + [RawDescriptor]:
for _, descriptor in XModuleDescriptor.load_classes() + [(None, RawDescriptor)]:
descriptor_js = descriptor.get_javascript()
module_js = descriptor.module_class.get_javascript()
......
class CMS.Models.NewModule extends Backbone.Model
url: '/clone_item'
newUrl: ->
"/new_item?#{$.param(parent_location: @get('parent_location'))}"
......@@ -7,7 +7,8 @@ class CMS.Views.Module extends Backbone.View
previewType = @$el.data('preview-type')
moduleType = @$el.data('type')
CMS.replaceView new CMS.Views.ModuleEdit
model: new CMS.Models.Module
id: @$el.data('id')
type: if moduleType == 'None' then null else moduleType
previewType: if previewType == 'None' then null else previewType
model: new CMS.Models.Module
id: @$el.data('id')
type: if moduleType == 'None' then null else moduleType
previewType: if previewType == 'None' then null else previewType
class CMS.Views.ModuleAdd extends Backbone.View
tagName: 'section'
className: 'add-pane'
events:
'click .cancel': 'cancel'
'click .save': 'save'
initialize: ->
@$el.load @model.newUrl()
save: (event) ->
event.preventDefault()
@model.save({
name: @$el.find('.name').val()
template: $(event.target).data('template-id')
}, {
success: -> CMS.popView()
error: -> alert('Create failed')
})
cancel: (event) ->
event.preventDefault()
CMS.popView()
......@@ -39,7 +39,7 @@ class CMS.Views.ModuleEdit extends Backbone.View
)
XModule.loadModules('display')
).fail(->
).fail( ->
alert("There was an error saving your changes. Please try again.")
)
......
class CMS.Views.Week extends Backbone.View
events:
'click .week-edit': 'edit'
'click .new-module': 'new'
initialize: ->
CMS.on('content.show', @resetHeight)
......@@ -23,3 +24,9 @@ class CMS.Views.Week extends Backbone.View
resetHeight: =>
@$el.height('')
new: (event) =>
event.preventDefault()
CMS.replaceView new CMS.Views.ModuleAdd
model: new CMS.Models.NewModule
parent_location: @$el.data('id')
<section>
<div>${parent_name}</div>
<div>${parent_location}</div>
<input type="text" class="name"/>
<div>
% for module_type, module_templates in templates:
<div>
<div>${module_type}</div>
<div>
% for template in module_templates:
<a class="save" data-template-id="${template.location.url()}">${template.display_name}</a>
% endfor
</div>
</div>
% endfor
</div>
<a class='cancel'>Cancel</a>
</section>
......@@ -7,7 +7,7 @@
<form>
<ul>
<li>
<input type="text" name="" id="" placeholder="Moldule title" />
<input type="text" name="" id="" placeholder="Module title" />
</li>
<li>
<select>
......
......@@ -9,8 +9,10 @@ import django.contrib.auth.views
urlpatterns = ('',
url(r'^$', 'contentstore.views.index', name='index'),
url(r'^new_item$', 'contentstore.views.new_item', name='new_item'),
url(r'^edit_item$', 'contentstore.views.edit_item', name='edit_item'),
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
url(r'^clone_item$', 'contentstore.views.clone_item', name='clone_item'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$',
'contentstore.views.course_index', name='course_index'),
url(r'^github_service_hook$', 'github_sync.views.github_post_receive'),
......
......@@ -6,7 +6,7 @@ import sys
from lxml import etree
from path import path
from .x_module import XModule
from .x_module import XModule, Template
from .xml_module import XmlDescriptor, name_to_pathname
from .editing_module import EditingDescriptor
from .stringify import stringify_children
......@@ -34,6 +34,10 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor):
module_class = HtmlModule
filename_extension = "xml"
templates = [
Template('Empty', '', [])
]
# VS[compat] TODO (cpennington): Delete this method once all fall 2012 course
# are being edited in the cms
@classmethod
......
......@@ -297,8 +297,11 @@ class ModuleStore(object):
"""
raise NotImplementedError
# TODO (cpennington): Replace with clone_item
def create_item(self, location, editor):
def clone_item(self, source, location):
"""
Clone a new item that is a copy of the item at the location `source`
and writes it to `location`
"""
raise NotImplementedError
def update_item(self, location, data):
......
......@@ -237,20 +237,16 @@ class MongoModuleStore(ModuleStoreBase):
return self._load_items(list(items), depth)
# TODO (cpennington): This needs to be replaced by clone_item as soon as we allow
# creation of items from the cms
def create_item(self, location):
def clone_item(self, source, location):
"""
Create an empty item at the specified location.
If that location already exists, raises a DuplicateItemError
location: Something that can be passed to Location
Clone a new item that is a copy of the item at the location `source`
and writes it to `location`
"""
try:
self.collection.insert({
'_id': Location(location).dict(),
})
source_item = self.collection.find_one(location_to_query(source))
source_item['_id'] = Location(location).dict()
self.collection.insert(source_item)
return self._load_items([source_item])[0]
except pymongo.errors.DuplicateKeyError:
raise DuplicateItemError(location)
......
......@@ -471,10 +471,6 @@ class XMLModuleStore(ModuleStoreBase):
"""
return dict( (k, self.errored_courses[k].errors) for k in self.errored_courses)
def create_item(self, location):
raise NotImplementedError("XMLModuleStores are read-only")
def update_item(self, location, data):
"""
Set the data in the item specified by the location to
......
......@@ -24,13 +24,6 @@ def import_from_xml(store, data_dir, course_dirs=None,
for course_id in module_store.modules.keys():
for module in module_store.modules[course_id].itervalues():
# TODO (cpennington): This forces import to overrite the same items.
# This should in the future create new revisions of the items on import
try:
store.create_item(module.location)
except DuplicateItemError:
log.exception('Item already exists at %s' % module.location.url())
pass
if 'data' in module.definition:
store.update_item(module.location, module.definition['data'])
if 'children' in module.definition:
......
from collections import defaultdict
from .x_module import XModuleDescriptor
from .modulestore import Location
from .modulestore.django import modulestore
def all_templates():
"""
Returns all templates for enabled modules, grouped by descriptor type
"""
templates = defaultdict(list)
for category, descriptor in XModuleDescriptor.load_classes():
templates[category] = descriptor.templates
return templates
def update_templates():
"""
Updates the set of templates in the modulestore with all templates currently
available from the installed plugins
"""
for category, templates in all_templates().items():
for template in templates:
template_location = Location('i4x', 'edx', 'templates', category, Location.clean_for_url_name(template.name))
modulestore().update_item(template_location, template.data)
modulestore().update_children(template_location, template.children)
modulestore().update_metadata(template_location, {'display_name': template.name})
......@@ -7,6 +7,7 @@ from functools import partial
from lxml import etree
from lxml.etree import XMLSyntaxError
from pprint import pprint
from collections import namedtuple
from xmodule.errortracker import exc_info_to_str
from xmodule.modulestore import Location
......@@ -71,7 +72,11 @@ class Plugin(object):
@classmethod
def load_classes(cls):
return [class_.load()
"""
Returns a list of containing the identifiers and their corresponding classes for all
of the available instances of this plugin
"""
return [(class_.name, class_.load())
for class_
in pkg_resources.iter_entry_points(cls.entry_point)]
......@@ -321,6 +326,9 @@ def policy_key(location):
return '{cat}/{name}'.format(cat=location.category, name=location.name)
Template = namedtuple("Template", "name data children")
class XModuleDescriptor(Plugin, HTMLSnippet):
"""
An XModuleDescriptor is a specification for an element of a course. This
......@@ -361,6 +369,11 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
equality_attributes = ('definition', 'metadata', 'location',
'shared_state_key', '_inherited_metadata')
# A list of Template objects that describe possible templates that can be used
# to create a module of this type.
# If no templates are provided, there will be no way to create a module of this type
templates = []
# ============================= STRUCTURAL MANIPULATION ===================
def __init__(self,
system,
......
......@@ -468,7 +468,7 @@ for dir_ in (js_file_dir, css_file_dir):
js_fragments = set()
css_fragments = defaultdict(set)
for descriptor in XModuleDescriptor.load_classes() + [HiddenDescriptor]:
for _, descriptor in XModuleDescriptor.load_classes() + [(None, HiddenDescriptor)]:
module_js = descriptor.module_class.get_javascript()
for filetype in ('coffee', 'js'):
for idx, fragment in enumerate(module_js.get(filetype, [])):
......
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