Commit 130de49b by Chris Dodge

support the editing of course handouts via a generic editor until we have…

support the editing of course handouts via a generic editor until we have implemented the Rich HTML editor completed
parent 7294e978
import logging
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from lxml import etree
import re
from django.http import HttpResponseBadRequest, Http404
def get_module_info(store, location, parent_location = None):
try:
if location.revision is None:
module = store.get_item(location)
else:
module = store.get_item(location)
except ItemNotFoundError:
raise Http404
return {
'id': module.location.url(),
'data': module.definition['data'],
'metadata': module.metadata
}
def set_module_info(store, location, post_data):
module = None
isNew = False
try:
if location.revision is None:
module = store.get_item(location)
else:
module = store.get_item(location)
except:
pass
if module is None:
# new module at this location
# presume that we have an 'Empty' template
template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty'])
module = store.clone_item(template_location, location)
isNew = True
logging.debug('post = {0}'.format(post_data))
if post_data.get('data') is not None:
data = post_data['data']
logging.debug('data = {0}'.format(data))
store.update_item(location, data)
# cdodge: note calling request.POST.get('children') will return None if children is an empty array
# so it lead to a bug whereby the last component to be deleted in the UI was not actually
# deleting the children object from the children collection
if 'children' in post_data and post_data['children'] is not None:
children = post_data['children']
store.update_children(location, children)
# cdodge: also commit any metadata which might have been passed along in the
# POST from the client, if it is there
# NOTE, that the postback is not the complete metadata, as there's system metadata which is
# not presented to the end-user for editing. So let's fetch the original and
# 'apply' the submitted metadata, so we don't end up deleting system metadata
if post_data.get('metadata') is not None:
posted_metadata = post_data['metadata']
# update existing metadata with submitted metadata (which can be partial)
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
for metadata_key in posted_metadata.keys():
# let's strip out any metadata fields from the postback which have been identified as system metadata
# and therefore should not be user-editable, so we should accept them back from the client
if metadata_key in module.system_metadata_fields:
del posted_metadata[metadata_key]
elif posted_metadata[metadata_key] is None:
# remove both from passed in collection as well as the collection read in from the modulestore
if metadata_key in module.metadata:
del module.metadata[metadata_key]
del posted_metadata[metadata_key]
# overlay the new metadata over the modulestore sourced collection to support partial updates
module.metadata.update(posted_metadata)
# commit to datastore
store.update_metadata(location, module.metadata)
......@@ -49,6 +49,7 @@ from contentstore.course_info_model import get_course_updates,\
update_course_updates, delete_course_update
from cache_toolbox.core import del_cached_content
from xmodule.timeparse import stringify_time
from contentstore.module_info_model import get_module_info, set_module_info
log = logging.getLogger(__name__)
......@@ -927,7 +928,8 @@ def course_info(request, org, course, name, provided_id=None):
'active_tab': 'courseinfo-tab',
'context_course': course_module,
'url_base' : "/" + org + "/" + course + "/",
'course_updates' : json.dumps(get_course_updates(location))
'course_updates' : json.dumps(get_course_updates(location)),
'handouts_location': Location(['i4x', org, course, 'course_info', 'handouts']).url()
})
@expect_json
......@@ -959,6 +961,31 @@ def course_info_updates(request, org, course, provided_id=None):
elif real_method == 'DELETE': # coming as POST need to pull from Request Header X-HTTP-Method-Override DELETE
return HttpResponse(json.dumps(delete_course_update(location, request.POST, provided_id)), mimetype="application/json")
@expect_json
@login_required
@ensure_csrf_cookie
def module_info(request, module_location):
location = Location(module_location)
# NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!!
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
if real_method == 'GET':
return HttpResponse(json.dumps(get_module_info(_modulestore(location), location)), mimetype="application/json")
elif real_method == 'POST' or real_method == 'PUT':
return HttpResponse(json.dumps(set_module_info(_modulestore(location), location, request.POST)), mimetype="application/json")
else:
raise Http400
@login_required
@ensure_csrf_cookie
def asset_index(request, org, course, name):
......
<a href="#" class="edit-button"><span class="edit-icon"></span>Edit</a>
<div class="handouts-content">
<h2>Course Handouts</h2>
<ol class="treeview-handoutsnav">
<li>
<a href="/static/content-mit-6002x/handouts/syllabus.a477535058a1.pdf">Syllabus</a>
</li>
<li>
<a href="/static/content-mit-6002x/handouts/at-a-glance.9674fe7f677e.pdf">6.002x At-A-Glance</a>
</li>
<li>
<a href="/static/content-mit-6002x/handouts/syllabus.a477535058a1.pdf">Syllabus</a>
</li>
<li>
<a href="/static/content-mit-6002x/handouts/at-a-glance.9674fe7f677e.pdf">6.002x At-A-Glance</a>
</li>
<li>
<a href="/static/content-mit-6002x/handouts/syllabus.a477535058a1.pdf">Syllabus</a>
</li>
</ol>
</div>
<h2>Course Handouts</h2>
<%if (model.get('data') != null) { %>
<div class="handouts-content">
<%= model.get('data') %>
</div>
<% } else {%>
<p>You have no handouts defined</p>
<% } %>
<form class="edit-handouts-form" style="display: block;">
<div class="row">
<textarea class="handouts-content-editor text-editor"></textarea>
......
......@@ -29,6 +29,8 @@ CMS.Models.CourseUpdateCollection = Backbone.Collection.extend({
model : CMS.Models.CourseUpdate
});
\ No newline at end of file
CMS.Models.ModuleInfo = Backbone.Model.extend({
url: function() {return "/module_info/" + this.id;},
defaults: {
"id": null,
"data": null,
"metadata" : null,
"children" : null
},
});
\ No newline at end of file
......@@ -5,7 +5,7 @@
if (typeof window.templateLoader == 'function') return;
var templateLoader = {
templateVersion: "0.0.4",
templateVersion: "0.0.6",
templates: {},
loadRemoteTemplate: function(templateName, filename, callback) {
if (!this.templates[templateName]) {
......
......@@ -15,8 +15,8 @@ CMS.Views.CourseInfoEdit = Backbone.View.extend({
});
new CMS.Views.ClassInfoHandoutsView({
el: this.$('#course-handouts-view')
// collection: this.model.get('')
el: this.$('#course-handouts-view'),
model: this.model.get('handouts')
});
return this;
}
......@@ -185,11 +185,17 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
initialize: function() {
var self = this;
window.templateLoader.loadRemoteTemplate("course_info_handouts",
"/static/coffee/src/client_templates/course_info_handouts.html",
function (raw_template) {
self.template = _.template(raw_template);
self.render();
this.model.fetch(
{
complete: function() {
window.templateLoader.loadRemoteTemplate("course_info_handouts",
"/static/coffee/src/client_templates/course_info_handouts.html",
function (raw_template) {
self.template = _.template(raw_template);
self.render();
}
);
}
}
);
},
......@@ -197,7 +203,12 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
render: function () {
var updateEle = this.$el;
var self = this;
this.$el.append($(this.template()));
this.$el.html(
$(this.template( {
model: this.model
})
)
);
this.$preview = this.$el.find('.handouts-content');
this.$form = this.$el.find(".edit-handouts-form");
this.$editor = this.$form.find('.handouts-content-editor');
......@@ -209,7 +220,6 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
onEdit: function(event) {
this.$editor.val(this.$preview.html());
this.$form.show();
this.$preview.hide();
$modalCover.show();
$modalCover.bind('click', function() {
self.closeEditor(self);
......@@ -217,6 +227,9 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
},
onSave: function(event) {
this.model.set('data', this.$editor.val());
this.render();
this.model.save();
this.$form.hide();
this.closeEditor(this);
},
......@@ -227,8 +240,6 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
},
closeEditor: function(self) {
this.$preview.html(this.$editor.val());
this.$preview.show();
this.$form.hide();
$modalCover.unbind('click');
$modalCover.hide();
......
......@@ -8,6 +8,7 @@
<%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_info.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/module_info.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/course_info_edit.js')}"></script>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
......@@ -19,14 +20,19 @@
var course_updates = new CMS.Models.CourseUpdateCollection();
course_updates.reset(${course_updates|n});
course_updates.urlbase = '${url_base}';
var course_handouts = new CMS.Models.ModuleInfo({
id: '${handouts_location}'
});
course_handouts.urlbase = '${url_base}';
var editor = new CMS.Views.CourseInfoEdit({
el: $('.main-wrapper'),
model : new CMS.Models.CourseInfo({
courseId : '${context_course.location}',
updates : course_updates,
// FIXME add handouts
handouts : null})
handouts : course_handouts
})
});
editor.render();
});
......
......@@ -44,6 +44,10 @@ urlpatterns = ('',
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'),
# this is a generic method to return the data/metadata associated with a xmodule
url(r'^module_info/(?P<module_location>.*)$', 'contentstore.views.module_info', name='module_info'),
# temporary landing page for a course
url(r'^edge/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.landing', name='landing'),
......
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