Upload Status mit Django und AJAX anzeigen


Django Logo

Auf einigen Seiten findet man beim Hochladen einer Datei eine kleine Leiste, mit der der Status des Uploads angezeigt wird. So auch zum Beispiel auf Vimeo.

So etwas ist auch mit Django relativ einfach zu machen und ich werde in diesem Artikel zeigen, wie es funktioniert.

Warnung: Dieser Artikel ist nicht für Anfänger gedacht und setzt etwas Erfahrung mit Django voraus.

Zuerst erstellen wir ein Django Projekt und editiert die settings.py. Dort fügt man die Datenbankeinstellungen ein, da wir später ein einfaches Model verwenden werden. Ich nenne das Projekt einfach upload.

django-admin.py startproject upload

Als nächstes erstellen wir eine Application. Ich werde die Upload Status Bar an einer einfachen Fotogalerie demonstrieren, also nennen wir diese gallery.

./manage.py startapp gallery

Dort erstellen wir jetzt ein Photo-Model (gallery/models.py):

from django.db import models
 
class Photo(models.Model):
	photo = models.ImageField('Foto', upload_to='photos/')

Nach dem wir 'gallery' in die INSTALLED_APPS in der settings.py eigetragen haben, können wir die Datenbank synchronisieren:

./manage.py syncdb

Nun erstellen wir einen einfachen View, der alle Bilder anzeigt und auch Uploads zulässt (gallery/views.py):

from django.shortcuts import render_to_response
from django.template import RequestContext
from django import forms
from gallery.models import Photo
from django.http import HttpResponseRedirect
 
class PhotoUploadForm(forms.ModelForm):
	class Meta:
		model = Photo
 
def gallery(request):
	if request.method == 'POST':
		form = PhotoUploadForm(request.POST, request.FILES)
		if form.is_valid():
			form.save()
			return HttpResponseRedirect(request.path)
	else:
		form = PhotoUploadForm()
	photo_list = Photo.objects.all()
	return render_to_response('gallery/gallery.html', {
		'photo_list': photo_list,
		'form': form,
	}, context_instance=RequestContext(request))

Dazu auch noch das passende Template (gallery/templates/gallery/gallery.html):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3. org/1999/xhtml">
	<head>
		<title>Galerie</title>
		<style type="text/css">
			body {
				font-family: sans-serif;
				font-size: 10pt;
				padding: 25px;
				margin: 0;
			}
			.photo {
				border: 1px solid #ddd;
				background-color: #fafafa;
				padding: 15px;
				margin: 10px;
				width: 200px;
				height: 200px;
				float: left;
			}
			.photo img {
				max-width: 200px;
				max-height: 200px;
				margin: auto;
			}
			.upload-progress {
				position: fixed;
				left: 0px;
				bottom: 50px;
				background-color: #fff;
				border: 1px solid #ddd;
				padding: 10px;
				color: #aaa;
				text-align: right;
			}
			.progress-info span {
				font-size: 2.25em;
				font-weight: bold;
				display: block;
			}
		</style>
		<script type="text/javascript" src="{{ MEDIA_URL }}jquery.js"></script>
		<script type="text/javascript" src="{{ MEDIA_URL }}upload.js"></script>
	</head>
	<body>
		<h1>Neues Foto hochladen</h1>
		<form method="post" enctype="multipart/form-data" action="/">
			{{ form.as_p }}
			<p><input type="submit" value="Foto hochladen" /></p>
		</form>
		<h1>Fotos ansehen</h1>
		{% for photo in photo_list %}
		<div class="photo">
			<img src="{{ photo.get_photo_url }}" />
		</div>
		{% endfor %}
		</body>
</html>

Das ganze tragen wir jetzt noch in die URL-Konfiguration ein (urls.py):

(r'^$', 'gallery.views.gallery'),

Jetzt funktioniert der Upload und wir können die Galerie eigentlich schon nutzen. Das sieht dann so aus:

Jetzt wird es etwas komplizierter: Wir müssen einen sogenannten FileUploadHandler “einbinden”. Diesen findet man auf djangosnippets.org. Um diesen nutzen zu können, muss Caching aktiviert sein! Standardmäßig ist das mit dem Wert 'locmem:///' für CACHE_BACKEND der Fall. Das könnte jedenfalls Probleme machen. Am sichersten ist es, wenn man 'file:///foo/bar' oder 'memcached://host:port' nutzt. Dieser Wert darf jedenfalls nicht 'dummy:///' sein!

Jetzt erstellen wir die Datei gallery/uploadhandlers.py und fügen den Code von djangosnippets.org dort ein (bis zu dem Kommentar # A view to report back on upload progress:). Danach müssen wir dir settings.py erneut editieren und diese zwei Zeilen Code hinzufügen:

from django.conf import global_settings 
FILE_UPLOAD_HANDLERS = ('gallery.uploadhandlers.UploadProgressCachedHandler',) + global_settings.FILE_UPLOAD_HANDLERS

Außerdem müss man in der Datei gallery/uploadhandlers.py die erste Zeile durch die folgende ersetzen, da es im Django Trunk eine entsprechende Änderung gab:

1
from django.core.files.uploadhandler import FileUploadHandler

Als nächstes fügen wir den Code nach dem Kommentar in unsere views.py ein. Dann muss nur noch die URL in der urls.py gemappt werden:

(r'^upload_status/$', 'gallery.views.upload_progress'),

Damit sind wir im Python-Code fertig. Der Rest wird Javascript sein. Darum speichern wir zuerst ein Javascript von djangosnippets.org in media/upload.js. Dieses müssen wir jetzt etwas anpassen.

Also müssen wir Zeile 18 so abändern:

18
var progress_url = '/upload_status/'; // ajax view serving progress info

Als nächstes müssen wir nur noch jQuery und upload.js einbinden:

<script type="text/javascript" src="{{ MEDIA_URL }}jquery.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}upload.js"></script>

Jetzt sollte der die Statusanzeige beim Upload funktioniern. Wichtig ist jedoch, dass das das ganze nicht mit dem Development Server funktioniert, da dieser nur eine Anfrage gleichzeitig (in diesem Fall den Upload) verarbeiten kann, wird die AJAX-Anfrage einfach nicht verarbeitet, wodurch die Anzeige durchgehend bei 0% bleibt.

Wichtig ist, dass man zum Testen eine etwas größere Datei nimmt, damit auch Zeit zum Updaten ist. Auch werden die Grafiken einfach durch CSS skaliert, aber hier geht es nur um die Statusanzeige.

Download: Das gesamte Django Project steht auch zum Download bereit. In diesem sind auch noch ein paar kleinere, relativ unwichtige Anpassungen enthalten.

upload.tar.gz (tar.gz, 2,1MB, BSD-Lizenz)

Die Fotos stammen von verschiedenen Flickr-Benutzern:
Chris Seufert, lorenzaccio*, Victor Geere, von Lutz-R. Frank und coda

Informationen und Links

Mach' mit, indem du kommentierst, verfolgst was andere zu sagen haben oder von deinem Blog aus verlinkst.


Weitere Artikel
delicious 2.0 ist da!
Ubuntu 8.10 Alpha 3 erschienen

Einen Kommentar schreiben

Nimm dir einen Moment Zeit und sage uns, was du denkst. Du darfst ein bisschen HTML zur Formatierung verwenden.

Kommentare

Super danke, genau auf das habe ich gewartet :)

Hallo,

zwei Fragen - was passiert wenn Javascript im Browser nicht aktiviert ist? m.e. müsste es wie ein normaler Upload ohne Progress funktionieren?

Funktioniert das mit allen Browsern (nicht uralt)? Ich dachte dass beim Fileupload die Browser den Content-Length Header nicht immer richtig setzen und es daher nicht möglich ist die Dateigröße vorab zu bestimmen?

Danke,
Stefan

Wenn der Benutzer Javascript deaktiviert hat, funktioniert der Upload ganz normal.

Wenn der Browser jedoch nicht in der Lage ist, den Header richtig zu setzen, kann es schon passieren, dass das ganze falsche Werte anzeigt.

Hi, I’m learning now German, but i got a question, I took your example but i got an error, the progressbar always mark 0%, i got installed firebug-plugin from Firefox and I realized that the json object return to browser says NULL, I have tried with cache ‘file:///var/tmp/django-tmp’, but got nothing, could be my problem cache is not been shared between process??

If you try to upload something using the built-in runserver option, it won’t show the status as mentioned in the article.

But it might not work if you try to use this with Django 1.0 or a current trunk version because this article was written more than two months ago and they changed a lot in the core.

I’ll test my code on a current trunk version and edit the article if necessary.

OK, I’ll wait for your test, meanwhile I can say for sure that I am not working on a built-in server and I try to do the same thing with session and not with cache, I got the same result, I get many response from the server but all NULL, help please

After many test and fault, I think I found the problem, it seems that the method upload_complete erase the cache before it can be accesed by the method upload_progress in the view, so I commented it, but I replace the cache by session and it works perfectly, try it and tell me if you agree or desagree?

Actually UploadProgressCachedHandler.upload_complete should only be called by the Django core if the upload is completed so the session should not be destroyed while you’re still uploading the file.

Did you try uploading a larger file? Maybe the upload is completed before the Javascript function requests its status the first time.

Yes, I belived that, but that was the condition that I have to erase from my mind because because the cache is been deleted before it can be read. And since the first moment I was trying with a scanned file of 430 MBytes, but belive me!!!