# Imports
from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponse, HttpResponseBadRequest, Http404, HttpResponseNotAllowed
import json
from superdjango.storage.backends.ajax.local import LocalAjaxStorage
from superdjango.storage.signals import ajax_file_uploaded
# Exports
__all__ = (
"AjaxFileUploader",
)
# Classes
[docs]class AjaxFileUploader(object):
"""This pseudo-view class provides support for uploading files via AJAX.
**Setup**
The JavaScript files for AJAX upload are located in the assets app, so make sure you have this in your
``settings.py`` file:
.. code-block:: python
INSTALL_APPS = [
# ...
"superdjango.assets.apps.DefaultConfig",
# ...
]
**Usage**
Create a view for handling the upload. In the example below, the `ajax_uploader` view is initialized (without any
options) at the bottom of the file. The ``upload_form()`` view is used to present an AJAX-enabled upload form. You
may wish to integrate this into your own form view and template instead.
.. code-block:: python
from django.middleware.csrf import get_token
from django.shortcuts import render_to_response
from django.template import RequestContext
from superdjango.ajax.uploads.views import AjaxFileUploader
def upload_form(request):
csrf_token = get_token(request)
return render_to_response(
'upload_form.html',
{'csrf_token': csrf_token},
context_instance=RequestContext(request)
)
ajax_uploader = AjaxFileUploader()
Add the views to your main ``urls.py`` file:
.. code-block:: python
from superdjango.ajax import views as ajax_upload_views
urlpatterns = [
# ...
path(r'ajax/upload/start/)', ajax_upload_views.upload_form, name="upload_form"),
path(r'ajax/upload/', ajax_upload_views.import_uploader, name="ajax_upload"),
# ...
]
Create a template or add to the template where the upload should occur:
.. code-block:: html
{% load i18n %}
{% load static %}
<!doctype html>
<head>
<script src="{% static "bundled/fileuploader/js/fileuploader.js" %}" ></script>
<link href="{% static "bundled/fileuploader/css/fileuploader.css" %}" rel="stylesheet" />
<script>
$(function(){
var uploader = new qq.FileUploader({
action: "{% url 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>
</head>
<body>
<div id="file-uploader">
<noscript>
<p>{% trans "Please enable JavaScript to use the file uploader." %}</p>
</noscript>
</div>
</body>
</html>
"""
[docs] def __init__(self, backend=None, **kwargs):
"""Initialize the view.
:param backend: The backend class to use. Defaults to :py:class:`LocalAjaxStorage`.
kwargs are passed to the backend upon instantiation.
"""
if backend is None:
backend = LocalAjaxStorage
self.get_backend = lambda: backend(**kwargs)
def __call__(self, request, *args, **kwargs):
return self._ajax_upload(request, *args, **kwargs)
def _ajax_upload(self, request, *args, **kwargs):
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
try:
if 'qqfile' in request.GET:
filename = request.GET['qqfile']
else:
filename = request.REQUEST['qqfilename']
except KeyError:
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]
else:
raise Http404("Bad Upload")
filename = upload.name
backend = self.get_backend()
# custom filename handler
filename = (backend.update_filename(request, filename, *args, **kwargs)
or filename)
# save the file
backend.setup(filename, *args, **kwargs)
success = backend.upload(upload, filename, is_raw, *args, **kwargs)
if success:
ajax_file_uploaded.send(sender=self.__class__, backend=backend, request=request)
# callback
extra_context = backend.upload_complete(request, filename, *args, **kwargs)
# let Ajax Upload know whether we saved it or not
ret_json = {'success': success, 'filename': filename}
if extra_context is not None:
ret_json.update(extra_context)
# although "application/json" is the correct content type, IE throws a fit
return HttpResponse(json.dumps(ret_json, cls=DjangoJSONEncoder), content_type='text/html; charset=utf-8')
else:
response = HttpResponseNotAllowed(['POST'])
response.write("ERROR: Only POST allowed")
return response