diff --git a/cms/__init__.py b/cms/__init__.py index e69de29bb2..8b13789179 100644 --- a/cms/__init__.py +++ b/cms/__init__.py @@ -0,0 +1 @@ + diff --git a/cms/djangoapps/contentstore/__init__.py b/cms/djangoapps/contentstore/__init__.py index e69de29bb2..e8dccbbf60 100644 --- a/cms/djangoapps/contentstore/__init__.py +++ b/cms/djangoapps/contentstore/__init__.py @@ -0,0 +1,3 @@ +from xmodule.templates import update_templates + +update_templates() diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index a6790ad59b..1d15f1e7df 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -26,4 +26,4 @@ class Command(BaseCommand): print "Importing. Data_dir={data}, course_dirs={courses}".format( data=data_dir, courses=course_dirs) - import_from_xml(modulestore(), data_dir, course_dirs) + import_from_xml(modulestore(), data_dir, course_dirs, load_error_modules=False) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index ff08588a63..da6611f248 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1,6 +1,7 @@ from util.json_request import expect_json import json import logging +import sys from collections import defaultdict from django.http import HttpResponse, Http404 @@ -12,6 +13,8 @@ from django.conf import settings from xmodule.modulestore import Location from xmodule.x_module import ModuleSystem +from xmodule.error_module import ErrorDescriptor +from xmodule.errortracker import exc_info_to_str from github_sync import export_to_github from static_replace import replace_urls @@ -20,6 +23,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 +133,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 . @@ -259,10 +291,17 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_ shared_state: A shared state string """ system = preview_module_system(request, preview_id, descriptor) - module = descriptor.xmodule_constructor(system)(instance_state, shared_state) + try: + module = descriptor.xmodule_constructor(system)(instance_state, shared_state) + except: + module = ErrorDescriptor.from_descriptor( + descriptor, + error_msg=exc_info_to_str(sys.exc_info()) + ).xmodule_constructor(system)(None, None) + 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 +365,28 @@ 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 + + # TODO: This needs to be deleted when we have proper storage for static content + new_item.metadata['data_dir'] = parent.metadata['data_dir'] + + 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..dc82af85af 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -195,6 +195,7 @@ STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage' # prep it for use in pipeline js from xmodule.x_module import XModuleDescriptor from xmodule.raw_module import RawDescriptor +from xmodule.error_module import ErrorDescriptor js_file_dir = PROJECT_ROOT / "static" / "coffee" / "module" css_file_dir = PROJECT_ROOT / "static" / "sass" / "module" module_styles_path = css_file_dir / "_module-styles.scss" @@ -210,7 +211,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), (None, ErrorDescriptor)]: descriptor_js = descriptor.get_javascript() module_js = descriptor.module_class.get_javascript() diff --git a/cms/envs/dev.py b/cms/envs/dev.py index 19b4dec8b2..fc2a5a5684 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -14,7 +14,6 @@ LOGGING = get_logger_config(ENV_ROOT / "log", tracking_filename="tracking.log", debug=True) - MODULESTORE = { 'default': { 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', 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 @@