Merge pull request #780 from MITx/feature/cdodge/cms-static-content-management
Feature/cdodge/cms static content management
This commit is contained in:
@@ -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):
|
||||
|
||||
0
common/lib/xmodule/xmodule/contentstore/__init__.py
Normal file
0
common/lib/xmodule/xmodule/contentstore/__init__.py
Normal file
26
common/lib/xmodule/xmodule/contentstore/content.py
Normal file
26
common/lib/xmodule/xmodule/contentstore/content.py
Normal 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
|
||||
|
||||
|
||||
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 .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()
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user