Merge pull request #780 from MITx/feature/cdodge/cms-static-content-management

Feature/cdodge/cms static content management
This commit is contained in:
Calen Pennington
2012-09-29 07:39:50 -07:00
17 changed files with 373 additions and 9 deletions

View File

@@ -10,6 +10,7 @@ import sys
from datetime import timedelta
from lxml import etree
from lxml.html import rewrite_links
from pkg_resources import resource_string
from capa.capa_problem import LoncapaProblem
@@ -332,6 +333,15 @@ class CapaModule(XModule):
html = '<div id="problem_{id}" class="problem" data-url="{ajax_url}">'.format(
id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "</div>"
# cdodge: OK, we have to do two rounds of url reference subsitutions
# one which uses the 'asset library' that is served by the contentstore and the
# more global /static/ filesystem based static content.
# NOTE: rewrite_content_links is defined in XModule
# This is a bit unfortunate and I'm sure we'll try to considate this into
# a one step process.
html = rewrite_links(html, self.rewrite_content_links)
# now do the substitutions which are filesystem based, e.g. '/static/' prefixes
return self.system.replace_urls(html, self.metadata['data_dir'])
def handle_ajax(self, dispatch, get):

View File

@@ -0,0 +1,26 @@
XASSET_LOCATION_TAG = 'c4x'
XASSET_SRCREF_PREFIX = 'xasset:'
class StaticContent(object):
def __init__(self, filename, name, content_type, data, last_modified_at=None):
self.filename = filename
self.name = name
self.content_type = content_type
self.data = data
self.last_modified_at = last_modified_at
@staticmethod
def compute_location_filename(org, course, name):
return '/{0}/{1}/{2}/asset/{3}'.format(XASSET_LOCATION_TAG, org, course, name)
'''
Abstraction for all ContentStore providers (e.g. MongoDB)
'''
class ContentStore(object):
def save(self, content):
raise NotImplementedError
def find(self, filename):
raise NotImplementedError

View 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

View File

@@ -0,0 +1,32 @@
from pymongo import Connection
import gridfs
from gridfs.errors import NoFile
import sys
import logging
from .content import StaticContent, ContentStore
from xmodule.exceptions import NotFoundError
class MongoContentStore(ContentStore):
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 save(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:
return StaticContent(fp.filename, fp.displayname, fp.content_type, fp.read(), fp.uploadDate)
except NoFile:
raise NotFoundError()

View File

@@ -4,6 +4,7 @@ import logging
import os
import sys
from lxml import etree
from lxml.html import rewrite_links
from path import path
from .x_module import XModule
@@ -12,6 +13,9 @@ from .xml_module import XmlDescriptor, name_to_pathname
from .editing_module import EditingDescriptor
from .stringify import stringify_children
from .html_checker import check_html
from xmodule.modulestore import Location
from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
log = logging.getLogger("mitx.courseware")
@@ -21,7 +25,8 @@ class HtmlModule(XModule):
js_module_name = "HTMLModule"
def get_html(self):
return self.html
# cdodge: perform link substitutions for any references to course static content (e.g. images)
return rewrite_links(self.html, self.rewrite_content_links)
def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, **kwargs):
@@ -30,6 +35,7 @@ class HtmlModule(XModule):
self.html = self.definition['data']
class HtmlDescriptor(XmlDescriptor, EditingDescriptor):
"""
Module for putting raw html in a course

View File

@@ -12,6 +12,8 @@ from pkg_resources import resource_listdir, resource_string, resource_isdir
from xmodule.modulestore import Location
from xmodule.timeparse import parse_time
from xmodule.contentstore.content import StaticContent, XASSET_SRCREF_PREFIX
log = logging.getLogger('mitx.' + __name__)
@@ -317,6 +319,20 @@ class XModule(HTMLSnippet):
get is a dictionary-like object '''
return ""
# cdodge: added to support dynamic substitutions of
# links for courseware assets (e.g. images). <link> is passed through from lxml.html parser
def rewrite_content_links(self, link):
# see if we start with our format, e.g. 'xasset:<filename>'
if link.startswith(XASSET_SRCREF_PREFIX):
# yes, then parse out the name
name = link[len(XASSET_SRCREF_PREFIX):]
loc = Location(self.location)
# resolve the reference to our internal 'filepath' which
link = StaticContent.compute_location_filename(loc.org, loc.course, name)
return link
def policy_key(location):
"""