227 lines
8.5 KiB
Python
227 lines
8.5 KiB
Python
import logging
|
|
from functools import partial
|
|
|
|
from django.http import HttpResponseBadRequest
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.views.decorators.http import require_http_methods
|
|
from django_future.csrf import ensure_csrf_cookie
|
|
from django.core.urlresolvers import reverse
|
|
from django.views.decorators.http import require_POST
|
|
|
|
from mitxmako.shortcuts import render_to_response
|
|
from cache_toolbox.core import del_cached_content
|
|
|
|
from xmodule.contentstore.django import contentstore
|
|
from xmodule.modulestore.django import modulestore
|
|
from xmodule.modulestore import Location
|
|
from xmodule.contentstore.content import StaticContent
|
|
from xmodule.util.date_utils import get_default_time_display
|
|
from xmodule.modulestore import InvalidLocationError
|
|
from xmodule.exceptions import NotFoundError
|
|
|
|
from .access import get_location_and_verify_access
|
|
from util.json_request import JsonResponse
|
|
import json
|
|
from django.utils.translation import ugettext as _
|
|
|
|
|
|
__all__ = ['asset_index', 'upload_asset']
|
|
|
|
|
|
@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 = get_location_and_verify_access(request, org, course, name)
|
|
|
|
upload_asset_callback_url = reverse('upload_asset', kwargs={
|
|
'org': org,
|
|
'course': course,
|
|
'coursename': name
|
|
})
|
|
|
|
course_module = modulestore().get_item(location)
|
|
|
|
course_reference = StaticContent.compute_location(org, course, name)
|
|
assets = contentstore().get_all_content_for_course(course_reference)
|
|
|
|
# sort in reverse upload date order
|
|
assets = sorted(assets, key=lambda asset: asset['uploadDate'], reverse=True)
|
|
|
|
asset_json = []
|
|
for asset in assets:
|
|
asset_id = asset['_id']
|
|
asset_location = StaticContent.compute_location(asset_id['org'], asset_id['course'], asset_id['name'])
|
|
# note, due to the schema change we may not have a 'thumbnail_location' in the result set
|
|
_thumbnail_location = asset.get('thumbnail_location', None)
|
|
thumbnail_location = Location(_thumbnail_location) if _thumbnail_location is not None else None
|
|
|
|
asset_locked = asset.get('locked', False)
|
|
asset_json.append(_get_asset_json(asset['displayname'], asset['uploadDate'], asset_location, thumbnail_location, asset_locked))
|
|
|
|
return render_to_response('asset_index.html', {
|
|
'context_course': course_module,
|
|
'asset_list': json.dumps(asset_json),
|
|
'upload_asset_callback_url': upload_asset_callback_url,
|
|
'update_asset_callback_url': reverse('update_asset', kwargs={
|
|
'org': org,
|
|
'course': course,
|
|
'name': name
|
|
})
|
|
})
|
|
|
|
|
|
@require_POST
|
|
@ensure_csrf_cookie
|
|
@login_required
|
|
def upload_asset(request, org, course, coursename):
|
|
'''
|
|
This method allows for POST uploading of files into the course asset
|
|
library, which will be supported by GridFS in MongoDB.
|
|
'''
|
|
# construct a location from the passed in path
|
|
location = get_location_and_verify_access(request, org, course, coursename)
|
|
|
|
# Does the course actually exist?!? Get anything from it to prove its
|
|
# existence
|
|
try:
|
|
modulestore().get_item(location)
|
|
except:
|
|
# no return it as a Bad Request response
|
|
logging.error('Could not find course' + location)
|
|
return HttpResponseBadRequest()
|
|
|
|
if 'file' not in request.FILES:
|
|
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
|
|
upload_file = request.FILES['file']
|
|
filename = upload_file.name
|
|
mime_type = upload_file.content_type
|
|
|
|
content_loc = StaticContent.compute_location(org, course, filename)
|
|
|
|
chunked = upload_file.multiple_chunks()
|
|
sc_partial = partial(StaticContent, content_loc, filename, mime_type)
|
|
if chunked:
|
|
content = sc_partial(upload_file.chunks())
|
|
tempfile_path = upload_file.temporary_file_path()
|
|
else:
|
|
content = sc_partial(upload_file.read())
|
|
tempfile_path = None
|
|
|
|
# first let's see if a thumbnail can be created
|
|
(thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(
|
|
content,
|
|
tempfile_path=tempfile_path
|
|
)
|
|
|
|
# delete cached thumbnail even if one couldn't be created this time (else
|
|
# the old thumbnail will continue to show)
|
|
del_cached_content(thumbnail_location)
|
|
# now store thumbnail location only if we could create it
|
|
if thumbnail_content is not None:
|
|
content.thumbnail_location = thumbnail_location
|
|
|
|
# then commit the content
|
|
contentstore().save(content)
|
|
del_cached_content(content.location)
|
|
|
|
# readback the saved content - we need the database timestamp
|
|
readback = contentstore().find(content.location)
|
|
|
|
locked = getattr(content, 'locked', False)
|
|
response_payload = {
|
|
'asset': _get_asset_json(content.name, readback.last_modified_at, content.location, content.thumbnail_location, locked),
|
|
'msg': _('Upload completed')
|
|
}
|
|
|
|
return JsonResponse(response_payload)
|
|
|
|
|
|
@require_http_methods(("DELETE", "POST", "PUT"))
|
|
@login_required
|
|
@ensure_csrf_cookie
|
|
def update_asset(request, org, course, name, asset_id):
|
|
"""
|
|
restful CRUD operations for a course asset.
|
|
Currently only DELETE, POST, and PUT methods are implemented.
|
|
|
|
org, course, name: Attributes of the Location for the item to edit
|
|
asset_id: the URL of the asset (used by Backbone as the id)
|
|
"""
|
|
def get_asset_location(asset_id):
|
|
""" Helper method to get the location (and verify it is valid). """
|
|
try:
|
|
return StaticContent.get_location_from_path(asset_id)
|
|
except InvalidLocationError as err:
|
|
# return a 'Bad Request' to browser as we have a malformed Location
|
|
return JsonResponse({"error": err.message}, status=400)
|
|
|
|
get_location_and_verify_access(request, org, course, name)
|
|
|
|
if request.method == 'DELETE':
|
|
loc = get_asset_location(asset_id)
|
|
# Make sure the item to delete actually exists.
|
|
try:
|
|
content = contentstore().find(loc)
|
|
except NotFoundError:
|
|
return JsonResponse(status=404)
|
|
|
|
# ok, save the content into the trashcan
|
|
contentstore('trashcan').save(content)
|
|
|
|
# see if there is a thumbnail as well, if so move that as well
|
|
if content.thumbnail_location is not None:
|
|
try:
|
|
thumbnail_content = contentstore().find(content.thumbnail_location)
|
|
contentstore('trashcan').save(thumbnail_content)
|
|
# hard delete thumbnail from origin
|
|
contentstore().delete(thumbnail_content.get_id())
|
|
# remove from any caching
|
|
del_cached_content(thumbnail_content.location)
|
|
except:
|
|
logging.warning('Could not delete thumbnail: ' + content.thumbnail_location)
|
|
|
|
# delete the original
|
|
contentstore().delete(content.get_id())
|
|
# remove from cache
|
|
del_cached_content(content.location)
|
|
return JsonResponse()
|
|
|
|
elif request.method in ('PUT', 'POST'):
|
|
# We don't support creation of new assets through this
|
|
# method-- just changing the locked state.
|
|
modified_asset = json.loads(request.body)
|
|
asset_id = modified_asset['url']
|
|
location = get_asset_location(asset_id)
|
|
contentstore().set_attr(location, 'locked', modified_asset['locked'])
|
|
# Delete the asset from the cache so we check the lock status the next time it is requested.
|
|
del_cached_content(location)
|
|
|
|
return JsonResponse(modified_asset, status=201)
|
|
|
|
|
|
def _get_asset_json(display_name, date, location, thumbnail_location, locked):
|
|
"""
|
|
Helper method for formatting the asset information to send to client.
|
|
"""
|
|
asset_url = StaticContent.get_url_path_from_location(location)
|
|
return {
|
|
'display_name': display_name,
|
|
'date_added': get_default_time_display(date),
|
|
'url': asset_url,
|
|
'portable_url': StaticContent.get_static_path_from_location(location),
|
|
'thumbnail': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None,
|
|
'locked': locked,
|
|
# Needed for Backbone delete/update.
|
|
'id': asset_url
|
|
}
|