# Documentation Django 6.0.7

# La validation de formulaires et de champs

La validation de formulaire intervient lorsque les données sont nettoyées. Si vous souhaitez personnaliser ce processus, il y a plusieurs points d’entrée où des changements peuvent se faire, chacun visant un objectif différent. Trois types de méthodes de nettoyage sont exécutés durant le traitement d’un formulaire. Elles sont normalement exécutées lorsque la méthode `is_valid()` d’un formulaire est appelée. Il y a d’autres opérations qui peuvent aussi provoquer le nettoyage et la validation (l’accès à l’attribut `errors` ou l’appel direct à `full_clean()`), mais en principe, cela ne fait pas partie du processus normal.

En général, toute méthode de nettoyage peut générer l’exception `ValidationError` si un problème survient lors du traitement des valeurs, en passant des informations adéquates au constructeur de `ValidationError`. Voir ci-dessous pour les bonnes pratiques de génération de `ValidationError`. Si aucune exception `ValidationError` n’est produite, la méthode doit renvoyer la valeur nettoyée (normalisée) sous forme d’objet Python.

La plupart de la validation peut se faire à l’aide de validateurs, des fonctions utilitaires pouvant être réutilisées. Il s’agit de fonctions (ou objets exécutables) acceptant un seul paramètre et générant une exception `ValidationError` en cas de saisie non valide. Les validateurs sont exécutés après l’appel aux méthodes `to_python` et `validate` du champ.

La validation d’un formulaire est partagée en plusieurs étapes, qui peuvent être personnalisées ou surchargées :

- La méthode `to_python()` d’un champ `Field` est la première étape de toute validation. Elle transforme la valeur dans le bon type de données et génère `ValidationError` si ce n’est pas possible. Cette méthode accepte la valeur brute provenant du composant et renvoie la valeur convertie. Par exemple, un champ `FloatField` transforme la donnée en un objet Python `float` ou génère une exception `ValidationError`.

- La méthode `validate()` d’un champ `Field` se charge de la validation spécifique du champ qui ne convient pas pour un validateur. Elle accepte une valeur ayant déjà été convertie dans le bon type et génère une exception `ValidationError` en cas d’erreur. Cette méthode ne renvoie rien et ne doit pas modifier la valeur. Elle peut être surchargée pour gérer de la logique de validation que vous ne pouvez ou ne voulez pas placer dans un validateur.

- La méthode `run_validators()` d’un champ lance tous les validateurs du champ et rassemble toutes les erreurs dans une seule exception `ValidationError`. Il n’est en principe pas utile de surcharger cette méthode.

- La méthode `clean()` d’une sous-classe de champ est responsable de l’exécution de `to_python()`, `validate()` et `run_validators()` dans le bon ordre et de la propagation des erreurs. Si à tout moment l’une de ces méthodes génère une exception `ValidationError`, la validation s’arrête et cette erreur est propagée. Cette méthode renvoie les données nettoyées qui sont ensuite insérées dans le dictionnaire `cleaned_data` du formulaire.

- La méthode `clean_<nom_du_champ>()` est appelée pour une sous-classe de formulaire – où `<nom_du_champ>` est remplacé par le nom de l’attribut de champ de formulaire. Cette méthode s’occupe de tout nettoyage spécifique à cet attribut, sans considération du type de champ concerné. Cette méthode ne reçoit aucun paramètre. Vous devez chercher vous-même la valeur du champ dans `self.cleaned_data` et vous rappeler qu’il s’agira à ce moment d’un objet Python, pas de la chaîne initialement soumise avec le formulaire (la valeur figure dans `cleaned_data` parce que la méthode générale `clean()` du champ aura déjà nettoyé la valeur une première fois).

  Par exemple, si vous souhaitiez valider que le contenu d’un champ `CharField` nommé `serialnumber` est unique, `clean_serialnumber()` serait le bon endroit pour le faire. Vous n’avez pas besoin d’un champ spécifique (c’est un `CharField`), mais vous avez besoin d’une séquence de validation propre au champ de formulaire et, si possible, de nettoyer/normaliser les données.

  La valeur renvoyée par cette méthode remplace la valeur existante dans `cleaned_data`, il doit donc s’agir soit de la valeur de `cleaned_data` (même si cette méthode ne l’a pas modifiée), soit d’une nouvelle valeur propre.

- La méthode `clean()` de la sous-classe de formulaire peut effectuer toute validation nécessitant d’accéder à plusieurs champs de formulaire. C’est ici que vous pouvez placer des contrôles du genre : si le champ `A` est renseigné , le champ `B` doit contenir une adresse de messagerie valide. Cette méthode peut renvoyer un dictionnaire complètement différent si elle le souhaite, et ce résultat sera utilisé comme contenu de `cleaned_data`.

  Comme les méthodes de validation des champs ont été exécutées au moment où `clean()` est appelée, vous avez aussi accès à l’attribut `errors` du formulaire qui contient toutes les erreurs générées lors du nettoyage individuel des champs.

  Note that any errors raised by your `Form.clean()` override will not be associated with any field in particular. They go into a special « field » (called `__all__`), which you can access via the `non_field_errors()` method if you need to. If you want to attach errors to a specific field in the form, you need to call `add_error()`.

  Notez également qu’il faut tenir compte de considérations particulières lors de la surcharge de la méthode `clean()` d’une sous-classe de `ModelForm` (voir la documentation de ModelForm pour plus d’informations).

Ces méthodes sont exécutées dans l’ordre metnionné ci-dessus, un champ après l’autre. C’est-à-dire que pour chaque champ du formulaire (dans l’ordre où ils ont été déclarés dans la définition du formulaire), la méthode `Field.clean()` (ou sa version surchargée) est exécutée, puis `clean_<fieldname>()`. Finalement, après que ces deux méthodes ont été exécutées pour chaque champ, la méthode `Form.clean()` ou sa version surchargée est exécutée dans tous les cas, même si les méthodes précédentes ont généré des erreurs.

Des exemples pour chacune de ces méthodes sont présentés ci-dessous.

Comme mentionné, chacune de ces méthodes peut générer une exception `ValidationError`. Pour chaque champ, si la méthode `Field.clean()` gènère une erreur `ValidationError`, la méthode de nettoyage spécifique au champ n’est pas appelée. Cependant, les méthodes de nettoyage de tous les autres champs sont tout de même exécutées.

## Génération de `ValidationError`

Pour une plus grande flexibilité des messages d’erreur et pour qu’il soit plus facile de les surcharger, voici quelques lignes de conduite :

- Provide a descriptive error `code` to the constructor, to allow programmatic use (e.g. in tests) that will not vary with translations:

      # Good
      ValidationError(_("Invalid value"), code="invalid")

      # Bad
      ValidationError(_("Invalid value"))

- Ne fusionnez pas les variables dans les messages ; utilisez des substituants ainsi que le paramètre `params` du constructeur :

      # Good
      ValidationError(
          _("Invalid value: %(value)s"),
          params={"value": "42"},
      )

      # Bad
      ValidationError(_("Invalid value: %s") % value)

- Préférez la substitution par clé de dictionnaire plutôt que le formatage positionnel. Cela permet de placer les variables dans n’importe quel ordre ou même de les omettre entièrement lors de la réécriture d’un message :

      # Good
      ValidationError(
          _("Invalid value: %(value)s"),
          params={"value": "42"},
      )

      # Bad
      ValidationError(
          _("Invalid value: %s"),
          params=("42",),
      )

- Englobez le message dans un appel `gettext` pour activer sa traduction :

      # Good
      ValidationError(_("Invalid value"))

      # Bad
      ValidationError("Invalid value")

Pour résumer le tout :

    raise ValidationError(
        _("Invalid value: %(value)s"),
        code="invalid",
        params={"value": "42"},
    )

Le respect de ces lignes de conduite est particulièrement utile si vous écrivez des formulaires, des champs de formulaire ou des champs de modèle réutilisables.

Même si ce n’est pas recommandé, si vous vous trouvez à la fin de la chaîne de validation (par ex. la méthode `clean()` de votre formulaire) et que vous savez que vous n’aurez jamais besoin de surcharger le message d’erreur, vous pouvez toujours opter pour la version plus directe :

    ValidationError(_("Invalid value: %s") % value)

Les méthodes `Form.errors.as_data()` et `Form.errors.as_json()` bénéficient grandement d’objets `ValidationError` complètement renseignés (avec un nom `code` et un dictionnaire `params`).

### Génération de plusieurs erreurs

Si vous détectez plusieurs erreurs durant une méthode de nettoyage et que vous souhaitiez toutes les signaler à celui qui a soumis le formulaire, il est possible de transmettre une liste d’erreurs au constructeur de `ValidationError`.

Comme mentionné plus haut, il est recommandé de passer une liste d’instances `ValidationError` avec les paramètres `code` et `params`, mais une liste de chaînes fera aussi l’affaire :

    # Good
    raise ValidationError(
        [
            ValidationError(_("Error 1"), code="error1"),
            ValidationError(_("Error 2"), code="error2"),
        ]
    )

    # Bad
    raise ValidationError(
        [
            _("Error 1"),
            _("Error 2"),
        ]
    )

## Utilisation de la validation en pratique

Les sections précédentes ont expliqué comment fonctionne la validation de manière générale pour les formulaires. Comme il est parfois plus simple de remettre les choses à leur place en examinant du code en contexte, voici une série de petits exemples qui font usage de chacune des fonctionnalités présentées précédemment.

### Utilisation des validateurs

Les champs de formulaire (et de modèle) de Django gèrent des fonctions et classes utilitaires connus sous le nom de validateurs. Un validateur est un objet exécutable acceptant une valeur et ne renvoyant rien du tout si la valeur est valide ou générant une exception `ValidationError` si elle ne l’est pas. Ces validateurs peuvent être transmis à un constructeur de champ par l’intermédiaire du paramètre `validators` du champ ou définis dans une classe `Field` par leur attribut `default_validators`.

Des validateurs peuvent être employés pour valider des valeurs d’un champ ; examinons par exemple le champ `SlugField` de Django :

    from django.core import validators
    from django.forms import CharField

    class SlugField(CharField):
        default_validators = [validators.validate_slug]

Comme vous pouvez le voir, `SlugField` est un `CharField` doté d’un validateur particulier validant que le texte soumis obéit à certaines règles textuelles. Cela peut aussi se faire lors de la définition du champ, ainsi :

    slug = forms.SlugField()

est équivalent à :

    slug = forms.CharField(validators=[validators.validate_slug])

Des cas courants comme la validation d’une adresse électronique ou d’une expression régulière peuvent être traités avec des classes de validation existantes de Django. Par exemple, `validators.validate_slug` est une instance de `RegexValidator` construite avec son premier paramètre équivalent au motif `^[-a-zA-Z0-9_]+\Z`. Consultez la section sur l’écriture de validateurs pour voir une liste de ce qui est déjà disponible et pour des exemples de la façon d’écrire un validateur.

### Nettoyage par défaut des champs de formulaire

Créons tout d’abord un champ de formulaire personnalisé qui valide que sa valeur d’entrée est une chaîne contenant des adresses électroniques séparées par des virgules. La classe complète ressemble à ceci :

    from django import forms
    from django.core.validators import validate_email

    class MultiEmailField(forms.Field):
        def to_python(self, value):
            """Normalize data to a list of strings."""
            # Return an empty list if no input was given.
            if not value:
                return []
            return value.split(",")

        def validate(self, value):
            """Check if value consists only of valid emails."""
            # Use the parent's handling of required fields, etc.
            super().validate(value)
            for email in value:
                validate_email(email)

Pour chaque formulaire utilisant ce champ, ces méthodes seront exécutées avant que quoi que ce soit puisse être fait avec les données du champ. Il s’agit de nettoyage spécifique à ce type de champ, quelle que soit la manière dont il sera utilisé par la suite.

Créons un formulaire `ContactForm` pour montrer comment ce champ peut être utilisé :

    class ContactForm(forms.Form):
        subject = forms.CharField(max_length=100)
        message = forms.CharField()
        contact_email = forms.EmailField()
        recipients = MultiEmailField()
        urgent = forms.BooleanField(required=False)

Utilisez `MultiEmailField` comme n’importe quel autre champ. Lorsque la méthode `is_valid()` est appelée pour le formulaire, la méthode `MultiEmailField.clean()` sera aussi exécutée dans le contexte du processus de nettoyage, et celle-ci appellera à son tour les méthodes personnalisées `to_python()` et `validate()`.

### Nettoyage d’un attribut de champ spécifique

Continuing on from the previous example, suppose that in our `ContactForm`, we want to make sure that the `recipients` field only contains addresses that are `@example.com`. This is validation that is specific to our form, so we don’t want to put it into the general `MultiEmailField` class. Instead, we write a cleaning method that operates on the `recipients` field, like so:

    from django import forms
    from django.core.exceptions import ValidationError

    class ContactForm(forms.Form):
        # Everything as before.
        ...

        def clean_recipients(self):
            data = self.cleaned_data["recipients"]
            if not all(email.endswith("@example.com") for email in data):
                raise ValidationError("All recipients must be @example.com.")

            # Always return a value to use as the new cleaned data, even if
            # this method didn't change it.
            return data

### Nettoyage et validation de champs qui dépendent l’un de l’autre

Suppose we add another requirement to our contact form: if the `urgent` field is `True`, the `subject` must contain `"help"`. We are performing validation on more than one field at a time, so the form’s `clean()` method is a good spot to do this. Notice that we are talking about the `clean()` method on the form here, whereas earlier we were writing a `clean()` method on a field. It’s important to keep the field and form difference clear when working out where to validate things. Fields are single data points, forms are a collection of fields.

Au moment où la méthode `clean()` du formulaire est appelée, toutes les méthodes de nettoyage de chaque champ auront déjà été exécutées (cf. les deux sections précédentes), ce qui fait que `self.cleaned_data` sera rempli par toute donnée ayant respecté la validation jusque là. Il faut donc aussi se rappeler de tenir compte du fait que les champs que vous souhaitez valider pourraient ne pas avoir passé l’étape préalable de la vérification au niveau du champ individuel.

Il y a deux manières de signaler des erreurs à ce stade. La méthode probablement la plus courante est d’afficher l’erreur au sommet du formulaire. Pour créer une telle erreur, vous pouvez générer une exception `ValidationError` à partir de la méthode `clean()`. Par exemple :

    from django import forms
    from django.core.exceptions import ValidationError

    class ContactForm(forms.Form):
        # Everything as before.
        ...

        def clean(self):
            cleaned_data = super().clean()
            urgent = cleaned_data.get("urgent")
            subject = cleaned_data.get("subject")

            if urgent and subject:
                # Only do something if both fields are valid so far.
                if "help" not in subject:
                    raise ValidationError(
                        "Must put 'help' in the subject for urgent requests."
                    )

Dans ce code, si l’erreur de validation est générée, le formulaire affichera un message d’erreur au sommet du formulaire (dans le cas normal) décrivant le problème. De telles erreurs sont des erreurs non liées à des champs qui sont affichées dans les gabarits par `{{ form.non_field_errors }}`.

L’appel à `super().clean()` dans l’exemple de code garantit que toute logique de validation dans les classes parentes est conservée. Si votre formulaire hérite d’un autre qui ne renvoie pas de dictionnaire `cleaned_data` dans sa méthode `clean()` (c’est facultatif), n’attribuez pas le résultat de l’appel à `super()` à `cleaned_data` et utilisez plutôt `self.cleaned_data`:

    def clean(self):
        super().clean()
        urgent = self.cleaned_data.get("urgent")
        ...

The second approach for reporting validation errors might involve assigning the error message to one of the fields. In this case, let’s assign an error message to both the « subject » and « urgent » rows in the form display. Be careful when doing this in practice, since it can lead to confusing form output. We’re showing what is possible here and leaving it up to you and your designers to work out what works effectively in your particular situation. Our new code (replacing the previous sample) looks like this:

    from django import forms

    class ContactForm(forms.Form):
        # Everything as before.
        ...

        def clean(self):
            cleaned_data = super().clean()
            urgent = cleaned_data.get("urgent")
            subject = cleaned_data.get("subject")

            if urgent and subject and "help" not in subject:
                self.add_error(
                    "subject", "Must put 'help' in the subject for urgent requests."
                )
                self.add_error(
                    "urgent", "Must not mark urgent unless 'help' is in the subject."
                )

Le second paramètre de `add_error()` peut être une chaîne, ou de préférence une instance de `ValidationError`. Consultez Génération de ValidationError pour plus de détails. Notez que `add_error()` enlève automatiquement le champ de `cleaned_data`.