From 1ee7646cfceb07354fb0a805737dd576e0aad944 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 28 Sep 2012 10:20:57 -0400 Subject: [PATCH] per feedback, use reverse() when generating callback urls. Also generate thumbnails when ingesting static images. We'll need these handy for the CMS UI. --- cms/djangoapps/contentstore/views.py | 61 +++++++++++++++++-- common/djangoapps/contentserver/middleware.py | 4 +- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index d8cbe0c7a9..3223bfda29 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1,10 +1,15 @@ from util.json_request import expect_json import json +import os import logging import sys import mimetypes +import StringIO from collections import defaultdict +# to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' +from PIL import Image + from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden from django.contrib.auth.decorators import login_required from django.core.context_processors import csrf @@ -101,11 +106,18 @@ def course_index(request, org, course, name): _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 - ) + #upload_asset_callback_url = "/{org}/{course}/course/{name}/upload_asset".format( + # org = org, + # course = course, + # name = name + # ) + + upload_asset_callback_url = reverse('upload_asset', kwargs = { + 'org' : org, + 'course' : course, + 'coursename' : name + }) + logging.debug(upload_asset_callback_url) return render_to_response('course_index.html', { 'weeks': weeks, @@ -460,5 +472,44 @@ def upload_asset(request, org, course, coursename): # to re-populate the cache. del_cached_content(file_location) + # if we're uploading an image, then let's generate a thumbnail so that we can + # serve it up when needed without having to rescale on the fly + if mime_type.split('/')[0] == 'image': + try: + # not sure if this is necessary, but let's rewind the stream just in case + request.FILES['file'].seek(0) + + # use PIL to do the thumbnail generation (http://www.pythonware.com/products/pil/) + # My understanding is that PIL will maintain aspect ratios while restricting + # the max-height/width to be whatever you pass in as 'size' + # @todo: move the thumbnail size to a configuration setting?!? + im = Image.open(request.FILES['file']) + + # I've seen some exceptions from the PIL library when trying to save palletted + # PNG files to JPEG. Per the google-universe, they suggest converting to RGB first. + im = im.convert('RGB') + size = 128, 128 + im.thumbnail(size, Image.ANTIALIAS) + thumbnail_file = StringIO.StringIO() + im.save(thumbnail_file, 'JPEG') + thumbnail_file.seek(0) + + # use a naming convention to associate originals with the thumbnail + # .thumbnail.jpg + thumbnail_name = os.path.splitext(name)[0] + '.thumbnail.jpg' + # then just store this thumbnail as any other piece of content + thumbnail_file_location = StaticContent.compute_location_filename(org, course, + thumbnail_name) + thumbnail_content = StaticContent(thumbnail_file_location, thumbnail_name, + 'image/jpeg', thumbnail_file) + contentstore().save(thumbnail_content) + + # remove any cached content at this location, as thumbnails are treated just like any + # other bit of static content + del_cached_content(thumbnail_file_location) + except: + # catch, log, and continue as thumbnails are not a hard requirement + logging.error('Failed to generate thumbnail for {0}. Continuing...'.format(name)) + return HttpResponse('Upload completed') diff --git a/common/djangoapps/contentserver/middleware.py b/common/djangoapps/contentserver/middleware.py index 1f0cdbba2a..56d4ed8d1c 100644 --- a/common/djangoapps/contentserver/middleware.py +++ b/common/djangoapps/contentserver/middleware.py @@ -26,7 +26,9 @@ class StaticContentServer(object): # 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)) + # @todo: we probably want to have 'cache hit' counters so we can + # measure the efficacy of our caches + pass # see if the last-modified at hasn't changed, if not return a 302 (Not Modified)