forked from skoczen/django-ajax-uploader
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request skoczen#1 from chromano/master
Support for backends from chromano
- Loading branch information
Showing
8 changed files
with
213 additions
and
122 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
class AbstractUploadBackend(object): | ||
BUFFER_SIZE = 10485760 # 10MB | ||
|
||
def setup(self, filename): | ||
"""Responsible for doing any pre-processing needed before the upload | ||
starts.""" | ||
|
||
def update_filename(self, request, filename): | ||
"""Returns a new name for the file being uploaded.""" | ||
|
||
def upload_chunk(self, chunk): | ||
"""Called when a string was read from the client.""" | ||
raise NotImplementedError | ||
|
||
def upload_complete(self, request, filename): | ||
"""Overriden to performs any actions needed post-upload, and returns | ||
a dict to be added to the render / json context""" | ||
|
||
def upload(self, uploaded, filename, raw_data): | ||
try: | ||
if raw_data: | ||
# File was uploaded via ajax, and is streaming in. | ||
chunk = uploaded.read(self.BUFFER_SIZE) | ||
while len(chunk) > 0: | ||
self.upload_chunk(chunk) | ||
chunk = uploaded.read(self.BUFFER_SIZE) | ||
else: | ||
# File was uploaded via a POST, and is here. | ||
for chunk in uploaded.chunks(): | ||
self.upload_chunk(chunk) | ||
return True | ||
except: | ||
# things went badly. | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from io import FileIO, BufferedWriter | ||
import os | ||
from StringIO import StringIO | ||
|
||
from django.conf import settings | ||
|
||
from ajaxuploader.backends.base import AbstractUploadBackend | ||
|
||
class LocalUploadBackend(AbstractUploadBackend): | ||
UPLOAD_DIR = "uploads" | ||
|
||
def setup(self, filename): | ||
self._path = os.path.join( | ||
settings.MEDIA_ROOT, self.UPLOAD_DIR, filename) | ||
try: | ||
os.makedirs(os.path.realpath(os.path.dirname(self._path))) | ||
except: | ||
pass | ||
self._dest = BufferedWriter(FileIO(self._path, "wb")) | ||
|
||
def upload_chunk(self, chunk): | ||
self._dest.write(chunk) | ||
|
||
def upload_complete(self, request, filename): | ||
path = settings.MEDIA_URL + self.UPLOAD_DIR + "/" + filename | ||
return {"path": path} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from multiprocessing import Pool | ||
from StringIO import StringIO | ||
|
||
import boto | ||
from django.conf import settings | ||
|
||
from ajaxuploader.backends.base import AbstractUploadBackend | ||
|
||
class S3UploadBackend(AbstractUploadBackend): | ||
NUM_PARALLEL_PROCESSES = 4 | ||
|
||
def _upload_chunk(self, chunk): | ||
buffer = StringIO() | ||
buffer.write(chunk) | ||
self._pool.apply_async( | ||
self._mp.upload_part_from_file(buffer, self._counter)) | ||
buffer.close() | ||
self._counter += 1 | ||
|
||
def setup(self, filename): | ||
self._bucket = boto.connect_s3(settings.AWS_ACCESS_KEY_ID, | ||
settings.AWS_SECRET_ACCESS_KEY)\ | ||
.lookup(settings.AWS_BUCKET_NAME) | ||
self._mp = self._bucket.initiate_multipart_upload(filename) | ||
self._pool = Pool(processes=self.NUM_PARALLEL_PROCESSES) | ||
self._counter = 0 | ||
|
||
def upload_complete(self, request, filename): | ||
# Tie up loose ends, and finish the upload | ||
self._pool.close() | ||
self._pool.join() | ||
self._mp.complete_upload() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import os | ||
|
||
from django.conf import settings | ||
from sorl.thumbnail import get_thumbnail | ||
|
||
from ajaxuploader.backends.local import LocalUploadBackend | ||
|
||
class ThumbnailUploadBackend(LocalUploadBackend): | ||
DIMENSION = "100x100" | ||
KEEP_ORIGINAL = False | ||
|
||
def upload_complete(self, request, filename): | ||
thumbnail = get_thumbnail(self._path, self.DIMENSION) | ||
if not self.KEEP_ORIGINAL: | ||
os.unlink(self._path) | ||
return {"path": settings.MEDIA_URL + thumbnail.name} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,39 @@ | ||
<!doctype html> | ||
<head> | ||
<script src="{{STATIC_URL}}django-ajax-uploader/fileuploader.js" ></script> | ||
<link href="{{STATIC_URL}}django-ajax-uploader/fileuploader.css" media="screen" rel="stylesheet" type="text/css" /> | ||
<script> | ||
var uploader = new qq.FileUploader( { | ||
action: "/sample-upload/", | ||
element: $('#file-uploader')[0], | ||
multiple: true, | ||
onComplete: function( id, fileName, responseJSON ) { | ||
if( responseJSON.success ) | ||
alert( "success!" ) ; | ||
else | ||
alert( "upload failed!" ) ; | ||
}, | ||
onAllComplete: function( uploads ) { | ||
// uploads is an array of maps | ||
// the maps look like this: { file: FileObject, response: JSONServerResponse } | ||
alert( "All complete!" ) ; | ||
}, | ||
params: { | ||
'csrf_token': '{{ csrf_token }}', | ||
'csrf_name': 'csrfmiddlewaretoken', | ||
'csrf_xname': 'X-CSRFToken', | ||
}, | ||
}) ; | ||
</script> | ||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" ></script> | ||
<script src="{{STATIC_URL}}django-ajax-uploader/fileuploader.js" ></script> | ||
<link href="{{STATIC_URL}}django-ajax-uploader/fileuploader.css" media="screen" rel="stylesheet" type="text/css" /> | ||
</head> | ||
<body> | ||
<div id="file-uploader"> | ||
<noscript> | ||
<p>Please enable JavaScript to use file uploader.</p> | ||
</noscript> | ||
</div> | ||
<div id="file-uploader"> | ||
<noscript> | ||
<p>Please enable JavaScript to use file uploader.</p> | ||
</noscript> | ||
</div> | ||
|
||
<script> | ||
var uploader = new qq.FileUploader( { | ||
action: "{% url my_ajax_upload %}", | ||
element: $('#file-uploader')[0], | ||
multiple: true, | ||
onComplete: function( id, fileName, responseJSON ) { | ||
if( responseJSON.success ) | ||
alert( "success!" ) ; | ||
else | ||
alert( "upload failed!" ) ; | ||
}, | ||
onAllComplete: function( uploads ) { | ||
// uploads is an array of maps | ||
// the maps look like this: { file: FileObject, response: JSONServerResponse } | ||
alert( "All complete!" ) ; | ||
}, | ||
params: { | ||
'csrf_token': '{{ csrf_token }}', | ||
'csrf_name': 'csrfmiddlewaretoken', | ||
'csrf_xname': 'X-CSRFToken', | ||
}, | ||
}) ; | ||
</script> | ||
|
||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,97 +1,58 @@ | ||
from StringIO import StringIO | ||
from multiprocessing import Pool | ||
from django.http import HttpResponse, HttpResponseBadRequest, Http404 | ||
import boto | ||
import json | ||
from django.conf import settings | ||
|
||
from ajaxuploader.backends.s3 import S3UploadBackend | ||
|
||
class AjaxFileUploader(object): | ||
NUM_PARALLEL_PROCESSES = 4 | ||
BUFFER_SIZE = 10485760 # 10MB | ||
def __init__(self, backend=None): | ||
if backend is None: | ||
backend = S3UploadBackend | ||
self._backend = backend() | ||
|
||
def __call__(self,request): | ||
return self._ajax_upload(request) | ||
|
||
def _update_filename(self, request, filename): | ||
return filename | ||
|
||
def _upload_complete(self, request, filename): | ||
"""Overriden to performs any actions needed post-upload, and | ||
returns a dict to be added to the render / json context""" | ||
return {} | ||
|
||
def _upload_chunk(self, pool, mp, chunk, counter): | ||
buffer = StringIO() | ||
buffer.write(chunk) | ||
pool.apply_async(mp.upload_part_from_file(buffer, counter)) | ||
buffer.close() | ||
|
||
def _save_upload(self, uploaded, filename, raw_data ): | ||
try: | ||
bucket = boto.connect_s3(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY).lookup(settings.AWS_BUCKET_NAME) | ||
mp = bucket.initiate_multipart_upload(filename) | ||
pool = Pool(processes=self.NUM_PARALLEL_PROCESSES) | ||
counter = 0 | ||
|
||
if raw_data: | ||
# File was uploaded via ajax, and is streaming in. | ||
chunk = uploaded.read(self.BUFFER_SIZE) | ||
while len(chunk) > 0: | ||
counter += 1 | ||
self._upload_chunk(pool, mp, chunk, counter) | ||
chunk = uploaded.read(self.BUFFER_SIZE) | ||
else: | ||
# File was uploaded via a POST, and is here. | ||
for chunk in uploaded.chunks(): | ||
counter += 1 | ||
self._upload_chunk(pool, mp, chunk, counter) | ||
|
||
# Tie up loose ends, and finish the upload | ||
pool.close() | ||
pool.join() | ||
mp.complete_upload() | ||
return True | ||
|
||
except: | ||
# things went badly. | ||
return False | ||
|
||
|
||
def _ajax_upload(self, request): | ||
if request.method == "POST": | ||
if request.is_ajax(): | ||
# the file is stored raw in the request | ||
upload = request | ||
is_raw = True | ||
# AJAX Upload will pass the filename in the querystring if it is the "advanced" ajax upload | ||
# AJAX Upload will pass the filename in the querystring if it | ||
# is the "advanced" ajax upload | ||
try: | ||
filename = request.GET[ 'qqfile' ] | ||
filename = request.GET['qqfile'] | ||
except KeyError: | ||
return HttpResponseBadRequest( "AJAX request not valid" ) | ||
# not an ajax upload, so it was the "basic" iframe version with submission via form | ||
return HttpResponseBadRequest("AJAX request not valid") | ||
# not an ajax upload, so it was the "basic" iframe version with | ||
# submission via form | ||
else: | ||
is_raw = False | ||
if len( request.FILES ) == 1: | ||
# FILES is a dictionary in Django but Ajax Upload gives the uploaded file an | ||
# ID based on a random number, so it cannot be guessed here in the code. | ||
# Rather than editing Ajax Upload to pass the ID in the querystring, | ||
# observe that each upload is a separate request, | ||
# so FILES should only have one entry. | ||
# Thus, we can just grab the first (and only) value in the dict. | ||
upload = request.FILES.values()[ 0 ] | ||
if len(request.FILES) == 1: | ||
# FILES is a dictionary in Django but Ajax Upload gives | ||
# the uploaded file an ID based on a random number, so it | ||
# cannot be guessed here in the code. Rather than editing | ||
# Ajax Upload to pass the ID in the querystring, observe | ||
# that each upload is a separate request, so FILES should | ||
# only have one entry. Thus, we can just grab the first | ||
# (and only) value in the dict. | ||
upload = request.FILES.values()[0] | ||
else: | ||
raise Http404( "Bad Upload" ) | ||
raise Http404("Bad Upload") | ||
filename = upload.name | ||
|
||
# custom filename handler | ||
filename = self._update_filename(request, filename) | ||
|
||
# custom filename handler | ||
filename = (self._backend.update_filename(request, filename) | ||
or filename) | ||
# save the file | ||
success = self._save_upload( upload, filename, is_raw ) | ||
|
||
self._backend.setup(filename) | ||
success = self._backend.upload(upload, filename, is_raw) | ||
# callback | ||
extra_context = self._upload_complete(request, filename) | ||
extra_context = self._backend.upload_complete(request, filename) | ||
|
||
# let Ajax Upload know whether we saved it or not | ||
ret_json = { 'success': success, } | ||
ret_json.update(extra_context) | ||
return HttpResponse( json.dumps( ret_json ) ) | ||
ret_json = {'success': success, 'filename': filename} | ||
if extra_context is not None: | ||
ret_json.update(extra_context) | ||
|
||
return HttpResponse(json.dumps(ret_json)) |