From 7512b78eb2e81ac4615e194cd496b4ca773d55f0 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Wed, 26 Sep 2012 15:26:13 -0400 Subject: [PATCH] work-in-flight for uploading/serving of static content for courses --- cms/templates/widgets/upload_assets.html | 44 +++++++++++++++++++ common/djangoapps/contentserver/__init__.py | 0 common/djangoapps/contentserver/middleware.py | 36 +++++++++++++++ .../xmodule/xmodule/contentstore/__init__.py | 15 +++++++ .../xmodule/xmodule/contentstore/django.py | 29 ++++++++++++ .../lib/xmodule/xmodule/contentstore/mongo.py | 32 ++++++++++++++ 6 files changed, 156 insertions(+) create mode 100644 cms/templates/widgets/upload_assets.html create mode 100644 common/djangoapps/contentserver/__init__.py create mode 100644 common/djangoapps/contentserver/middleware.py create mode 100644 common/lib/xmodule/xmodule/contentstore/__init__.py create mode 100644 common/lib/xmodule/xmodule/contentstore/django.py create mode 100644 common/lib/xmodule/xmodule/contentstore/mongo.py diff --git a/cms/templates/widgets/upload_assets.html b/cms/templates/widgets/upload_assets.html new file mode 100644 index 0000000000..876166fdf7 --- /dev/null +++ b/cms/templates/widgets/upload_assets.html @@ -0,0 +1,44 @@ +
+
+ You can upload file assets to reference in your courseware +
+ + +
+
+
+
+
0%
+
+ +
+ +
+ + + diff --git a/common/djangoapps/contentserver/__init__.py b/common/djangoapps/contentserver/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/contentserver/middleware.py b/common/djangoapps/contentserver/middleware.py new file mode 100644 index 0000000000..48b760a8d3 --- /dev/null +++ b/common/djangoapps/contentserver/middleware.py @@ -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 diff --git a/common/lib/xmodule/xmodule/contentstore/__init__.py b/common/lib/xmodule/xmodule/contentstore/__init__.py new file mode 100644 index 0000000000..08658ea721 --- /dev/null +++ b/common/lib/xmodule/xmodule/contentstore/__init__.py @@ -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) + diff --git a/common/lib/xmodule/xmodule/contentstore/django.py b/common/lib/xmodule/xmodule/contentstore/django.py new file mode 100644 index 0000000000..d8b3084135 --- /dev/null +++ b/common/lib/xmodule/xmodule/contentstore/django.py @@ -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 diff --git a/common/lib/xmodule/xmodule/contentstore/mongo.py b/common/lib/xmodule/xmodule/contentstore/mongo.py new file mode 100644 index 0000000000..eddb162539 --- /dev/null +++ b/common/lib/xmodule/xmodule/contentstore/mongo.py @@ -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() + + +