Mit dem SWFUpload Cookies Plugin und einer Django Middleware, lässt sich der Flash-basierte SWFUpload einfach und komfortabel in Django integrieren. Der Vorteil der ganze Prozedur ist für die Benutzer der Seite ein wesentlich einfacher Upload Mechanismus, welcher es erlaubt mehrere Dateien gleichzeitig hochzuladen. Mit diesem kleinen Tutorial möchte ich euch zeigen wie das ganze funktioniert.
Ein paar Worte zu Beginn
Warum überhaupt ein ganzes Tutorial, mehr als einwenig Javascript und Python Code ist das ganze ja nicht. Eigentlich ja, jedoch gibt es genau dort ein kleines Problem. Django’s Authentifizierungssystem gibt bei jeder Server Anfrage den Session-Token via Browser Cookie weiter. SWFUpload hingegen sendet den sessionid
Cookie nicht weiter. Somit sieht Django im View nur das request.user
ein AnonymousUser
ist, egal ob der Uploader authentifiziert war oder nicht.
Um das Problem zu beheben benötigen wir das SWFUpload Cookie Plugin, was nicht mehr als ein wenig JavaScript Code ist, welcher die aktuellen Cookies hinzufügt. Dann werden wir eine Middleware in Django schreiben welche uns den Session Token extrahiert und zurück zu request.COOKIES
schreibt.
Vorbereitung
Ich zeige das ganze Vorgehen nun bei dem Beispiel einer (einfachen) Bildergalerie in Django und ich gehe davon aus das euer Webserver statische Dateien wie in settings.MEDIA_ROOT
ausliefert und diese im Web via settings.MEDIA_URL
zugänglich macht (siehe Django Dokumentation).
Zuerst einmal unser Model in gallery.models
from django.db import Models from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from tagging.fields import TagField from tagging.models import Tag from category.models import Category def get_image_path(instance, filename): '''Create dynamic image upload path for every gallery''' return 'gallery/%s/%s' % (str(instance.album.slug), filename) class Album(models.Model): '''Model for a gallery''' name = models.CharField(max_length=150, verbose_name=_('Name')) slug = models.SlugField(max_length=150, unique=True) content = models.TextField(verbose_name=_('Beschreibung'), blank=True, null=True) user = models.ForeinKey(User, related_name='user') category = models.ForeignKey(Category, related_name='category', verbose_name=_('Kategorie') def __unicode__(self): return self.name @models.permalink def get_absolute_url(self): return 'gallery_detail', (), {'slug': self.slug} class Meta: verbose_name = _(u'Fotoalbum') verbose_name_plural = _(u'Fotoalben') class Picture(models.Model): '''Model for images. Every Image must have a gallery.''' album = models.ForeignKey(Album, related_name='album') image = models.ImageField(max_length=180, upload_to=get_image_path, verbose_name=_('Bild')) content = models.TextField(verbose_name=_('Beschreibung'), blank=True, null=True) tags = TagField() def set_tags(self, tags): Tag.objects.update_tags(self, tags) def get_tags(self, tags): return Tag.objects.get_for_object(self) def __unicode__(self): return '%s Picture #%d'% (self.album.name, self.id) class Meta: verbose_name = _(u'Foto') verbose_name_plural = _(u'Fotos')
Nun gehen wir davon aus, das in unser View per gallery_add erreicht wird. Will heißen in der urls.py wird der view mit diesem Name versehen. Bevor wir uns jedoch um den View kümmern integrieren wir erst einmal SWFUpload ins Template (bei mir gallery/add.html). Also zunächst einmal das Core Package von SWFUpload downloaden, Archiv entpacken und die Dateien Flash/swfupload.swf
, swfupload.js
und plugins/swfupload.cookies.js
ins settings.MEDIA_ROOT
Verzeichnis verschieben. Im Template selbst nun die JavaScript Dateien einfügen.
SWFUpload integrieren
Der nächste Schritt ist SWFUpload mit ein klein bisschen JavaScript verfügbar zu machen. In der Dokumentation von SWFUpload ist vieles weitaus ausführlicher beschrieben, wie ich das hier mache, ich beschränke mich auf wesentliche.
var swfu; SWFUpload.onload = function () { var settings = { flash_url: '{{ MEDIA_URL }}swfupload.swf', upload_url: '{% url gallery_add slug=slug %}', file_size_limit: '10 MB', file_types: '*.*', file_types_description: 'Bilddateien', file_upload_limit: 100, file_queue_limit: 0, custom_settings: { progressTarget : 'fsUploadProgress', cancelButtonId : 'btnCancel' }, post_params: { 'csrfmiddlewaretoken': '{{ csrf_token }}', 'slug': '{{ slug }}' }, debug: false, // Button Settings button_placeholder_id: "spanButtonPlaceholder", button_width: 128, button_height: 30, button_window_mode: SWFUpload.WINDOW_MODE.TRANSPARENT, button_cursor: SWFUpload.CURSOR.HAND, // The event handler functions are defined in handlers.js swfupload_loaded_handler: swfUploadLoaded, file_queued_handler: fileQueued, file_queue_error_handler: fileQueueError, file_dialog_complete_handler: fileDialogComplete, upload_start_handler: uploadStart, upload_progress_handler: uploadProgress, upload_error_handler: uploadError, upload_success_handler: uploadSuccess, upload_complete_handler: uploadComplete, queue_complete_handler: queueComplete, // Queue plugin event // SWFObject settings minimum_flash_version: "9.0.28", swfupload_pre_load_handler: swfUploadPreLoad, swfupload_load_failed_handler: swfUploadLoadFailed }; swfu = new SWFUpload(settings); }
Der Parameter flash_url
muss auf die swfupload.url
verweisen, der Parameter upload_url
verweist demnach auf unseren View, den ich später vorstelle. Was zu beachten ist, die URL die zum View führt wird via Slug weitergegeben.
Wie man bereits erkennt hab ich hier custom_settings
angegeben und die ganzen Handler-Funktionen ausgelagert. Aber es reicht auch ein minimales Setup wie in der Dokumenation oder Demos beschrieben.
Wichtig sind jedoch die Parameter in post_params
! Hier geben wir den csrf_token
sowie unseren Slug via POST mit. Das ist wichtig, sonst wird Django böse wenn wir im POST ohne Cross-Site-Request-Forgey – Token ankommen!
Hier meine komplette gallery/add.html:
{% extends "base.html" %} {% load i18n %} {% block title %}{% trans "Bilder hinzufügen | fha-django-gallery" %}{% endblock %} {% block extrahead %} <link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}swfupload.css" /> <script type="text/javascript" src="{{ MEDIA_URL }}swfupload.js"></script> <script type="text/javascript" src="{{ MEDIA_URL }}swfupload.queue.js"></script> <script type="text/javascript" src="{{ MEDIA_URL }}swfupload.swfobject.js"></script> <script type="text/javascript" src="{{ MEDIA_URL }swfupload.cookies.js"></script> <script type="text/javascript" src="{{ MEDIA_URL }swfupload.fileprogress.js"></script> <script type="text/javascript" src="{{ MEDIA_URL }}swfupload.handlers.js"></script> <script type="text/javascript"> var swfu; SWFUpload.onload = function () { var settings = { flash_url: '{{ MEDIA_URL }swfupload.swf', upload_url: '{% url gallery_add slug=slug %}', file_size_limit: '10 MB', file_types: '*.*', file_types_description: 'Bilddateien', file_upload_limit: 100, file_queue_limit: 0, custom_settings: { progressTarget : 'fsUploadProgress', cancelButtonId : 'btnCancel' }, post_params: { 'csrfmiddlewaretoken': '{{ csrf_token }}', 'slug': '{{ slug }}' }, debug: false, // Button Settings button_placeholder_id: "spanButtonPlaceholder", button_width: 128, button_height: 30, button_window_mode: SWFUpload.WINDOW_MODE.TRANSPARENT, button_cursor: SWFUpload.CURSOR.HAND, // The event handler functions are defined in handlers.js swfupload_loaded_handler: swfUploadLoaded, file_queued_handler: fileQueued, file_queue_error_handler: fileQueueError, file_dialog_complete_handler: fileDialogComplete, upload_start_handler: uploadStart, upload_progress_handler: uploadProgress, upload_error_handler: uploadError, upload_success_handler: uploadSuccess, upload_complete_handler: uploadComplete, queue_complete_handler: queueComplete, // Queue plugin event // SWFObject settings minimum_flash_version: "9.0.28", swfupload_pre_load_handler: swfUploadPreLoad, swfupload_load_failed_handler: swfUploadLoadFailed }; swfu = new SWFUpload(settings); } </script> {% endblock %} {% url gallery_add slug=slug as gallery_add %} {% block breadcrumbs %} {% blocktrans %} Sie befinden sich hier: <a href="/">fha-django-gallery</a> » <a href="/account/profile/">Account</a> » <a href="{{ gallery_add }}">Bilder hinzufügen</a> {% endblocktrans %} {% endblock %} {% block content %} <h2>{% trans "Bilder zur Galerie hinzufügen" %}</h2> <p>{% trans "Um Bilder zur Galerie hinzuzufügen, wählen sie diese bitte aus und klicken anschließend auf Weiter" %}</p> <p><a href="{% url gallery_edit slug=slug %}" class="button blue">{% trans "Weiter" %}</a></p> <div id="divSWFUploadUI"> <div class="fieldset flash" id="fsUploadProgress"> <span class="legend">Warteschlange</span> </div> <p id="divStatus">0 Dateien hochgeladen</p> <p> <span id="spanButtonPlaceholder"></span> <button id="btnUpload" class="red" type="button">Dateien auswählen</button> <button id="btnCancel" type="button" disabled="disabled">Uploads abbrechen</button> </p> </div> <noscript style="background-color: #FFFF66; border-top: solid 4px #FF9966; border-bottom: solid 4px #FF9966; margin: 10px 25px; padding: 10px 15px;"> {% trans "We're sorry. SWFUpload could not load. You must have JavaScript enabled to enjoy SWFUpload." %} </noscript> <div id="divLoadingContent" class="content" style="background-color: #FFFF66; border-top: solid 4px #FF9966; border-bottom: solid 4px #FF9966; margin: 10px 25px; padding: 10px 15px; display: none;"> {% trans "SWFUpload is loading. Please wait a moment..." %} </div> <div id="divLongLoading" class="content" style="background-color: #FFFF66; border-top: solid 4px #FF9966; border-bottom: solid 4px #FF9966; margin: 10px 25px; padding: 10px 15px; display: none;"> {% trans "SWFUpload is taking a long time to load or the load has failed. Please make sure that the Flash Plugin is enabled and that a working version of the Adobe Flash Player is installed." %} </div> <div id="divAlternateContent" class="content" style="background-color: #FFFF66; border-top: solid 4px #FF9966; border-bottom: solid 4px #FF9966; margin: 10px 25px; padding: 10px 15px; display: none;"> {% blocktrans %} We're sorry. SWFUpload could not load. You may need to install or upgrade Flash Player. Visit the <a href="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash">Adobe website</a> to get the Flash Player. {% endblocktrans %} </div> {% endblock %}
Schreiben der Middleware
Als vorletzten Schritt müssen wir eine Middleware in Django schreiben. Keine Sorge klingt schlimmer wie es eigentlich ist ;). In der Middleware müssen wir mehrere Dinge beachten: Den Slug extrhahieren, genauso mit dem csrf_token fungieren und ganz wichtig die sessionid
aus dem POST rausholen und in request.COOKIES
schreiben. Das ganze am Besten unter gallery/middleware.py schreiben.
Hier nun die komplette Middleware:
"""This middleware etracts slug, csrf_token and sessionid and gives them django back.""" from django.conf import settings from django.core.urlresolvers import reverse class SWFUploadMiddleware(object): def process_request(self, request): if request.POST.has_key('slug'): if (request.method == 'POST') and (request.path == reverse('gallery_add', kwargs={'slug': request.POST['slug']})): if request.POST.has_key(settings.SESSION_COOKIE_NAME): request.COOKIES[settings.SESSION_COOKIE_NAME] = request.POST[settings.SESSION_COOKIE_NAME] if request.POST.has_key('csrftoken'): request.COOKIES['csrftoken'] = request.POST['csrftoken']
Wie gesagt nicht viel Code und relativ einfach ist es auch. Jedoch müssen wir die Middleware noch in die settings.py
einfügen damit Django diese auch kennt. Das ganze sollte am Besten an die zweite Stelle der Middlewares eingefügt werden.
MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'gallery.middleware.SWFUploadMiddleware', #adds our new middleware 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', )
Den Upload verarbeiten
So als letztes brauchen wir noch den View der unsere hochgeladenen Bilder entgegen nimmt und verarbeitet. Selbstverständlich funktioniert das nur wenn man angemeldet ist… wär ja schrecklich wenn jeder User irgendwas hochladen dürfte…
Das ganze ist nicht besonders kompliziert, Django typisch halt. Die Dateien kommen in request.FILES
an, über diese müssen wir iterieren und daraus ein Objekt erstellen.
from django.shortcuts import render_to_response from django.template import RequestContext from django.http import HttpResponse from django.contrib.auth.decorators import login_required from django.middleware.csrf import get_token from gironimo.gallery.models import Album, Picture @login_required def add(request, slug): '''Adds new images to a given gallery, only authenticated access''' if request.method == 'POST': album = Album.objects.get(slug=slug) for field_name in request.FILES: image = request.FILES[field_name] picture = Picture.objects.create(album=album, image=image) picture.save() # say ok to SWFUploader return HttpResponse("ok", mimetype="text/plain") else: return render_to_response('gallery/add.html', { 'slug': slug, 'csrf_token': get_token(request) }, context_instance=RequestContext(request))
Das wars! Wichtig ist nur das wir den csrf_token
mitgeben und SWFUpload ein HttpResponse
zurück geben, damit er weiß das alles OK war.
Nun sollte das ganze bei euch wunderbar funktionieren, jedoch bleibt noch viel Arbeit das ganze schön aussehen zu lassen denn Flash Sachen nerven beim designen.
Sollte ich irgendwo einen Fehler gemacht haben oder Blödsinn aufgeschrieben haben, sagt mir das wär echt super. Auch für Verbesserungsvorschläge oder andere Ideen wäre ich dankbar (natürlich ist ein Danke auch super ;))