From 85e6c233860e1ed0e34e7c15dfb10b8e4d10e2b0 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Wed, 26 Sep 2012 15:24:21 -0400 Subject: [PATCH] work-in-flight for uploading/serving of static content for courses --- cms/djangoapps/contentstore/views.py | 82 +++++++++++++++++++++++-- cms/envs/common.py | 3 +- cms/envs/dev.py | 11 ++++ cms/templates/course_index.html | 3 + cms/urls.py | 4 +- common/djangoapps/cache_toolbox/core.py | 17 +++++ 6 files changed, 113 insertions(+), 7 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index da6611f248..6692192590 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -2,14 +2,16 @@ from util.json_request import expect_json import json import logging import sys +import mimetypes from collections import defaultdict -from django.http import HttpResponse, Http404 +from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden from django.contrib.auth.decorators import login_required from django.core.context_processors import csrf from django_future.csrf import ensure_csrf_cookie from django.core.urlresolvers import reverse from django.conf import settings +from django import forms from xmodule.modulestore import Location from xmodule.x_module import ModuleSystem @@ -26,6 +28,13 @@ from functools import partial from itertools import groupby from operator import attrgetter +from xmodule.contentstore.django import contentstore +from xmodule.contentstore import StaticContent + +#from django.core.cache import cache + +from cache_toolbox.core import set_cached_content, get_cached_content + log = logging.getLogger(__name__) @@ -89,9 +98,19 @@ def course_index(request, org, course, name): raise Http404 # TODO (vshnayder): better error # TODO (cpennington): These need to be read in from the active user - course = modulestore().get_item(location) - weeks = course.get_children() - return render_to_response('course_index.html', {'weeks': weeks}) + _course = modulestore().get_item(location) + weeks = _course.get_children() + + upload_asset_callback_url = "/{org}/{course}/course/{name}/upload_asset".format( + org = org, + course = course, + name = name + ) + + return render_to_response('course_index.html', { + 'weeks': weeks, + 'upload_asset_callback_url': upload_asset_callback_url + }) @login_required @@ -115,12 +134,13 @@ def edit_item(request): lms_link = "{lms_base}/courses/{course_id}/jump_to/{location}".format( lms_base=settings.LMS_BASE, # TODO: These will need to be changed to point to the particular instance of this problem in the particular course - course_id=modulestore().get_containing_courses(item.location)[0].id, + course_id= modulestore().get_containing_courses(item.location)[0].id, location=item.location, ) else: lms_link = None + return render_to_response('unit.html', { 'contents': item.get_html(), 'js_module': item.js_module_name, @@ -390,3 +410,55 @@ def clone_item(request): modulestore().update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()]) return HttpResponse() + +''' +cdodge: this method allows for POST uploading of files into the course asset library, which will +be supported by GridFS in MongoDB. +''' +#@login_required +#@ensure_csrf_cookie +def upload_asset(request, org, course, coursename): + + if request.method != 'POST': + # (cdodge) @todo: Is there a way to do a - say - 'raise Http400'? + return HttpResponseBadRequest() + + # construct a location from the passed in path + location = ['i4x', org, course, 'course', coursename] + if not has_access(request.user, location): + return HttpResponseForbidden() + + # Does the course actually exist?!? + + try: + item = modulestore().get_item(location) + except: + # no return it as a Bad Request response + logging.error('Could not find course' + location) + return HttpResponseBadRequest() + + # compute a 'filename' which is similar to the location formatting, we're using the 'filename' + # nomenclature since we're using a FileSystem paradigm here. We're just imposing + # the Location string formatting expectations to keep things a bit more consistent + + name = request.FILES['file'].name + mime_type = request.FILES['file'].content_type + filedata = request.FILES['file'].read() + + file_location = StaticContent.compute_location_filename(org, course, name) + + content = StaticContent(file_location, name, mime_type, filedata) + + # first commit to the DB + contentstore().update(content) + + # then update the cache so we're not serving up stale content + set_cached_content(content) + + return HttpResponse('Upload completed') + + + +class UploadFileForm(forms.Form): + title = forms.CharField(max_length=50) + file = forms.FileField() diff --git a/cms/envs/common.py b/cms/envs/common.py index dc82af85af..7190ba9e51 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -118,6 +118,7 @@ TEMPLATE_LOADERS = ( ) MIDDLEWARE_CLASSES = ( + 'contentserver.middleware.StaticContentServer', 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -130,7 +131,7 @@ MIDDLEWARE_CLASSES = ( 'track.middleware.TrackMiddleware', 'mitxmako.middleware.MakoMiddleware', - 'django.middleware.transaction.TransactionMiddleware', + 'django.middleware.transaction.TransactionMiddleware' ) ############################ SIGNAL HANDLERS ################################ diff --git a/cms/envs/dev.py b/cms/envs/dev.py index fc2a5a5684..dd0e0337f6 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -28,6 +28,17 @@ MODULESTORE = { } } +# cdodge: This is the specifier for the MongoDB (using GridFS) backed static content store +# This is for static content for courseware, not system static content (e.g. javascript, css, edX branding, etc) +CONTENTSTORE = { + 'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore', + 'OPTIONS': { + 'host': 'localhost', + 'db' : 'xcontent', + } +} + + DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', diff --git a/cms/templates/course_index.html b/cms/templates/course_index.html index e490ad7817..347e93ef4a 100644 --- a/cms/templates/course_index.html +++ b/cms/templates/course_index.html @@ -7,8 +7,11 @@ <%include file="widgets/navigation.html"/> + <%include file="widgets/upload_assets.html"/> +
+ diff --git a/cms/urls.py b/cms/urls.py index e51ae59b08..ddd54adc65 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -17,7 +17,9 @@ urlpatterns = ('', 'contentstore.views.course_index', name='course_index'), url(r'^github_service_hook$', 'github_sync.views.github_post_receive'), url(r'^preview/modx/(?P[^/]*)/(?P.*?)/(?P[^/]*)$', - 'contentstore.views.preview_dispatch', name='preview_dispatch') + 'contentstore.views.preview_dispatch', name='preview_dispatch'), + url(r'^(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)/upload_asset$', + 'contentstore.views.upload_asset', name='upload_asset') ) # User creation and updating views diff --git a/common/djangoapps/cache_toolbox/core.py b/common/djangoapps/cache_toolbox/core.py index 208be34a73..85e1c8a246 100644 --- a/common/djangoapps/cache_toolbox/core.py +++ b/common/djangoapps/cache_toolbox/core.py @@ -10,6 +10,7 @@ Core methods from django.core.cache import cache from django.db import DEFAULT_DB_ALIAS +from xmodule.contentstore import StaticContent from . import app_settings @@ -107,3 +108,19 @@ def instance_key(model, instance_or_pk): model._meta.module_name, getattr(instance_or_pk, 'pk', instance_or_pk), ) + +def content_key(filename): + return 'content:%s' % (filename) + +def set_cached_content(content): + cache.set(content_key(content.filename), content) + +def get_cached_content(filename): + return cache.get(content_key(filename)) + + +#def set_cached_content(filename, content_type, data): +# cache.set(content_key(filename), (filename, content_type, data)) + +#def get_cached_content(filename): +# return cache.get(content_key(filename))