ModelsForms mit Choices validiert nicht korrekt
22. Juli 2008 19:15:42 Uhr
Um es vorweg zu nehmen, dies ist die Beschreibung eines Bugs in Django. Ja, dafür gibt es schon ein Ticket und nein, es ist derzeit noch nicht im Trunk gefixt.
Oki, wer bis hierhin durchgehalten hat, weiter gehts:
Angenommen wir haben dieses simple Model, die Werte des Testfeldes bestehen aus den vorher definierten Choices.
from django.db import models CHOICES = ( (u'Bube', u'Bube'), (u'Dame', u'Dame'), (u'Koenig', u'Koenig'), (u'Ass', u'Ass'), ) # Das Model class TestModel(models.Model): testfeld = models.CharField(max_length=30, choices=CHOICES)
Um ein simples Formular daraus zu erstellen ist ModelForms ideal:
from testprojekt.models import TestModel from django import forms # Django 1.0 Alpha wohooo! class TestForm(forms.ModelForm): class Meta: model = TestModel
Einfacher gehts kaum, mit den paar Zeilen hat man schon ein komplettes Formular parat. Aber dass wußtest du sicher schon, also weiter. Basteln wir uns eine einfache Ausgabe:
>>> f = TestForm() >>> f.as_ul() # Beispielausgabe <select name="testfeld" id="id_testfeld"> <option value="Bube">Bube</option> <option value="Dame">Dame</option> <option value="Koenig">Koenig</option> <option value="Ass">Ass</option> </select>
Alles korrekt bis hier hin. Angenommen das Formular wird nun auf der Webseite angezeigt, der User wählt "Bube" und schickt das Formular ab:
>>> f = TestForm(data={'testfeld': 'Bube'}) >>> f.as_ul() <select name="testfeld" id="id_testfeld"> <option value="Bube" selected="selected">Bube</option> <option value="Dame">Dame</option> <option value="Koenig">Koenig</option> <option value="Ass">Ass</option> </select> >>> f.is_valid() True
Das Formular ist valide, weil "Bube" ja eine korrekte Auswahl für das Feld 'testfeld' ist. Aber zur Erinnerung: SELECT-Felder in HTML sind auch nur stinknormale Datenfelder und der Browser prüft nicht, ob der abgesendete Wert auch des Feldes auch vorher in den SELECT-Options definiert wurde. Ändern wir also mal die Werte der Formulardaten:
>>> f = TestForm(data={'testfeld': 'Boeses Maedchen'}) >>> f.as_ul() <select name="testfeld" id="id_testfeld"> <option value="Bube">Bube</option> <option value="Dame">Dame</option> <option value="Koenig">Koenig</option> <option value="Ass">Ass</option> </select> >>> f.is_valid() True
Wem das nicht schlimm erscheint: Diese Daten speichert das Formular leider auch in die Datenbank, das Model selbst validiert also nicht noch einmal nach.
>>> objekt = f.save() >>> objekt.testfeld u'Boeses Maedchen'
Ganz fatal in meinen Augen. Zur Erinnerung, eigentlich sollte das testfeld im Model nur die Daten Bube, Dame, Koenig und Ass annehmen.
Wie kann man eigentlich "fremde" Daten in ein Select-Feld schreiben? Nichts einfacher als das, die bekannte Firefox-Extension "Web Developer Toolbar" hat eine eigene Funktion dafür:
Mein derzeitiger Lösungsvorschlag sieht so aus, dass ich einfach mit einer clean-Methode auf das Vorhandensein der Eingabe in den Choices prüfe:
from testprojekt.models import TestModel, CHOICES from django import forms class TestForm(forms.ModelForm): def clean_testfeld(self): if not self.cleaned_data['testfeld'] in dict(CHOICES): raise forms.ValidationError(u'%s is not a valid choice' % (self.cleaned_data['testfeld'])) return self.cleaned_data['testfeld'] class Meta: model = TestModel
Dann klappts auch wie gewünscht.
>>> f = TestForm(data={'testfeld': 'Boeses Maedchen'}) >>> print f.is_valid() False >>> f.as_ul() errorlist: "Boeses Maedchen is not a valid choice" <select name="testfeld" id="id_testfeld"> <option value="Bube">Bube</option> <option value="Dame">Dame</option> <option value="Koenig">Koenig</option> <option value="Ass">Ass</option> </select
Durch das derzeitge Release von Django 1.0 Alpha gehe ich aber mal davon aus, dass dieser Bug in nächster Zeit gefixt wird. Ein Patch exisitiert ja schon.