Eine andere Möglichkeit GET und POST in Django zu verarbeiten
Letztens bin ich über einen Blog-Eintrag von Dan Fairs gestolpert, in dem er eine Lösung anbietet, welche das Problem sauber lösen soll, dass man sehr oft eine if-else Konstruktion in den Views braucht um GET und POST Anfragen unterschiedlich zu behandeln.
Er ersetzt dazu die bisherigen View Funktionen gegen eine Klasse, in welcher verschiedene Methoden mit @get, @post etc. dekoriert werden um die entsprechende Anfrage zu verarbeiten. Spontan dachte ich dabei an web.py, ein sehr minimalistisches Python Web-Framework, welches eigentlich genauso arbeitet. URLs werden auf Klassen gemappt und dort gibt es pro HTTP-Methode eine Methode die diese Anfrage verarbeitet. Statt jedoch (umständlich) mit Dekoratoren zu arbeiten, gibt es einfach die Konvention, dass die Methode, welche z.B. HTTP-GET-Anfragen verarbeitet GET() heißt.
Kombiniert man die beiden Ansätze gelange ich zu folgender Lösung:
Zuerst definiere ich eine Basis-Klasse für alle späteren View-Klassen, in diesem Fall die Klasse BaseView:
from django.http import Http404 class BaseView(object): ALLOWED_HTTP_METHODS = ('get', 'post') def __call__(self, request, *args, **kwargs): if request.method.lower() not in self.ALLOWED_HTTP_METHODS: raise Http404 #should be 405 ... if not hasattr(self, request.method.lower()): raise Http404 return getattr(self, request.method.lower())(request, *args, **kwargs)
In diesem Beispiel verarbeite ich nur GET und POST Anfragen, weitere
HTTP-Methoden (z.B. HEAD) hinzuzufügen ist trivial, man trägt einfach den
Namen in Kleinbuchstaben in das Tupel ALLOWED_HTTP_METHODS
ein.
Der nächste Schritt ist jetzt eine View-Klasse zu schreiben, welche die
tatsächlichen Anfragen verarbeitet. Diese Klasse erbt von der BaseView Klasse
und ich nenne sie beispielhaft FooView
:
from somewhere import BaseView from django.http import HttpResponse, HttpResponseRedirect class FooView(BaseView): def get(self, request, id): #do something return HttpResponse('get %s' % id) def post(self, request, id): #do post processing return HttpResponseRedirect('/')
Die Klasse FooView
definiert zwei Methoden: get()
und post()
, d.h.
sowohl HTTP-GET als auch HTTP-POST Anfragen werden angenommen. Würde es eine
der Methoden nicht geben, würde der Client einen 404 Fehler bekommen
(Implementiert in der BaseView Klasse). Eigentlich sollte man hier zwar
einen HTTP 405 Method Not Allowed Status zurückgeben, aber der Einfachheit
halber habe ich die Http404 Exception von Django benutzt. Einen 405er zu
Implementieren bleibt Aufgabe für den Leser.
Nachdem nun die View-Klasse fertig ist folgt noch die Konfiguration in der urls.py um die Anfragen auch an diese Klasse zu leiten:
from django.conf.urls.defaults import * from somewhere import FooView urlpatterns = patterns('', (r'^test/(?P[\d]+)/', FooView()), )
Der Code sollte eigentlich selbsterklärend sein. Alle Anfragen an "test/id/",
wobei id eine Zahl ist, werden an eine Instanz der FooView-Klasse gesendet.
Da die Klasse eine __call__
-Methode definiert ist sie callable und kann
wie eine Funktion aufgerufen werden. Diese Funktionalität ist in der
BaseView-Klasse implementiert. Der id-Parameter im URL-Muster ist ein
Beispiel, damit man sieht, dass diese Paramter wie bisher an der Funktion
ankommen, die die Anfrage verarbeitet.
So sieht also meine Lösung für das Problem aus dem Blog-Eintrag von Dan aus. Ich finde diese Lösung etwas schöner, weil sie mit deutlich weniger Code auskommt, aber vielleicht habe ich auch etwas übersehen. Einen kleinen Bonus gibt es aber noch. Man strebt ja immer das Don't-Repeat-Yourself (DRY) Prinzip an und auch wenn ich keinen konreten Einsatzzweck habe, so gibt es doch eine Möglichkeit, wie diese Lösung mit View-Klassen DRY unterstützen könnte.
Man könnte die View-Klasse mit einem Konstruktor versehen, welcher die Klasse nach unterschiedlichen Vorgaben initialisiert, so dass man eine View-Klasse für mehrere ähnliche Views benutzen könnte. Ein Beispiel (wenn auch ein sinnloses) verdeutlich wohl am besten was ich meine. Zuerst bekommt die FooView-Klasse einen Konstruktor:
class FooView(BaseView): def __init__(self, foobar): self.foobar = foobar ...
Jetzt kann man in der urls.py mehrere Instanzen der View-Klasse verwenden, jeweils mit unterschiedlichen Werten initialisiert:
from django.conf.urls.defaults import * from somewhere import FooView urlpatterns = patterns('', (r'^test/(?P[\d]+)/', FooView(foobar='test')), (r'^example/(?P[\d]+)/', FooView(foobar='example')), )
Und um das Beispiel zu vervollständigen, hier eine leicht geänderte Fassung
der get()
-Methode:
def get(self, request, id): #do something return HttpResponse('get %s - %s' % (id, self.foobar))
Diese get()
-Methode benutzt nun den Wert, mit welchem die Klasse in der `
urls.py initialisiert wurde. Ich gebe zu das Beispiel ist etwas zu trivial,
aber wenn man die View-Klasse relativ generisch hällt und die Konfiguration
in der urls.py macht, kann man sich sehr leicht eine Reihe von Klasse bauen,
die ähnlich wie generic-views, für oft wiederkehrende Aufgaben geeignet sind
und außerdem eine schöne Trennung zwischen den unterschiedlichen HTTP-Methoden ermöglichen.
Hallo
Na ich weiß nicht, das hört sich bei dir so einfach an, aber ob ich das so hinbekomme steht in den Sternen-
Geschrieben von Francois 2 Tage, 1 Stunde nach Veröffentlichung des Blog-Eintrags am 13. März 2008, 14:35. Antworten