From eb20e4716093e64204d26ae9e701aea7e0dd0ccf Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 19 Sep 2012 14:52:51 -0400 Subject: [PATCH 01/16] Remove drag-and-drop handles from navigation until we actually are going to enable them --- cms/templates/widgets/navigation.html | 1 - 1 file changed, 1 deletion(-) diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html index f576abfd27..f7e79bceb3 100644 --- a/cms/templates/widgets/navigation.html +++ b/cms/templates/widgets/navigation.html @@ -61,7 +61,6 @@ data-preview-type="${module.module_class.js_module_name}"> ${module.display_name} - handle % endfor <%include file="module-dropdown.html"/> From 9c5b0ba6ea6e9aa9d606de2075a3615367d87c6c Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 19 Sep 2012 14:54:41 -0400 Subject: [PATCH 02/16] Create the data dir to put files in, if it doesn't exist when loading a module from mongo --- common/lib/xmodule/xmodule/modulestore/mongo.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py index 33901947a6..882fa9dda1 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo.py @@ -153,7 +153,12 @@ class MongoModuleStore(ModuleStoreBase): Load an XModuleDescriptor from item, using the children stored in data_cache """ data_dir = item.get('metadata', {}).get('data_dir', item['location']['course']) - resource_fs = OSFS(self.fs_root / data_dir) + root = self.fs_root / data_dir + + if not root.isdir(): + root.mkdir() + + resource_fs = OSFS(root) system = CachingDescriptorSystem( self, From 912d6cd6f1e869ac2f25bed1323a3474750089de Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 19 Sep 2012 14:55:56 -0400 Subject: [PATCH 03/16] Move common code for updating modules in mongo to a single helper function that also handles creating the item if it doesn't already exist --- .../lib/xmodule/xmodule/modulestore/mongo.py | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py index 882fa9dda1..bf56e30805 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo.py @@ -254,6 +254,24 @@ class MongoModuleStore(ModuleStoreBase): except pymongo.errors.DuplicateKeyError: raise DuplicateItemError(location) + def _update_single_item(self, location, update): + """ + Set update on the specified item, and raises ItemNotFoundError + if the location doesn't exist + """ + + # See http://www.mongodb.org/display/DOCS/Updating for + # atomic update syntax + result = self.collection.update( + {'_id': Location(location).dict()}, + {'$set': update}, + multi=False, + upsert=True, + ) + if result['n'] == 0: + raise ItemNotFoundError(location) + + def update_item(self, location, data): """ Set the data in the item specified by the location to @@ -263,13 +281,7 @@ class MongoModuleStore(ModuleStoreBase): data: A nested dictionary of problem data """ - # See http://www.mongodb.org/display/DOCS/Updating for - # atomic update syntax - self.collection.update( - {'_id': Location(location).dict()}, - {'$set': {'definition.data': data}}, - - ) + self._update_single_item(location, {'definition.data': data}) def update_children(self, location, children): """ @@ -280,12 +292,7 @@ class MongoModuleStore(ModuleStoreBase): children: A list of child item identifiers """ - # See http://www.mongodb.org/display/DOCS/Updating for - # atomic update syntax - self.collection.update( - {'_id': Location(location).dict()}, - {'$set': {'definition.children': children}} - ) + self._update_single_item(location, {'definition.children': children}) def update_metadata(self, location, metadata): """ @@ -296,12 +303,7 @@ class MongoModuleStore(ModuleStoreBase): metadata: A nested dictionary of module metadata """ - # See http://www.mongodb.org/display/DOCS/Updating for - # atomic update syntax - self.collection.update( - {'_id': Location(location).dict()}, - {'$set': {'metadata': metadata}} - ) + self._update_single_item(location, {'metadata': metadata}) def get_parent_locations(self, location): '''Find all locations that are the parents of this location. Needed From 25544132910315dbe275f46ff49b36676ae78c0e Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 19 Sep 2012 15:02:26 -0400 Subject: [PATCH 04/16] Hook up link to create modules from templates --- cms/__init__.py | 3 ++ cms/djangoapps/contentstore/views.py | 52 ++++++++++++++++++- cms/envs/common.py | 2 +- .../coffee/src/models/new_module.coffee | 5 ++ cms/static/coffee/src/views/module.coffee | 9 ++-- cms/static/coffee/src/views/module_add.coffee | 26 ++++++++++ .../coffee/src/views/module_edit.coffee | 2 +- cms/static/coffee/src/views/week.coffee | 7 +++ cms/templates/new_item.html | 19 +++++++ cms/templates/widgets/module-dropdown.html | 2 +- cms/urls.py | 2 + common/lib/xmodule/xmodule/html_module.py | 6 ++- .../xmodule/xmodule/modulestore/__init__.py | 7 ++- .../lib/xmodule/xmodule/modulestore/mongo.py | 18 +++---- common/lib/xmodule/xmodule/modulestore/xml.py | 4 -- .../xmodule/modulestore/xml_importer.py | 7 --- common/lib/xmodule/xmodule/templates.py | 30 +++++++++++ common/lib/xmodule/xmodule/x_module.py | 15 +++++- lms/envs/common.py | 2 +- 19 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 cms/static/coffee/src/models/new_module.coffee create mode 100644 cms/static/coffee/src/views/module_add.coffee create mode 100644 cms/templates/new_item.html create mode 100644 common/lib/xmodule/xmodule/templates.py diff --git a/cms/__init__.py b/cms/__init__.py index e69de29bb2..e8dccbbf60 100644 --- a/cms/__init__.py +++ b/cms/__init__.py @@ -0,0 +1,3 @@ +from xmodule.templates import update_templates + +update_templates() diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index ff08588a63..a3ac10cd07 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -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 . @@ -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() diff --git a/cms/envs/common.py b/cms/envs/common.py index d111db8095..6297d55fb6 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -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() diff --git a/cms/static/coffee/src/models/new_module.coffee b/cms/static/coffee/src/models/new_module.coffee new file mode 100644 index 0000000000..58a109225e --- /dev/null +++ b/cms/static/coffee/src/models/new_module.coffee @@ -0,0 +1,5 @@ +class CMS.Models.NewModule extends Backbone.Model + url: '/clone_item' + + newUrl: -> + "/new_item?#{$.param(parent_location: @get('parent_location'))}" diff --git a/cms/static/coffee/src/views/module.coffee b/cms/static/coffee/src/views/module.coffee index 32540d845d..1b9e39e8c2 100644 --- a/cms/static/coffee/src/views/module.coffee +++ b/cms/static/coffee/src/views/module.coffee @@ -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 + diff --git a/cms/static/coffee/src/views/module_add.coffee b/cms/static/coffee/src/views/module_add.coffee new file mode 100644 index 0000000000..f379174c77 --- /dev/null +++ b/cms/static/coffee/src/views/module_add.coffee @@ -0,0 +1,26 @@ +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() + + diff --git a/cms/static/coffee/src/views/module_edit.coffee b/cms/static/coffee/src/views/module_edit.coffee index 0baf68ab58..2c4eb26eff 100644 --- a/cms/static/coffee/src/views/module_edit.coffee +++ b/cms/static/coffee/src/views/module_edit.coffee @@ -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.") ) diff --git a/cms/static/coffee/src/views/week.coffee b/cms/static/coffee/src/views/week.coffee index 8483b9d134..e2b5a50d59 100644 --- a/cms/static/coffee/src/views/week.coffee +++ b/cms/static/coffee/src/views/week.coffee @@ -1,6 +1,7 @@ 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') diff --git a/cms/templates/new_item.html b/cms/templates/new_item.html new file mode 100644 index 0000000000..60da39fd2a --- /dev/null +++ b/cms/templates/new_item.html @@ -0,0 +1,19 @@ +
+
${parent_name}
+
${parent_location}
+ +
+ % for module_type, module_templates in templates: +
+
${module_type}
+
+ % for template in module_templates: + ${template.display_name} + % endfor +
+
+ % endfor +
+ Cancel +
+ diff --git a/cms/templates/widgets/module-dropdown.html b/cms/templates/widgets/module-dropdown.html index 2fdd327cdd..a450907cc1 100644 --- a/cms/templates/widgets/module-dropdown.html +++ b/cms/templates/widgets/module-dropdown.html @@ -7,7 +7,7 @@
  • - +