.. _validation: 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: ``_ 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: .. code-block:: python 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: .. code-block:: python 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. .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python from .validators import datetime_in_future und weisen den Validator dem Modelfeld ``date`` zu: .. code-block:: python date = models.DateTimeField(validators=[datetime_in_future]) Dann führen wir makemigrations und migrate aus, da Validatoren auch migriert werden müssen. .. code-block:: bash 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. .. image:: /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: ``_ Hier nochmal eine vereinfachte Darstellung des Form-Validierungsprozesses, der genau in dieser Reihenfolge abgearbeitet wird: .. csv-table:: Die wichtigsten Kommandos: :widths: 40, 60 ``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 ``_