Wie bei newforms die Validierung abläuft
26. Juni 2008 13:24:48 Uhr
Djangos newforms Formularen stehen 3 Methoden zur Verfügung, Eingabedaten zu prüfen.
- Feld.clean(): Die clean-Methode des Feldes selbst. Django bringt für die jeweiligen Felder schon grundsätzliche Prüfungen mit. In ein IntegerField dürfen eben nur Ziffern eingegeben werden und ein EmailField prüft die korrekte Syntax der E-Mail-Adresse.
In der Regel ist es nicht notwendig, bestehende Feld-Clean-Methoden zu ändern, denn dafür gibt es:
Formular.clean_<feldname>(): In dieser Funktion kannst du penibel festlegen, welche Daten für das Feld valide sind und einen Fehler ausgeben oder diese Daten ändern.
Formular.clean(): Diese Funktion wird am Ende ausgeführt, wenn alle Felder einzeln schon geprüft worden sind.
In allen Funktionen hat das Formular stets Zugriff auf die eingegeben Daten, denn diese stehen im Dict self.cleaned_data. Wofür ist also die "globale" clean() Methode gut?
Dazu ist es wichtig zu wissen, wie der Ablauf der Validierung von Newforms-Formularen stattfindet. Vergleiche einmal dieses Formular und dessen Ausgabe:
#!/usr/bin/env python # -*- coding: utf-8 -*- from django import newforms as forms class Form(forms.Form): name = forms.CharField() plz = forms.IntegerField() ort = forms.CharField() def clean_name(self): print "Vorhandene Daten bei 'name': %s" % self.cleaned_data return self.cleaned_data['name'] def clean_plz(self): print "Vorhandene Daten bei 'plz': %s" % self.cleaned_data return self.cleaned_data['plz'] def clean_ort(self): print "Vorhandene Daten bei 'ort': %s" % self.cleaned_data return self.cleaned_data['ort'] def clean(self): print "Die Formular clean() Methode. Spätestens jetzt sind alle Daten" \ "vorhanden: %s" % self.cleaned_data return self.cleaned_data test_data = {'name': 'Martin', 'plz': '18581', 'ort': 'Putbus'} f = Form(test_data) f.as_ul()
Ausgabe:
>python -u test.py
Vorhandene Daten bei 'name': {'name': u'Martin'}
Vorhandene Daten bei 'plz': {'name': u'Martin', 'plz': 18581}
Vorhandene Daten bei 'ort': {'ort': u'Putbus', 'name': u'Martin', 'plz': 18581}
Die Formular clean() Methode. Spätestens jetzt
sind alle Datenvorhanden: {'ort': u'Putbus', 'name': u'Martin', 'plz': 18581}
Zur Erläuterung. Die Validierung im Formular geht der Reihe nach, anhand der Felder von oben nach unten. In diesem Formular ist es also dieses Schema:
- 'name' den Wert aus den Daten zuweisen
- name.clean() - Die interne clean-Funktion eines CharFields
- f.clean_name() - Unsere selbst definierte clean-Funktion für 'name'
- 'plz' den Wert aus den Daten zuweisen
- plz.clean() - Die interne clean-Funktion eines IntegerFields
- f.clean_plz() - Unsere selbst definierte clean-Funktion für 'plz'
- 'ort' den Wert aus den Daten zuweisen
- ort.clean() - Die interne clean-Funktion eines CharFields
- f.clean_ort() - Unsere selbst definierte clean-Funktion für 'ort'
- f.clean() - Die "globale" Clean-Methode wird erst am Ende ausgeführt.
In der Ausgabe siehst du auch, das dir in der Funktion "clean_name" (unser erstes Feld) noch gar keine Werte für die Felder "plz" und "ort" zur Verfügung stehen. Nochmal: die Validierung und Zuweisung der Daten läuft von oben nach unten, in der Reihenfolge der Felder.
Warum kann man also schon im ersten Feld nicht auf alle Eingabedaten zugreifen? Hypothese: Wozu soll das Formular auch alle Datenfelder durchlaufen, wenn schon im ersten Feld 'name' ein Fehler (ValidationError) auftreten kann.
Aha, toll
Ok, kommen wir endlich zum springenden Punkt für diesen Artikel. In der Regel wird ein Formular nicht nur pro Feld validiert sondern auch in Kombination. Beispiele währen:
- 2 Passwort-Felder müssen das selbe Passwort haben
- Möchte ich per Lastschrift zahlen, muss ich auch Kontodaten hinterlegen
- Wünsche ich Antwort per E-Mail, muss ich auch eine E-Mail-Adresse hinterlegen, sonst reicht mein Name
Und so weiter, das ganze hängt ja vom Anwendungsfall ab und die Möglichkeiten so eines Formulares sind unbegrenzt. Nun endlich der springende Punkt, und ich würde das ganze hier nicht schreiben, wenn es nicht ein häufiges (Anfänger)Problem währe:
Willst du mehrere Felder miteinander vergleichen gehört diese Logik in die clean()-Funktion des Formulares, nicht in die des Feldes! Der Grund dafür steht ja schon oben.
Nun aber das Aber. Der Grund warum so viele Leute die Validierungslogik in eine clean_<feldname> Funktion packen – obwohl es mehrere Felder betrifft – ist, dass Fehlermeldungen aus der clean() Funktion immer ganz oben, vor allen Feldern angezeigt werden. Die so genannten non_field_errors, also Fehlermeldungen die nicht an ein Feld gebunden sind.
Hier ein Beispiel dass die Felder 'ort' und 'name' miteinander prüft:
#!/usr/bin/env python # -*- coding: utf-8 -*- from django import newforms as forms class Form(forms.Form): name = forms.CharField() plz = forms.IntegerField() ort = forms.CharField() def clean(self): if self.cleaned_data['name'] == 'Meier' and \ self.cleaned_data['ort'] == 'Berlin': raise forms.ValidationError('Es gibt keinen "Meier" in Berlin.' \ 'Bitte legen Sie sich einen anderen Namen zu.') test_data = {'name': 'Meier', 'plz': u'10627', 'ort': 'Berlin'} f = Form(test_data) print f.as_ul()
In der Ausgabe des Formulares steht der Fehler ganz oben:
<!-- non_field_erros Fehlermeldung (normalerweise ganz oben) --> <li><ul class="errorlist"><li>Es gibt keinen "Meier" in Berlin.Bitte legen Sie sich einen anderen Namen zu.</li></ul></li> <!-- Weiter gehts mit den Feldern, die aber keine eigenen Fehlermeldungen besitzen --> <li><label for="id_name">Name:</label> <input type="text" name="name" value="Meier" id="id_name" /></li> <li><label for="id_plz">Plz:</label> <input type="text" name="plz" value="10627" id="id_plz" /></li> <li><label for="id_ort">Ort:</label> <input type="text" name="ort" value="Berlin" id="id_ort" /></li>
Möchte man aber im Nachhinein noch einem Feld einen Fehlertext zuweisen, so geht das einfach über das Dict self.errors[<feldname>] (es erwartet für das Feld ein Liste oder Tupel, über die es iterieren kann):
#!/usr/bin/env python class Form(forms.Form): # ... def clean(self): if self.cleaned_data['name'] == 'Meier' and \ self.cleaned_data['ort'] == 'Berlin': self.errors['name'] = ('Es gibt keinen "Meier" in Berlin.',) raise forms.ValidationError('Es sind Fehler aufgetreten:')
In der Ausgabe hat unser Feld nun seine eigene Fehlermeldung:
<!-- non_field_erros Fehlermeldung (normalerweise ganz oben) --> <li><ul class="errorlist"><li>Es sind Fehler aufgetreten:</li></ul></li> <!-- Unser Namensfeld mit einer eigenen Fehlermeldung --> <li> <ul class="errorlist"><li>Es gibt keinen "Meier" in Berlin.</li></ul> <label for="id_name">Name:</label> <input type="text" name="name" value="Meier" id="id_name" /> </li> <li><label for="id_plz">Plz:</label> <input type="text" name="plz" value="10627" id="id_plz" /></li> <li><label for="id_ort">Ort:</label> <input type="text" name="ort" value="Berlin" id="id_ort" /></li>
So weit... viel Spass weiterhin mit newforms.