Commit 77865f7a by cahrens

Merge branch 'feature/cale/cms-master' into feature/christina/tiny-mce

parents 5f05ec83 fe5d7285
......@@ -27,4 +27,5 @@ class Command(BaseCommand):
print "Importing. Data_dir={data}, course_dirs={courses}".format(
data=data_dir,
courses=course_dirs)
import_from_xml(modulestore('direct'), data_dir, course_dirs, load_error_modules=False,static_content_store=contentstore())
import_from_xml(modulestore('direct'), data_dir, course_dirs, load_error_modules=False,
static_content_store=contentstore(), verbose=True)
from django.test.testcases import TestCase
from cache_toolbox.core import get_cached_content, set_cached_content, del_cached_content
from xmodule.modulestore import Location
from xmodule.contentstore.content import StaticContent
class Content:
def __init__(self, location, content):
self.location = location
self.content = content
def get_id(self):
return StaticContent.get_id_from_location(self.location)
class CachingTestCase(TestCase):
# Tests for https://edx.lighthouseapp.com/projects/102637/tickets/112-updating-asset-does-not-refresh-the-cached-copy
unicodeLocation = Location(u'c4x', u'mitX', u'800', u'thumbnail', u'monsters.jpg')
# Note that some of the parts are strings instead of unicode strings
nonUnicodeLocation = Location('c4x', u'mitX', u'800', 'thumbnail', 'monsters.jpg')
mockAsset = Content(unicodeLocation, 'my content')
def test_put_and_get(self):
set_cached_content(self.mockAsset)
self.assertEqual(self.mockAsset.content, get_cached_content(self.unicodeLocation).content,
'should be stored in cache with unicodeLocation')
self.assertEqual(self.mockAsset.content, get_cached_content(self.nonUnicodeLocation).content,
'should be stored in cache with nonUnicodeLocation')
def test_delete(self):
set_cached_content(self.mockAsset)
del_cached_content(self.nonUnicodeLocation)
self.assertEqual(None, get_cached_content(self.unicodeLocation),
'should not be stored in cache with unicodeLocation')
self.assertEqual(None, get_cached_content(self.nonUnicodeLocation),
'should not be stored in cache with nonUnicodeLocation')
......@@ -760,14 +760,16 @@ def upload_asset(request, org, course, coursename):
content_loc = StaticContent.compute_location(org, course, filename)
content = StaticContent(content_loc, filename, mime_type, filedata)
# first let's save a thumbnail so we can get back a thumbnail location
thumbnail_content = contentstore().generate_thumbnail(content)
# first let's see if a thumbnail can be created
(thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(content)
# delete cached thumbnail even if one couldn't be created this time (else the old thumbnail will continue to show)
del_cached_content(thumbnail_location)
# now store thumbnail location only if we could create it
if thumbnail_content is not None:
content.thumbnail_location = thumbnail_content.location
del_cached_content(thumbnail_content.location)
content.thumbnail_location = thumbnail_location
#then commit the content
#then commit the content
contentstore().save(content)
del_cached_content(content.location)
......@@ -777,7 +779,7 @@ def upload_asset(request, org, course, coursename):
response_payload = {'displayname' : content.name,
'uploadDate' : get_date_display(readback.last_modified_at),
'url' : StaticContent.get_url_path_from_location(content.location),
'thumb_url' : StaticContent.get_url_path_from_location(thumbnail_content.location) if thumbnail_content is not None else None,
'thumb_url' : StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
'msg' : 'Upload completed'
}
......
......@@ -498,7 +498,7 @@ function displayFinishedUpload(xhr) {
}
var resp = JSON.parse(xhr.responseText);
$('.upload-modal .embeddable-xml-input').val('<img src="' + xhr.getResponseHeader('asset_url') + '"/>');
$('.upload-modal .embeddable-xml-input').val(xhr.getResponseHeader('asset_url'));
$('.upload-modal .embeddable').show();
$('.upload-modal .file-name').hide();
$('.upload-modal .progress-fill').html(resp.msg);
......
......@@ -28,7 +28,7 @@
{{uploadDate}}
</td>
<td class="embed-col">
<input type="text" class="embeddable-xml-input" value='{{url}}'>
<input type="text" class="embeddable-xml-input" value='{{url}}' disabled>
</td>
</tr>
</script>
......@@ -69,7 +69,7 @@
${asset['uploadDate']}
</td>
<td class="embed-col">
<input type="text" class="embeddable-xml-input" value='${asset['url']}'>
<input type="text" class="embeddable-xml-input" value="${asset['url']}" disabled>
</td>
</tr>
% endfor
......@@ -99,8 +99,8 @@
<div class="progress-fill"></div>
</div>
<div class="embeddable">
<label>Embeddable XML:</label>
<input type="text" class="embeddable-xml-input" value='<img src=""/>'>
<label>URL:</label>
<input type="text" class="embeddable-xml-input" value='' disabled>
</div>
<form class="file-chooser" action="${upload_asset_callback_url}"
method="post" enctype="multipart/form-data">
......
......@@ -52,42 +52,3 @@
</div>
</div>
</%block>
<%block name="jsextra">
<script>
(function() {
var bar = $('.progress-bar');
var fill = $('.progress-fill');
var percent = $('.percent');
var status = $('#status');
var submitBtn = $('.submit-button');
$('form').ajaxForm({
beforeSend: function() {
status.empty();
var percentVal = '0%';
bar.show();
fill.width(percentVal);
percent.html(percentVal);
submitBtn.hide();
},
uploadProgress: function(event, position, total, percentComplete) {
var percentVal = percentComplete + '%';
fill.width(percentVal);
percent.html(percentVal);
},
complete: function(xhr) {
if (xhr.status == 200) {
alert('Your import was successful.');
window.location = '${successful_import_redirect_url}';
}
else
alert('Your import has failed.\n\n' + xhr.responseText);
submitBtn.show();
bar.hide();
}
});
})();
</script>
</%block>
\ No newline at end of file
......@@ -24,7 +24,7 @@
<li><a href="#" class="cheatsheet-toggle" data-tooltip="Toggle Cheatsheet">?</a></li>
</ul>
</div>
<textarea class="markdown-box">${markdown}</textarea>
<textarea class="markdown-box">${markdown | h}</textarea>
%endif
<textarea class="xml-box" rows="8" cols="40">${data | h}</textarea>
</div>
......
......@@ -109,10 +109,10 @@ def instance_key(model, instance_or_pk):
)
def set_cached_content(content):
cache.set(content.get_id(), content)
cache.set(str(content.location), content)
def get_cached_content(location):
return cache.get(StaticContent.get_id_from_location(location))
return cache.get(str(location))
def del_cached_content(location):
cache.delete(StaticContent.get_id_from_location(location))
cache.delete(str(location))
......@@ -18,7 +18,7 @@ class StaticContent(object):
self.content_type = content_type
self.data = data
self.last_modified_at = last_modified_at
self.thumbnail_location = Location(thumbnail_location)
self.thumbnail_location = Location(thumbnail_location) if thumbnail_location is not None else None
# optional information about where this file was imported from. This is needed to support import/export
# cycles
self.import_path = import_path
......@@ -113,6 +113,12 @@ class ContentStore(object):
def generate_thumbnail(self, content):
thumbnail_content = None
# use a naming convention to associate originals with the thumbnail
thumbnail_name = StaticContent.generate_thumbnail_name(content.location.name)
thumbnail_file_location = StaticContent.compute_location(content.location.org, content.location.course,
thumbnail_name, is_thumbnail = True)
# if we're uploading an image, then let's generate a thumbnail so that we can
# serve it up when needed without having to rescale on the fly
if content.content_type is not None and content.content_type.split('/')[0] == 'image':
......@@ -131,13 +137,8 @@ class ContentStore(object):
thumbnail_file = StringIO.StringIO()
im.save(thumbnail_file, 'JPEG')
thumbnail_file.seek(0)
# use a naming convention to associate originals with the thumbnail
thumbnail_name = StaticContent.generate_thumbnail_name(content.location.name)
# then just store this thumbnail as any other piece of content
thumbnail_file_location = StaticContent.compute_location(content.location.org, content.location.course,
thumbnail_name, is_thumbnail = True)
# store this thumbnail as any other piece of content
thumbnail_content = StaticContent(thumbnail_file_location, thumbnail_name,
'image/jpeg', thumbnail_file)
......@@ -147,7 +148,7 @@ class ContentStore(object):
# log and continue as thumbnails are generally considered as optional
logging.exception("Failed to generate thumbnail for {0}. Exception: {1}".format(content.location, str(e)))
return thumbnail_content
return thumbnail_content, thumbnail_file_location
......
......@@ -316,15 +316,15 @@ describe 'MarkdownEditingDescriptor', ->
</multiplechoiceresponse>
<p>Choice checks</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoiceChecks">
<choiceresponse>
<checkboxgroup direction="vertical">
<choice correct="false">option1 [x]</choice>
<choice correct="true">correct</choice>
<choice correct="true">redundant</choice>
<choice correct="false">distractor</choice>
<choice correct="false">no space</choice>
</choicegroup>
</multiplechoiceresponse>
</checkboxgroup>
</choiceresponse>
<p>Option with multiple correct ones</p>
......
......@@ -213,8 +213,8 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
// group check answers
xml = xml.replace(/(^\s*\[.?\].*?$\n*)+/gm, function(match, p) {
var groupString = '<multiplechoiceresponse>\n';
groupString += ' <choicegroup type="MultipleChoiceChecks">\n';
var groupString = '<choiceresponse>\n';
groupString += ' <checkboxgroup direction="vertical">\n';
var options = match.split('\n');
for(var i = 0; i < options.length; i++) {
if(options[i].length > 0) {
......@@ -223,8 +223,8 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
groupString += ' <choice correct="' + correct + '">' + value + '</choice>\n';
}
}
groupString += ' </choicegroup>\n';
groupString += '</multiplechoiceresponse>\n\n';
groupString += ' </checkboxgroup>\n';
groupString += '</choiceresponse>\n\n';
return groupString;
});
......
......@@ -54,20 +54,24 @@ class DraftModuleStore(ModuleStoreBase):
in the request. The depth is counted in the number of calls to
get_children() to cache. None indicates to cache all descendents
"""
# cdodge: we're forcing depth=0 here as the Draft store is not handling caching well
try:
return wrap_draft(super(DraftModuleStore, self).get_item(as_draft(location), depth))
return wrap_draft(super(DraftModuleStore, self).get_item(as_draft(location), depth=0))
except ItemNotFoundError:
return wrap_draft(super(DraftModuleStore, self).get_item(location, depth))
return wrap_draft(super(DraftModuleStore, self).get_item(location, depth=0))
def get_instance(self, course_id, location, depth=0):
"""
Get an instance of this location, with policy for course_id applied.
TODO (vshnayder): this may want to live outside the modulestore eventually
"""
# cdodge: we're forcing depth=0 here as the Draft store is not handling caching well
try:
return wrap_draft(super(DraftModuleStore, self).get_instance(course_id, as_draft(location), depth=depth))
return wrap_draft(super(DraftModuleStore, self).get_instance(course_id, as_draft(location), depth=0))
except ItemNotFoundError:
return wrap_draft(super(DraftModuleStore, self).get_instance(course_id, location, depth=depth))
return wrap_draft(super(DraftModuleStore, self).get_instance(course_id, location, depth=0))
def get_items(self, location, depth=0):
"""
......@@ -83,8 +87,10 @@ class DraftModuleStore(ModuleStoreBase):
get_children() to cache. None indicates to cache all descendents
"""
draft_loc = as_draft(location)
draft_items = super(DraftModuleStore, self).get_items(draft_loc, depth)
items = super(DraftModuleStore, self).get_items(location, depth)
# cdodge: we're forcing depth=0 here as the Draft store is not handling caching well
draft_items = super(DraftModuleStore, self).get_items(draft_loc, depth=0)
items = super(DraftModuleStore, self).get_items(location, depth=0)
draft_locs_found = set(item.location._replace(revision=None) for item in draft_items)
non_draft_items = [
......
......@@ -11,7 +11,8 @@ from xmodule.contentstore.content import StaticContent, XASSET_SRCREF_PREFIX
log = logging.getLogger(__name__)
def import_static_content(modules, course_loc, course_data_path, static_content_store, target_location_namespace, subpath = 'static'):
def import_static_content(modules, course_loc, course_data_path, static_content_store, target_location_namespace,
subpath = 'static', verbose=False):
remap_dict = {}
......@@ -23,6 +24,9 @@ def import_static_content(modules, course_loc, course_data_path, static_content_
try:
content_path = os.path.join(dirname, filename)
if verbose:
log.debug('importing static content {0}...'.format(content_path))
fullname_with_subpath = content_path.replace(static_dir, '') # strip away leading path from the name
if fullname_with_subpath.startswith('/'):
fullname_with_subpath = fullname_with_subpath[1:]
......@@ -35,10 +39,10 @@ def import_static_content(modules, course_loc, course_data_path, static_content_
content = StaticContent(content_loc, filename, mime_type, data, import_path = fullname_with_subpath)
# first let's save a thumbnail so we can get back a thumbnail location
thumbnail_content = static_content_store.generate_thumbnail(content)
(thumbnail_content, thumbnail_location) = static_content_store.generate_thumbnail(content)
if thumbnail_content is not None:
content.thumbnail_location = thumbnail_content.location
content.thumbnail_location = thumbnail_location
#then commit the content
static_content_store.save(content)
......@@ -69,10 +73,10 @@ def verify_content_links(module, base_dir, static_content_store, link, remap_dic
content = StaticContent(content_loc, filename, mime_type, data, import_path = path)
# first let's save a thumbnail so we can get back a thumbnail location
thumbnail_content = static_content_store.generate_thumbnail(content)
(thumbnail_content, thumbnail_location) = static_content_store.generate_thumbnail(content)
if thumbnail_content is not None:
content.thumbnail_location = thumbnail_content.location
content.thumbnail_location = thumbnail_location
#then commit the content
static_content_store.save(content)
......@@ -90,7 +94,7 @@ def verify_content_links(module, base_dir, static_content_store, link, remap_dic
def import_from_xml(store, data_dir, course_dirs=None,
default_class='xmodule.raw_module.RawDescriptor',
load_error_modules=True, static_content_store=None, target_location_namespace=None):
load_error_modules=True, static_content_store=None, target_location_namespace=None, verbose=False):
"""
Import the specified xml data_dir into the "store" modulestore,
using org and course as the location org and course.
......@@ -121,6 +125,9 @@ def import_from_xml(store, data_dir, course_dirs=None,
course_data_path = None
course_location = None
if verbose:
log.debug("Scanning {0} for course module...".format(course_id))
# Quick scan to get course module as we need some info from there. Also we need to make sure that the
# course module is committed first into the store
for module in module_store.modules[course_id].itervalues():
......@@ -155,15 +162,14 @@ def import_from_xml(store, data_dir, course_dirs=None,
course_items.append(module)
# then import all the static content
if static_content_store is not None:
_namespace_rename = target_location_namespace if target_location_namespace is not None else module_store.modules[course_id].location
_namespace_rename = target_location_namespace if target_location_namespace is not None else course_location
# first pass to find everything in /static/
import_static_content(module_store.modules[course_id], course_location, course_data_path, static_content_store,
_namespace_rename, subpath='static')
_namespace_rename, subpath='static', verbose=verbose)
# finally loop through all the modules
for module in module_store.modules[course_id].itervalues():
......@@ -177,6 +183,8 @@ def import_from_xml(store, data_dir, course_dirs=None,
if target_location_namespace is not None:
module = remap_namespace(module, target_location_namespace)
if verbose:
log.debug('importing module location {0}'.format(module.location))
if 'data' in module.definition:
module_data = module.definition['data']
......
import unittest
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.content import ContentStore
from xmodule.modulestore import Location
class Content:
def __init__(self, location, content_type):
self.location = location
self.content_type = content_type
class ContentTest(unittest.TestCase):
def test_thumbnail_none(self):
# We had a bug where a thumbnail location of None was getting transformed into a Location tuple, with
# all elements being None. It is important that the location be just None for rendering.
content = StaticContent('loc', 'name', 'content_type', 'data', None, None, None)
self.assertIsNone(content.thumbnail_location)
content = StaticContent('loc', 'name', 'content_type', 'data')
self.assertIsNone(content.thumbnail_location)
def test_generate_thumbnail_nonimage(self):
contentStore = ContentStore()
content = Content(Location(u'c4x', u'mitX', u'800', u'asset', u'monsters.jpg'), None)
(thumbnail_content, thumbnail_file_location) = contentStore.generate_thumbnail(content)
self.assertIsNone(thumbnail_content)
self.assertEqual(Location(u'c4x', u'mitX', u'800', u'thumbnail', u'monsters.jpg'), thumbnail_file_location)
\ No newline at end of file
......@@ -120,7 +120,7 @@ default_options = {
}
task :predjango do
sh("find . -type f -name *.pyc -delete")
sh("find . -type f -name '*.pyc' -delete")
sh('pip install -q --upgrade --no-deps -r local-requirements.txt')
end
......@@ -421,7 +421,9 @@ end
namespace :cms do
desc "Import course data within the given DATA_DIR variable"
task :import do
if ENV['DATA_DIR']
if ENV['DATA_DIR'] and ENV['COURSE_DIR']
sh(django_admin(:cms, :dev, :import, ENV['DATA_DIR'], ENV['COURSE_DIR']))
elsif ENV['DATA_DIR']
sh(django_admin(:cms, :dev, :import, ENV['DATA_DIR']))
else
raise "Please specify a DATA_DIR variable that point to your data directory.\n" +
......
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