work-in-flight for uploading/serving of static content for courses
This commit is contained in:
44
cms/templates/widgets/upload_assets.html
Normal file
44
cms/templates/widgets/upload_assets.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<section>
|
||||
<div class="assset-upload">
|
||||
You can upload file assets to reference in your courseware
|
||||
<form action="${upload_asset_callback_url}" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="file">
|
||||
<input type="submit" value="Upload File">
|
||||
</form>
|
||||
</div>
|
||||
<div class="asset-upload-progress">
|
||||
<div class="bar"></div>
|
||||
<div class="percent">0%</div>
|
||||
</div>
|
||||
|
||||
<div id="status"></div>
|
||||
|
||||
<section>
|
||||
|
||||
<script src="http://malsup.github.com/jquery.form.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
|
||||
var bar = $('.bar');
|
||||
var percent = $('.percent');
|
||||
var status = $('#status');
|
||||
|
||||
$('form').ajaxForm({
|
||||
beforeSend: function() {
|
||||
status.empty();
|
||||
var percentVal = '0%';
|
||||
bar.width(percentVal)
|
||||
percent.html(percentVal);
|
||||
},
|
||||
uploadProgress: function(event, position, total, percentComplete) {
|
||||
var percentVal = percentComplete + '%';
|
||||
bar.width(percentVal)
|
||||
percent.html(percentVal);
|
||||
},
|
||||
complete: function(xhr) {
|
||||
status.html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
0
common/djangoapps/contentserver/__init__.py
Normal file
0
common/djangoapps/contentserver/__init__.py
Normal file
36
common/djangoapps/contentserver/middleware.py
Normal file
36
common/djangoapps/contentserver/middleware.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import logging
|
||||
|
||||
from django.http import HttpResponse, Http404
|
||||
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.contentstore import StaticContent
|
||||
from cache_toolbox.core import get_cached_content, set_cached_content
|
||||
from xmodule.exceptions import NotFoundError
|
||||
|
||||
|
||||
class StaticContentServer(object):
|
||||
def __init__(self):
|
||||
self.match_tag = StaticContent.get_location_tag()
|
||||
|
||||
def process_request(self, request):
|
||||
# look to see if the request is prefixed with 'c4x' tag
|
||||
if request.path.startswith('/' + self.match_tag):
|
||||
|
||||
# first look in our cache so we don't have to round-trip to the DB
|
||||
content = get_cached_content(request.path)
|
||||
if content is None:
|
||||
# nope, not in cache, let's fetch from DB
|
||||
try:
|
||||
content = contentstore().find(request.path)
|
||||
except NotFoundError:
|
||||
raise Http404
|
||||
|
||||
# since we fetched it from DB, let's cache it going forward
|
||||
set_cached_content(content)
|
||||
else:
|
||||
logging.debug('cache hit on {0}'.format(content.filename))
|
||||
|
||||
response = HttpResponse(content.data, content_type=content.content_type)
|
||||
response['Content-Disposition'] = 'attachment; filename={0}'.format(content.name)
|
||||
|
||||
return response
|
||||
15
common/lib/xmodule/xmodule/contentstore/__init__.py
Normal file
15
common/lib/xmodule/xmodule/contentstore/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
class StaticContent(object):
|
||||
def __init__(self, filename, name, content_type, data):
|
||||
self.filename = filename
|
||||
self.name = name
|
||||
self.content_type = content_type
|
||||
self.data = data
|
||||
|
||||
@staticmethod
|
||||
def get_location_tag():
|
||||
return 'c4x'
|
||||
|
||||
@staticmethod
|
||||
def compute_location_filename(org, course, name):
|
||||
return '/{0}/{1}/{2}/asset/{3}'.format(StaticContent.get_location_tag(), org, course, name)
|
||||
|
||||
29
common/lib/xmodule/xmodule/contentstore/django.py
Normal file
29
common/lib/xmodule/xmodule/contentstore/django.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from __future__ import absolute_import
|
||||
from importlib import import_module
|
||||
from os import environ
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
_CONTENTSTORE = None
|
||||
|
||||
def load_function(path):
|
||||
"""
|
||||
Load a function by name.
|
||||
|
||||
path is a string of the form "path.to.module.function"
|
||||
returns the imported python object `function` from `path.to.module`
|
||||
"""
|
||||
module_path, _, name = path.rpartition('.')
|
||||
return getattr(import_module(module_path), name)
|
||||
|
||||
|
||||
def contentstore():
|
||||
global _CONTENTSTORE
|
||||
|
||||
if _CONTENTSTORE is None:
|
||||
class_ = load_function(settings.CONTENTSTORE['ENGINE'])
|
||||
options = {}
|
||||
options.update(settings.CONTENTSTORE['OPTIONS'])
|
||||
_CONTENTSTORE = class_(**options)
|
||||
|
||||
return _CONTENTSTORE
|
||||
32
common/lib/xmodule/xmodule/contentstore/mongo.py
Normal file
32
common/lib/xmodule/xmodule/contentstore/mongo.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from pymongo import Connection
|
||||
import gridfs
|
||||
from gridfs.errors import NoFile
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from . import StaticContent
|
||||
from xmodule.exceptions import NotFoundError
|
||||
|
||||
|
||||
class MongoContentStore(object):
|
||||
def __init__(self, host, db, port=27017):
|
||||
logging.debug( 'Using MongoDB for static content serving at host={0} db={1}'.format(host,db))
|
||||
_db = Connection(host=host, port=port)[db]
|
||||
self.fs = gridfs.GridFS(_db)
|
||||
|
||||
def update(self, content):
|
||||
with self.fs.new_file(filename=content.filename, content_type=content.content_type, displayname=content.name) as fp:
|
||||
fp.write(content.data)
|
||||
return content
|
||||
|
||||
def find(self, filename):
|
||||
try:
|
||||
with self.fs.get_last_version(filename) as fp:
|
||||
logging.debug('fetched {0}'.format(fp.name))
|
||||
return StaticContent(fp.filename, fp.displayname, fp.content_type, fp.read())
|
||||
except NoFile:
|
||||
raise NotFoundError()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user