Validierung von Feldwerten

Formulardaten müssen validiert werden, denn jeder Userinput muss als schädlich betrachtet werden. Einen Großteil der technischen Validierung nimmt uns Django schon von Haus aus ab: wenn wir versuchen, ein Wort an ein Integerfeld zu übergeben, wird uns Django mit einem Validierungsfehler antworten. Wir könnten auch versuchen, ein Datum einzutragen, dass nicht als Datum geparst werden kann… das wird ebenfalls nicht gehen.

Oft müssen wir aber prüfen, ob die übergebenen Werte in Abhängigkeit unseren Anforderungen überhaupt plausibel sind. So mag ein Datum generell der richtige Datentyp sein, aber wenn es in der Vergangenheit liegt, ist es im Sinne des Event-Managers unsinnig.

Wir können, wir wir schon gesehen haben, die Eingabedaten in der Form-Klasse validieren. Das hat den Nachteil, dass die Validatoren, die dort definiert wurden, nicht in der Admin-Oberfläche oder in sonstigen Stellen berücksichtigt werden. Sie werden nur berücksichtigt, wenn Daten über ein Formular eingetragen werden.

Mehr zu Form-Validierung hier: https://docs.djangoproject.com/en/stable/forms/validation/

Deshalb ist es sinnvoll, Validatoren in den Models zu definieren.

Model Validatoren

Bei Model-Validatoren handelt es sich um Klassen oder Funktionen, die einzelnen Feldern des Models zugeteilt werden. Dazu nutzen wir das validators - Keyword Argument.

Eigene Validatoren entwickeln

Eigene Validatoren lassen sich einfach als Klasse oder Funktion entwickeln und den Model-Feldern zuordnen.

https://docs.djangoproject.com/en/stable/validators/#writing-validators

Validator-Funktion schreiben

In folgendem Beispiel erheben wir einen ValidationError, wenn eine Zahl nicht gerade ist:

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

def validate_even(value):
    if value % 2 != 0:
        raise ValidationError(
            _('%(value)s is not an even number'),
            params={'value': value},
        )

Wir nutzen hier die gettext_lazy - Funktion, um die Fehlermeldung später auch mehrsprachig darstellen zu können.

Validator in Model nutzen:

Den Valdiator könnte man hypothetisch in einem Integerfield dann so nutzen:

from django.db import models

class MyModel(models.Model):
    even_field = models.IntegerField(validators=[validate_even])

Built-In Validatoren nutzen

Django verfügt über eine große Anzahl an sogenannten built-in Validatoren, die importiert und genutzt werden können. Zum Beispiel einen EmailValidator oder einen MinValueValidator

Built in Validator nutzen

Im hypothetischen Beispiel nutzen wir MinValueValidator und MaxValueValidator, um die Körpergröße einzugrenzen.

from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator

class Person(models.Model):
    height = models.IntegerField(
        validators=[MinValueValidator(130), MaxValueValidator(220)]
    )

Eine eigene Validation-Funktion erstellen

Wir wollen nun eigene Model-Felder validieren. Beim Eintragen der Events ist aufgefallen, dass es problemlos möglich ist, Daten, die in der Vergangenheit liegen, einzutragen. Das sollte nicht möglich sein. Deshalb wollen wir die Eingabe des date-Feldes des Event-Models validieren.

Wir erstellen eine neue Datei event_manager/events/validators.py und erstellen dort die folgende Funktion:

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.utils import timezone

def datetime_in_future(value):
    """prüfe, ob Zeitpunkt value in der Vergangenheit liegt.
    wenn ja, raise ValidationError"""
    if value <= timezone.now():
        raise ValidationError(_("Der Zeitpunkt des Events darf nicht in der Vergangenheit liegen"))

Und importieren in den event_manager/events/models.py dann den Validator:

from .validators import datetime_in_future

und weisen den Validator dem Modelfeld date zu:

date = models.DateTimeField(validators=[datetime_in_future])

Dann führen wir makemigrations und migrate aus, da Validatoren auch migriert werden müssen.

python manage.py makemigrations events
python manage.py migrate events

Jetzt können wir unseren neuen Validator auch ausprobieren und versuchen ein Formular einzutragen, welches in der Vergangenheit liegt. Darauf achten, mit dem Superuser eingeloggt zu sein, damit der Eintrag auch klappt. Beim Absenden mit einem Datum in der Vergangenheit sollte das Feld rot hinterlegt sein, und einen Fehler anzeigen.

../_images/event_form_8.png

Der Form-Validierungsprozess

Formularfelder können also an mehreren Orten validiert werden. Django arbeitet die Validierung dabei in einem festen Prozess ab. Dieser Validierungsprozess, der für jedes Feld durchgeführt wird, wird zum Beispiel gestartet, wenn wir die Funktion is_valid() aufrufen.

Der Prozess wird in der Django-Doku beschrieben, ist allerdings nicht sonderlich zugänglich: https://docs.djangoproject.com/en/stable/ref/forms/validation/

Hier nochmal eine vereinfachte Darstellung des Form-Validierungsprozesses, der genau in dieser Reihenfolge abgearbeitet wird:

Die wichtigsten Kommandos:

to_python()

wandelt Feldwerte in Python-Datentypen um und erhebt im

Fall eines Fehlers einen ValidationError

validate()

führt feldspezifische Validierungen durch und wirft einen

ValidationError im Falle eines Fehlers

run_validators()

führt die den Feldern zugewiesenen Validatoren aus

und sammelt alle Fehler in einem ValidationError.

clean()

führt neben to_python() auch validate() und run_validators()

aus und erstellt das cleaned_data - Dictionary.

clean_FELDNAME()

ruft für jedes Feld mit FELDNAME und speichert den

Rückgabewert in cleaned_data.

clean()

um feldübergreifende Validierungen vorzunehmen.

Der Rückgabewert ist das finale cleaned_data- Dictionary

https://docs.djangoproject.com/en/stable/validators/#built-in-validators