Commit f0108757 by Calen Pennington

Merge pull request #848 from MITx/feature/cas/lyla/asset-store-integration

Feature/cas/lyla/asset store integration
parents 6a7fb1d5 f06c67ef
...@@ -74,3 +74,8 @@ def compute_unit_state(unit): ...@@ -74,3 +74,8 @@ def compute_unit_state(unit):
return UnitState.private return UnitState.private
else: else:
return UnitState.public return UnitState.public
def get_date_display(date):
print date, type(date)
return date.strftime("%d %B, %Y at %I:%M %p")
import traceback
from util.json_request import expect_json from util.json_request import expect_json
import exceptions import exceptions
import json import json
...@@ -44,7 +45,7 @@ from cache_toolbox.core import set_cached_content, get_cached_content, del_cache ...@@ -44,7 +45,7 @@ from cache_toolbox.core import set_cached_content, get_cached_content, del_cache
from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role
from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group
from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME
from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, get_date_display
from xmodule.templates import all_templates from xmodule.templates import all_templates
...@@ -618,7 +619,7 @@ def upload_asset(request, org, course, coursename): ...@@ -618,7 +619,7 @@ def upload_asset(request, org, course, coursename):
if not has_access(request.user, location): if not has_access(request.user, location):
return HttpResponseForbidden() return HttpResponseForbidden()
# Does the course actually exist?!? # Does the course actually exist?!? Get anything from it to prove its existance
try: try:
item = modulestore().get_item(location) item = modulestore().get_item(location)
...@@ -635,23 +636,11 @@ def upload_asset(request, org, course, coursename): ...@@ -635,23 +636,11 @@ def upload_asset(request, org, course, coursename):
mime_type = request.FILES['file'].content_type mime_type = request.FILES['file'].content_type
filedata = request.FILES['file'].read() filedata = request.FILES['file'].read()
file_location = StaticContent.compute_location(org, course, name) thumbnail_file_location = None
content = StaticContent(file_location, name, mime_type, filedata)
# first commit to the DB
contentstore().save(content)
# then remove the cache so we're not serving up stale content
# NOTE: we're not re-populating the cache here as the DB owns the last-modified timestamp
# which is used when serving up static content. This integrity is needed for
# browser-side caching support. We *could* re-fetch the saved content so that we have the
# timestamp populated, but we might as well wait for the first real request to come in
# to re-populate the cache.
del_cached_content(content.location)
# if we're uploading an image, then let's generate a thumbnail so that we can # if the upload asset is an image, we can generate a thumbnail from it
# serve it up when needed without having to rescale on the fly # let's do so now, so that we have the thumbnail location which we need
# so that the asset can point to it
if mime_type.split('/')[0] == 'image': if mime_type.split('/')[0] == 'image':
try: try:
# not sure if this is necessary, but let's rewind the stream just in case # not sure if this is necessary, but let's rewind the stream just in case
...@@ -673,24 +662,45 @@ def upload_asset(request, org, course, coursename): ...@@ -673,24 +662,45 @@ def upload_asset(request, org, course, coursename):
thumbnail_file.seek(0) thumbnail_file.seek(0)
# use a naming convention to associate originals with the thumbnail # use a naming convention to associate originals with the thumbnail
thumbnail_name = content.generate_thumbnail_name() thumbnail_name = StaticContent.generate_thumbnail_name(name)
# then just store this thumbnail as any other piece of content # then just store this thumbnail as any other piece of content
thumbnail_file_location = StaticContent.compute_location(org, course, thumbnail_file_location = StaticContent.compute_location(org, course,
thumbnail_name) thumbnail_name, is_thumbnail=True)
thumbnail_content = StaticContent(thumbnail_file_location, thumbnail_name, thumbnail_content = StaticContent(thumbnail_file_location, thumbnail_name,
'image/jpeg', thumbnail_file) 'image/jpeg', thumbnail_file)
contentstore().save(thumbnail_content) contentstore().save(thumbnail_content)
# remove any cached content at this location, as thumbnails are treated just like any # remove any cached content at this location, as thumbnails are treated just like any
# other bit of static content # other bit of static content
del_cached_content(thumbnail_content.location) del_cached_content(thumbnail_content.location)
# not sure if this is necessary, but let's rewind the stream just in case
request.FILES['file'].seek(0)
except: except:
# catch, log, and continue as thumbnails are not a hard requirement # catch, log, and continue as thumbnails are not a hard requirement
logging.error('Failed to generate thumbnail for {0}. Continuing...'.format(name)) logging.error('Failed to generate thumbnail for {0}. Continuing...'.format(name))
thumbnail_file_location = None
file_location = StaticContent.compute_location(org, course, name)
# create a StaticContent entity and point to the thumbnail
content = StaticContent(file_location, name, mime_type, filedata, thumbnail_location = thumbnail_file_location)
return HttpResponse('Upload completed') # first commit to the DB
contentstore().save(content)
# then remove the cache so we're not serving up stale content
# NOTE: we're not re-populating the cache here as the DB owns the last-modified timestamp
# which is used when serving up static content. This integrity is needed for
# browser-side caching support. We *could* re-fetch the saved content so that we have the
# timestamp populated, but we might as well wait for the first real request to come in
# to re-populate the cache.
del_cached_content(content.location)
response = HttpResponse('Upload completed')
response['asset_url'] = StaticContent.get_url_path_from_location(file_location)
return response
''' '''
This view will return all CMS users who are editors for the specified course This view will return all CMS users who are editors for the specified course
...@@ -772,23 +782,69 @@ def remove_user(request, location): ...@@ -772,23 +782,69 @@ def remove_user(request, location):
return create_json_response() return create_json_response()
@login_required
@ensure_csrf_cookie
def asset_index(request, location):
return render_to_response('asset_index.html',{})
# points to the temporary course landing page with log in and sign up # points to the temporary course landing page with log in and sign up
def landing(request, org, course, coursename): def landing(request, org, course, coursename):
return render_to_response('temp-course-landing.html', {}) return render_to_response('temp-course-landing.html', {})
def static_pages(request, org, course, coursename): def static_pages(request, org, course, coursename):
return render_to_response('static-pages.html', {}) return render_to_response('static-pages.html', {})
def edit_static(request, org, course, coursename): def edit_static(request, org, course, coursename):
return render_to_response('edit-static-page.html', {}) return render_to_response('edit-static-page.html', {})
def not_found(request): def not_found(request):
return render_to_response('error.html', {'error': '404'}) return render_to_response('error.html', {'error': '404'})
def server_error(request): def server_error(request):
return render_to_response('error.html', {'error': '500'}) return render_to_response('error.html', {'error': '500'})
\ No newline at end of file
@login_required
@ensure_csrf_cookie
def asset_index(request, org, course, name):
"""
Display an editable asset library
org, course, name: Attributes of the Location for the item to edit
"""
location = ['i4x', org, course, 'course', name]
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
upload_asset_callback_url = reverse('upload_asset', kwargs = {
'org' : org,
'course' : course,
'coursename' : name
})
course_reference = StaticContent.compute_location(org, course, name)
assets = contentstore().get_all_content_for_course(course_reference)
thumbnails = contentstore().get_all_content_thumbnails_for_course(course_reference)
asset_display = []
for asset in assets:
id = asset['_id']
display_info = {}
display_info['displayname'] = asset['displayname']
display_info['uploadDate'] = get_date_display(asset['uploadDate'])
asset_location = StaticContent.compute_location(id['org'], id['course'], id['name'])
display_info['url'] = StaticContent.get_url_path_from_location(asset_location)
# note, due to the schema change we may not have a 'thumbnail_location' in the result set
thumbnail_location = Location(asset.get('thumbnail_location', None))
display_info['thumb_url'] = StaticContent.get_url_path_from_location(thumbnail_location)
asset_display.append(display_info)
return render_to_response('asset_index.html', {
'assets': asset_display,
'upload_asset_callback_url': upload_asset_callback_url
})
...@@ -23,6 +23,10 @@ $(document).ready(function() { ...@@ -23,6 +23,10 @@ $(document).ready(function() {
$modalCover.bind('click', hideHistoryModal); $modalCover.bind('click', hideHistoryModal);
$('.assets .upload-button').bind('click', showUploadModal); $('.assets .upload-button').bind('click', showUploadModal);
$('.upload-modal .close-button').bind('click', hideModal); $('.upload-modal .close-button').bind('click', hideModal);
$('a.show-xml').toggle(showEmbeddableXML, hideEmbeddableXML);
$('a.copy-button').toggle(showEmbeddableXML, hideEmbeddableXML);
$('.unit .item-actions .delete-button').bind('click', deleteUnit); $('.unit .item-actions .delete-button').bind('click', deleteUnit);
$('.new-unit-item').bind('click', createNewUnit); $('.new-unit-item').bind('click', createNewUnit);
$('.save-subsection').bind('click', saveSubsection); $('.save-subsection').bind('click', saveSubsection);
...@@ -72,6 +76,17 @@ function removePolicyMetadata(e) { ...@@ -72,6 +76,17 @@ function removePolicyMetadata(e) {
_parent_el.remove(); _parent_el.remove();
else else
_parent_el.appendTo("#policy-to-delete"); _parent_el.appendTo("#policy-to-delete");
function showEmbeddableXML(e) {
$ceiling = $(this).parents('tr');
if ($ceiling.length === 0) $ceiling = $(this).parents('.upload-modal');
$ceiling.find('.embeddable-xml').html('<img src="'+$(this).attr('href')+'"/>');
}
function hideEmbeddableXML(e) {
$ceiling = $(this).parents('tr');
console.log($ceiling.length)
if ($ceiling.length === 0) $ceiling = $(this).parents('.upload-modal');
$ceiling.find('.embeddable-xml').html("");
} }
...@@ -233,20 +248,40 @@ function showFileSelectionMenu(e) { ...@@ -233,20 +248,40 @@ function showFileSelectionMenu(e) {
function startUpload(e) { function startUpload(e) {
$('.upload-modal h1').html('Uploading…'); $('.upload-modal h1').html('Uploading…');
$('.upload-modal .file-name').html($('.file-input').val()); $('.upload-modal .file-name').html($('.file-input').val());
$('.upload-modal .file-chooser').ajaxSubmit({
beforeSend: resetUploadBar,
uploadProgress: showUploadFeedback,
complete: displayFinishedUpload
});
$('.upload-modal .choose-file-button').hide(); $('.upload-modal .choose-file-button').hide();
$('.upload-modal .progress-bar').removeClass('loaded').show(); $('.upload-modal .progress-bar').removeClass('loaded').show();
$('.upload-modal .progress-fill').html('').css('width', '0').animate({ }
'width': '100%'
}, 1500); function resetUploadBar(){
setTimeout(markAsLoaded, 1500); var percentVal = '0%';
$('.upload-modal .progress-fill').width(percentVal)
$('.upload-modal .progress-fill').html(percentVal);
}
function showUploadFeedback(event, position, total, percentComplete) {
var percentVal = percentComplete + '%';
$('.upload-modal .progress-fill').width(percentVal);
$('.upload-modal .progress-fill').html(percentVal);
}
function displayFinishedUpload(xhr) {
if(xhr.status = 200){
markAsLoaded();
}
$('.upload-modal .copy-button').attr('href', xhr.getResponseHeader('asset_url'));
$('.upload-modal .progress-fill').html(xhr.responseText);
$('.upload-modal .choose-file-button').html('Load Another File').show();
} }
function markAsLoaded() { function markAsLoaded() {
$('.upload-modal .copy-button').css('display', 'inline-block'); $('.upload-modal .copy-button').css('display', 'inline-block');
$('.upload-modal .progress-bar').addClass('loaded'); $('.upload-modal .progress-bar').addClass('loaded');
$('.upload-modal .progress-fill').html('loaded successfully'); }
$('.upload-modal .choose-file-button').html('Load Another File').show();
}
function hideModal(e) { function hideModal(e) {
e.preventDefault(); e.preventDefault();
......
...@@ -86,6 +86,9 @@ ...@@ -86,6 +86,9 @@
} }
} }
} }
.show-xml {
@include blue-button;
}
} }
.upload-modal { .upload-modal {
......
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
<div class="inner-wrapper"> <div class="inner-wrapper">
<h1>Asset Library</h1> <h1>Asset Library</h1>
<div class="page-actions"> <div class="page-actions">
<a href="#" class="upload-button">Upload New File</a><input type="text" class="asset-search-input search wip-box" placeholder="search assets" /> <a href="#" class="upload-button">Upload New File</a>
<input type="text" class="asset-search-input search wip-box" placeholder="search assets" />
</div> </div>
<article class="asset-library"> <article class="asset-library">
<table> <table>
...@@ -22,149 +23,26 @@ ...@@ -22,149 +23,26 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
% for asset in assets:
<tr> <tr>
<td class="thumb-col"> <td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/3185/screenshots/149571/picture_39.png"></div> <div class="thumb"><img src="${asset['thumb_url']}"></div>
</td> </td>
<td class="name-col"> <td class="name-col">
<a href="#" class="filename">raygun-1.jpg</a> <a href="${asset['url']}" class="filename">${asset['displayname']}</a>
<div class="embeddable-xml"></div>
</td> </td>
<td class="date-col"> <td class="date-col">
10/2/2012 ${asset['uploadDate']}
</td> </td>
<td class="embed-col"> <td class="embed-col">
<a href="#">copy</a> <a class="show-xml" href="${asset['url']}">XML</a>
</td>
</tr>
<tr>
<td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/4573/screenshots/157708/final.png"></div>
</td>
<td class="name-col">
<a href="#" class="filename">raygun-2.jpg</a>
</td>
<td class="date-col">
10/2/2012
</td>
<td class="embed-col">
<a href="#">copy</a>
</td>
</tr>
<tr>
<td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/15101/screenshots/228625/star_wars_lightsbaer_2.jpg"></div>
</td>
<td class="name-col">
<a href="#" class="filename">raygun-2.jpg</a>
</td>
<td class="date-col">
10/2/2012
</td>
<td class="embed-col">
<a href="#">copy</a>
</td>
</tr>
<tr>
<td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/3185/screenshots/149571/picture_39.png"></div>
</td>
<td class="name-col">
<a href="#" class="filename">raygun-1.jpg</a>
</td>
<td class="date-col">
10/2/2012
</td>
<td class="embed-col">
<a href="#">copy</a>
</td>
</tr>
<tr>
<td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/4573/screenshots/157708/final.png"></div>
</td>
<td class="name-col">
<a href="#" class="filename">raygun-2.jpg</a>
</td>
<td class="date-col">
10/2/2012
</td>
<td class="embed-col">
<a href="#">copy</a>
</td>
</tr>
<tr>
<td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/15101/screenshots/228625/star_wars_lightsbaer_2.jpg"></div>
</td>
<td class="name-col">
<a href="#" class="filename">raygun-2.jpg</a>
</td>
<td class="date-col">
10/2/2012
</td>
<td class="embed-col">
<a href="#">copy</a>
</td>
</tr>
<tr>
<td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/3185/screenshots/149571/picture_39.png"></div>
</td>
<td class="name-col">
<a href="#" class="filename">raygun-1.jpg</a>
</td>
<td class="date-col">
10/2/2012
</td>
<td class="embed-col">
<a href="#">copy</a>
</td>
</tr>
<tr>
<td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/4573/screenshots/157708/final.png"></div>
</td>
<td class="name-col">
<a href="#" class="filename">raygun-2.jpg</a>
</td>
<td class="date-col">
10/2/2012
</td>
<td class="embed-col">
<a href="#">copy</a>
</td>
</tr>
<tr>
<td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/15101/screenshots/228625/star_wars_lightsbaer_2.jpg"></div>
</td>
<td class="name-col">
<a href="#" class="filename">raygun-2.jpg</a>
</td>
<td class="date-col">
10/2/2012
</td>
<td class="embed-col">
<a href="#">copy</a>
</td>
</tr>
<tr>
<td class="thumb-col">
<div class="thumb"><img src="http://dribbble.s3.amazonaws.com/users/3185/screenshots/149571/picture_39.png"></div>
</td>
<td class="name-col">
<a href="#" class="filename">raygun-1.jpg</a>
</td>
<td class="date-col">
10/2/2012
</td>
<td class="embed-col">
<a href="#">copy</a>
</td> </td>
</tr> </tr>
% endfor
</tbody> </tbody>
</table> </table>
<nav class="pagination"> <nav class="pagination wip-box">
Page: Page:
<ol class="pages"> <ol class="pages">
<li>1</li> <li>1</li>
...@@ -187,14 +65,17 @@ ...@@ -187,14 +65,17 @@
<div class="progress-bar"> <div class="progress-bar">
<div class="progress-fill"></div> <div class="progress-fill"></div>
</div> </div>
<a href="#" class="copy-button">Copy Embed Link</a> <div class="embeddable-xml"></div>
<div class="file-chooser"> <a href="#" class="copy-button">Show Embeddable XML</a>
<form class="file-chooser" action="${upload_asset_callback_url}"
method="post" enctype="multipart/form-data">
<a href="#" class="choose-file-button">Choose File</a> <a href="#" class="choose-file-button">Choose File</a>
<input type="file" class="file-input"> <input type="file" class="file-input" name="file">
</div> </form>
</div> </div>
</div> </div>
<div class="modal-cover"></div> <div class="modal-cover"></div>
</%block> </%block>
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
<script src="${static.url('js/vendor/jquery.cookie.js')}"></script> <script src="${static.url('js/vendor/jquery.cookie.js')}"></script>
<script src="${static.url('js/vendor/jquery.leanModal.min.js')}"></script> <script src="${static.url('js/vendor/jquery.leanModal.min.js')}"></script>
<script src="${static.url('js/vendor/jquery.tablednd.js')}"></script> <script src="${static.url('js/vendor/jquery.tablednd.js')}"></script>
<script src="http://malsup.github.com/jquery.form.js"></script>
<script type="text/javascript"> <script type="text/javascript">
document.write('\x3Cscript type="text/javascript" src="' + document.write('\x3Cscript type="text/javascript" src="' +
document.location.protocol + '//www.youtube.com/player_api">\x3C/script>'); document.location.protocol + '//www.youtube.com/player_api">\x3C/script>');
......
...@@ -30,16 +30,17 @@ urlpatterns = ('', ...@@ -30,16 +30,17 @@ urlpatterns = ('',
'contentstore.views.remove_user', name='remove_user'), 'contentstore.views.remove_user', name='remove_user'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)/remove_user$', url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)/remove_user$',
'contentstore.views.remove_user', name='remove_user'), 'contentstore.views.remove_user', name='remove_user'),
url(r'^assets/(?P<location>.*?)$', 'contentstore.views.asset_index', name='asset_index'),
url(r'^pages/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.static_pages', name='static_pages'), url(r'^pages/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.static_pages', name='static_pages'),
url(r'^edit_static/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.edit_static', name='edit_static'), url(r'^edit_static/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.edit_static', name='edit_static'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/assets/(?P<name>[^/]+)$', 'contentstore.views.asset_index', name='asset_index'),
# temporary landing page for a course # temporary landing page for a course
url(r'^landing/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.landing', name='landing'), url(r'^landing/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.landing', name='landing'),
url(r'^not_found$', 'contentstore.views.not_found', name='not_found'), url(r'^not_found$', 'contentstore.views.not_found', name='not_found'),
url(r'^server_error$', 'contentstore.views.server_error', name='server_error') url(r'^server_error$', 'contentstore.views.server_error', name='server_error'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/assets/(?P<name>[^/]+)$', 'contentstore.views.asset_index', name='asset_index'),
) )
# User creation and updating views # User creation and updating views
......
XASSET_LOCATION_TAG = 'c4x' XASSET_LOCATION_TAG = 'c4x'
XASSET_SRCREF_PREFIX = 'xasset:' XASSET_SRCREF_PREFIX = 'xasset:'
XASSET_THUMBNAIL_TAIL_NAME = '.thumbnail.jpg' XASSET_THUMBNAIL_TAIL_NAME = '.jpg'
import os import os
import logging import logging
from xmodule.modulestore import Location from xmodule.modulestore import Location
class StaticContent(object): class StaticContent(object):
def __init__(self, loc, name, content_type, data, last_modified_at=None): def __init__(self, loc, name, content_type, data, last_modified_at=None, thumbnail_location=None):
self.location = loc self.location = loc
self.name = name #a display string which can be edited, and thus not part of the location which needs to be fixed self.name = name #a display string which can be edited, and thus not part of the location which needs to be fixed
self.content_type = content_type self.content_type = content_type
self.data = data self.data = data
self.last_modified_at = last_modified_at self.last_modified_at = last_modified_at
self.thumbnail_location = thumbnail_location
@property @property
def is_thumbnail(self): def is_thumbnail(self):
return self.name.endswith(XASSET_THUMBNAIL_TAIL_NAME) return self.location.category == 'thumbnail'
def generate_thumbnail_name(self): @staticmethod
return ('{0}'+XASSET_THUMBNAIL_TAIL_NAME).format(os.path.splitext(self.name)[0]) def generate_thumbnail_name(original_name):
return ('{0}'+XASSET_THUMBNAIL_TAIL_NAME).format(os.path.splitext(original_name)[0])
@staticmethod @staticmethod
def compute_location(org, course, name, revision=None): def compute_location(org, course, name, revision=None, is_thumbnail=False):
return Location([XASSET_LOCATION_TAG, org, course, 'asset', name, revision]) return Location([XASSET_LOCATION_TAG, org, course, 'asset' if not is_thumbnail else 'thumbnail', Location.clean(name), revision])
def get_id(self): def get_id(self):
return StaticContent.get_id_from_location(self.location) return StaticContent.get_id_from_location(self.location)
...@@ -34,7 +36,10 @@ class StaticContent(object): ...@@ -34,7 +36,10 @@ class StaticContent(object):
@staticmethod @staticmethod
def get_url_path_from_location(location): def get_url_path_from_location(location):
return "/{tag}/{org}/{course}/{category}/{name}".format(**location.dict()) if location is not None:
return "/{tag}/{org}/{course}/{category}/{name}".format(**location.dict())
else:
return None
@staticmethod @staticmethod
def get_id_from_location(location): def get_id_from_location(location):
...@@ -65,4 +70,23 @@ class ContentStore(object): ...@@ -65,4 +70,23 @@ class ContentStore(object):
raise NotImplementedError raise NotImplementedError
def get_all_content_for_course(self, location): def get_all_content_for_course(self, location):
'''
Returns a list of all static assets for a course. The return format is a list of dictionary elements. Example:
[
{u'displayname': u'profile.jpg', u'chunkSize': 262144, u'length': 85374,
u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 183000), u'contentType': u'image/jpeg',
u'_id': {u'category': u'asset', u'name': u'profile.jpg', u'course': u'6.002x', u'tag': u'c4x',
u'org': u'MITx', u'revision': None}, u'md5': u'36dc53519d4b735eb6beba51cd686a0e'},
{u'displayname': u'profile.thumbnail.jpg', u'chunkSize': 262144, u'length': 4073,
u'uploadDate': datetime.datetime(2012, 10, 3, 5, 41, 54, 196000), u'contentType': u'image/jpeg',
u'_id': {u'category': u'asset', u'name': u'profile.thumbnail.jpg', u'course': u'6.002x', u'tag': u'c4x',
u'org': u'MITx', u'revision': None}, u'md5': u'ff1532598830e3feac91c2449eaa60d6'},
....
]
'''
raise NotImplementedError raise NotImplementedError
...@@ -28,7 +28,9 @@ class MongoContentStore(ContentStore): ...@@ -28,7 +28,9 @@ class MongoContentStore(ContentStore):
if self.fs.exists({"_id" : id}): if self.fs.exists({"_id" : id}):
self.fs.delete(id) self.fs.delete(id)
with self.fs.new_file(_id = id, filename=content.get_url_path(), content_type=content.content_type, displayname=content.name) as fp: with self.fs.new_file(_id = id, filename=content.get_url_path(), content_type=content.content_type,
displayname=content.name, thumbnail_location=content.thumbnail_location) as fp:
fp.write(content.data) fp.write(content.data)
return content return content
...@@ -38,11 +40,18 @@ class MongoContentStore(ContentStore): ...@@ -38,11 +40,18 @@ class MongoContentStore(ContentStore):
id = StaticContent.get_id_from_location(location) id = StaticContent.get_id_from_location(location)
try: try:
with self.fs.get(id) as fp: with self.fs.get(id) as fp:
return StaticContent(location, fp.displayname, fp.content_type, fp.read(), fp.uploadDate) return StaticContent(location, fp.displayname, fp.content_type, fp.read(),
fp.uploadDate, thumbnail_location = fp.thumbnail_location if 'thumbnail_location' in fp else None)
except NoFile: except NoFile:
raise NotFoundError() raise NotFoundError()
def get_all_content_info_for_course(self, location): def get_all_content_thumbnails_for_course(self, location):
return self._get_all_content_for_course(location, get_thumbnails = True)
def get_all_content_for_course(self, location):
return self._get_all_content_for_course(location, get_thumbnails = False)
def _get_all_content_for_course(self, location, get_thumbnails = False):
''' '''
Returns a list of all static assets for a course. The return format is a list of dictionary elements. Example: Returns a list of all static assets for a course. The return format is a list of dictionary elements. Example:
...@@ -62,7 +71,8 @@ class MongoContentStore(ContentStore): ...@@ -62,7 +71,8 @@ class MongoContentStore(ContentStore):
] ]
''' '''
course_filter = Location(XASSET_LOCATION_TAG, category="asset",course=location.course,org=location.org) course_filter = Location(XASSET_LOCATION_TAG, category="asset" if not get_thumbnails else "thumbnail",
course=location.course,org=location.org)
# 'borrow' the function 'location_to_query' from the Mongo modulestore implementation # 'borrow' the function 'location_to_query' from the Mongo modulestore implementation
items = self.fs_files.find(location_to_query(course_filter)) items = self.fs_files.find(location_to_query(course_filter))
return list(items) return list(items)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment