262 lines
9.2 KiB
Python
262 lines
9.2 KiB
Python
import logging
|
|
import json
|
|
import os
|
|
import tarfile
|
|
import shutil
|
|
import cgi
|
|
import re
|
|
from functools import partial
|
|
from tempfile import mkdtemp
|
|
from path import path
|
|
|
|
from django.conf import settings
|
|
from django.http import HttpResponse, HttpResponseBadRequest
|
|
from django.contrib.auth.decorators import login_required
|
|
from django_future.csrf import ensure_csrf_cookie
|
|
from django.core.urlresolvers import reverse
|
|
from django.core.servers.basehttp import FileWrapper
|
|
from django.core.files.temp import NamedTemporaryFile
|
|
from django.views.decorators.http import require_POST, require_http_methods
|
|
|
|
from mitxmako.shortcuts import render_to_response
|
|
from cache_toolbox.core import del_cached_content
|
|
from auth.authz import create_all_course_groups
|
|
|
|
from xmodule.modulestore.xml_importer import import_from_xml
|
|
from xmodule.contentstore.django import contentstore
|
|
from xmodule.modulestore.xml_exporter import export_to_xml
|
|
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, SerializationError
|
|
|
|
from .access import get_location_and_verify_access
|
|
from util.json_request import JsonResponse
|
|
|
|
|
|
__all__ = ['asset_index', 'upload_asset']
|
|
|
|
def assets_to_json_dict(assets):
|
|
"""
|
|
Transform the results of a contentstore query into something appropriate
|
|
for output via JSON.
|
|
"""
|
|
ret = []
|
|
for asset in assets:
|
|
obj = {
|
|
"name": asset.get("displayname", ""),
|
|
"chunkSize": asset.get("chunkSize", 0),
|
|
"path": asset.get("filename", ""),
|
|
"length": asset.get("length", 0),
|
|
}
|
|
uploaded = asset.get("uploadDate")
|
|
if uploaded:
|
|
obj["uploaded"] = uploaded.isoformat()
|
|
thumbnail = asset.get("thumbnail_location")
|
|
if thumbnail:
|
|
obj["thumbnail"] = thumbnail
|
|
id_info = asset.get("_id")
|
|
if id_info:
|
|
obj["id"] = "/{tag}/{org}/{course}/{revision}/{category}/{name}" \
|
|
.format(
|
|
org=id_info.get("org", ""),
|
|
course=id_info.get("course", ""),
|
|
revision=id_info.get("revision", ""),
|
|
tag=id_info.get("tag", ""),
|
|
category=id_info.get("category", ""),
|
|
name=id_info.get("name", ""),
|
|
)
|
|
ret.append(obj)
|
|
return ret
|
|
|
|
|
|
@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)
|
|
|
|
if request.META.get('HTTP_ACCEPT', "").startswith("application/json"):
|
|
return JsonResponse(assets_to_json_dict(assets))
|
|
|
|
asset_display = []
|
|
for asset in assets:
|
|
asset_id = asset['_id']
|
|
display_info = {}
|
|
display_info['displayname'] = asset['displayname']
|
|
display_info['uploadDate'] = get_default_time_display(asset['uploadDate'])
|
|
|
|
asset_location = StaticContent.compute_location(asset_id['org'], asset_id['course'], asset_id['name'])
|
|
display_info['url'] = StaticContent.get_url_path_from_location(asset_location)
|
|
display_info['portable_url'] = StaticContent.get_static_path_from_location(asset_location)
|
|
|
|
# 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
|
|
display_info['thumb_url'] = StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None
|
|
|
|
asset_display.append(display_info)
|
|
|
|
return render_to_response('asset_index.html', {
|
|
'context_course': course_module,
|
|
'assets': asset_display,
|
|
'upload_asset_callback_url': upload_asset_callback_url,
|
|
'remove_asset_callback_url': reverse('remove_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
|
|
|
|
thumbnail_content = None
|
|
thumbnail_location = 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)
|
|
|
|
response_payload = {
|
|
'displayname': content.name,
|
|
'uploadDate': get_default_time_display(readback.last_modified_at),
|
|
'url': StaticContent.get_url_path_from_location(content.location),
|
|
'portable_url': StaticContent.get_static_path_from_location(content.location),
|
|
'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location)
|
|
if thumbnail_content is not None else None,
|
|
'msg': 'Upload completed'
|
|
}
|
|
|
|
response = JsonResponse(response_payload)
|
|
return response
|
|
|
|
|
|
@ensure_csrf_cookie
|
|
@login_required
|
|
def remove_asset(request, org, course, name):
|
|
'''
|
|
This method will perform a 'soft-delete' of an asset, which is basically to
|
|
copy the asset from the main GridFS collection and into a Trashcan
|
|
'''
|
|
get_location_and_verify_access(request, org, course, name)
|
|
|
|
location = request.POST['location']
|
|
|
|
# make sure the location is valid
|
|
try:
|
|
loc = StaticContent.get_location_from_path(location)
|
|
except InvalidLocationError:
|
|
# return a 'Bad Request' to browser as we have a malformed Location
|
|
response = HttpResponse()
|
|
response.status_code = 400
|
|
return response
|
|
|
|
# also make sure the item to delete actually exists
|
|
try:
|
|
content = contentstore().find(loc)
|
|
except NotFoundError:
|
|
response = HttpResponse()
|
|
response.status_code = 404
|
|
return response
|
|
|
|
# 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:
|
|
pass # OK if this is left dangling
|
|
|
|
# delete the original
|
|
contentstore().delete(content.get_id())
|
|
# remove from cache
|
|
del_cached_content(content.location)
|
|
|
|
return HttpResponse()
|
|
|
|
|