# Documentation Django 6.0.7

# Gestion de formulaires avec les vues fondées sur les classes

Le traitement de formulaires utilise généralement trois parcours :

- Affichage initial GET (vierge ou contenu pré-rempli)

- Envoi POST avec données non valides (réaffiche normalement le formulaire avec indication des erreurs)

- Envoi POST avec données valides (traitement des données normalement suivi par une redirection)

Lorsqu’on implémente soi-même ces étapes, il en résulte souvent beaucoup de code répétitif (voir Utilisation d’un formulaire dans une vue). Pour l’éviter, Django fournit un ensemble de vues génériques fondées sur les classes dédiées au traitement des vues.

## Formulaires élémentaires

Étant donné un formulaire de contact :

`forms.py`

    from django import forms

    class ContactForm(forms.Form):
        name = forms.CharField()
        message = forms.CharField(widget=forms.Textarea)

        def send_email(self):
            # send email using the self.cleaned_data dictionary
            pass

La vue peut être construite en utilisant une classe `FormView`:

`views.py`

    from myapp.forms import ContactForm
    from django.views.generic.edit import FormView

    class ContactFormView(FormView):
        template_name = "contact.html"
        form_class = ContactForm
        success_url = "/thanks/"

        def form_valid(self, form):
            # This method is called when valid form data has been POSTed.
            # It should return an HttpResponse.
            form.send_email()
            return super().form_valid(form)

Notes :

- `FormView` hérite de `TemplateResponseMixin`, ce qui fait qu’on peut utiliser `template_name` à cet endroit.

- L’implémentation par défaut de `form_valid()` redirige simplement vers `success_url`.

## Formulaires de modèles

Les vues génériques brillent particulièrement lorsqu’elles interagissent avec des modèles. Ces vues génériques vont automatiquement créer un formulaire `ModelForm`, pour autant qu’elles puissent déterminer la classe de modèle à utiliser :

- Si l’attribut `model` est présent, c’est cette classe de modèle qui sera utilisée.

- Si `get_object()` renvoie un objet, c’est la classe de cet objet qui sera utilisée.

- Si un attribut `queryset` est présent, c’est le modèle correspondant à ce jeu de requête qui sera utilisé.

Les vues de formulaire de modèle contiennent une implémentation de `form_valid()` qui enregistre automatiquement le modèle. Vous pouvez surcharger cela si vous avez des exigences particulières ; voir ci-dessous pour des exemples.

Vous n’avez même pas besoin d’indiquer un attribut `success_url` pour `CreateView` ou `UpdateView`, elles vont utiliser `get_absolute_url()` sur l’objet de modèle le cas échéant.

Si vous souhaitez utiliser un formulaire `ModelForm` personnalisé (par exemple pour ajouter de la validation en plus), définissez `form_class` dans la vue.

Note

Lorsqu’une classe de formulaire personnalisée est présente, il est toujours nécessaire de définir le modèle, même si la classe `form_class` est une classe `ModelForm`.

Nous devons d’abord ajouter une méthode `get_absolute_url()` à notre classe `Author`:

`models.py`

    from django.db import models
    from django.urls import reverse

    class Author(models.Model):
        name = models.CharField(max_length=200)

        def get_absolute_url(self):
            return reverse("author-detail", kwargs={"pk": self.pk})

Puis nous pouvons utiliser `CreateView` et compagnie pour faire le travail. Remarquez que nous avons ici juste à configurer les vues génériques fondées sur les classes ; nous n’avons pas à écrire nous-même de logique de vue :

`views.py`

    from django.urls import reverse_lazy
    from django.views.generic.edit import CreateView, DeleteView, UpdateView
    from myapp.models import Author

    class AuthorCreateView(CreateView):
        model = Author
        fields = ["name"]

    class AuthorUpdateView(UpdateView):
        model = Author
        fields = ["name"]

    class AuthorDeleteView(DeleteView):
        model = Author
        success_url = reverse_lazy("author-list")

Note

Nous devons utiliser `reverse_lazy()` au lieu de `reverse()` car la configuration d’URL n’est pas encore chargée au moment où le fichier est importé.

L’attribut `fields` fonctionne de la même manière que l’attribut `fields` de la classe interne `Meta` de `ModelForm`. Sauf dans les cas où la classe de formulaire est définie d’une autre manière, cet attribut est obligatoire et la vue génère une exception `ImproperlyConfigured` s’il est absent.

Si vous définissez à la fois les attributs `fields` et `form_class`, une exception `ImproperlyConfigured` est générée.

Finalement, nous connectons ces nouvelles vues dans la configuration d’URL :

`urls.py`

    from django.urls import path
    from myapp.views import AuthorCreateView, AuthorDeleteView, AuthorUpdateView

    urlpatterns = [
        # ...
        path("author/add/", AuthorCreateView.as_view(), name="author-add"),
        path("author/<int:pk>/", AuthorUpdateView.as_view(), name="author-update"),
        path("author/<int:pk>/delete/", AuthorDeleteView.as_view(), name="author-delete"),
    ]

Note

Ces vues héritent de `SingleObjectTemplateResponseMixin` qui utilise `template_name_suffix` pour construire `template_name` en fonction du modèle.

Dans cet exemple :

- `CreateView` et `UpdateView` utilisent `myapp/author_form.html`

- `DeleteView` utilise `myapp/author_confirm_delete.html`

Si vous aimeriez avoir des gabarits séparés pour `CreateView` et `UpdateView`, vous pouvez définir `template_name` ou `template_name_suffix` dans votre classe de vue.

## Modèles et `request.user`

Pour garder trace de l’utilisateur ayant créé un objet en utilisant `CreateView`, vous pouvez utiliser un formulaire `ModelForm` personnalisé. Premièrement, ajoutez la relation de clé étrangère dans le modèle :

`models.py`

    from django.contrib.auth.models import User
    from django.db import models

    class Author(models.Model):
        name = models.CharField(max_length=200)
        created_by = models.ForeignKey(User, on_delete=models.CASCADE)

        # ...

Dans la vue, prenez soin de ne pas inclure `created_by` dans la liste des champs modifiables et surchargez `form_valid()` afin d’ajouter l’utilisateur :

`views.py`

    from django.contrib.auth.mixins import LoginRequiredMixin
    from django.views.generic.edit import CreateView
    from myapp.models import Author

    class AuthorCreateView(LoginRequiredMixin, CreateView):
        model = Author
        fields = ["name"]

        def form_valid(self, form):
            form.instance.created_by = self.request.user
            return super().form_valid(form)

`LoginRequiredMixin` empêche les utilisateurs qui ne sont pas authentifiés d’accéder au formulaire. Si vous omettez ça, il sera de votre responsabilité de gérer les utilisateurs non autorisés dans `form_valid()`.

## Exemple de négociation de contenu

Voici un exemple montrant une possible implémentation d’un formulaire fonctionnant aussi bien pour un flux basé sur une API que pour des envois de formulaire POST « normaux » :

    from django.http import JsonResponse
    from django.views.generic.edit import CreateView
    from myapp.models import Author

    class JsonableResponseMixin:
        """
        Mixin to add JSON support to a form.
        Must be used with an object-based FormView (e.g. CreateView)
        """

        def form_invalid(self, form):
            response = super().form_invalid(form)
            if self.request.accepts("text/html"):
                return response
            else:
                return JsonResponse(form.errors, status=400)

        def form_valid(self, form):
            # We make sure to call the parent's form_valid() method because
            # it might do some processing (in the case of CreateView, it will
            # call form.save() for example).
            response = super().form_valid(form)
            if self.request.accepts("text/html"):
                return response
            else:
                data = {
                    "pk": self.object.pk,
                }
                return JsonResponse(data)

    class AuthorCreateView(JsonableResponseMixin, CreateView):
        model = Author
        fields = ["name"]

L’exemple ci-dessus part du principe que si le client accepte `text/html`, il s’agit de sa préférence. Ceci n’est cependant pas toujours vrai. Lors d’une requête pour un fichier `.css`, bien des navigateurs enverront l’en-tête `Accept: text/css,*/*;q=0.1`, indiquant qu’ils préfèrent CSS, mais que toute autre valeur conviendra aussi. Cela signifie que `request.accepts("text/html")` vaudra `True`.

Pour déterminer le format correct, en prenant en compte la préférence du client, utilisez `django.http.HttpRequest.get_preferred_type()`:

    class JsonableResponseMixin:
        """
        Mixin to add JSON support to a form.
        Must be used with an object-based FormView (e.g. CreateView).
        """

        accepted_media_types = ["text/html", "application/json"]

        def dispatch(self, request, *args, **kwargs):
            if request.get_preferred_type(self.accepted_media_types) is None:
                # No format in common.
                return HttpResponse(
                    status_code=406, headers={"Accept": ",".join(self.accepted_media_types)}
                )

            return super().dispatch(request, *args, **kwargs)

        def form_invalid(self, form):
            response = super().form_invalid(form)
            accepted_type = self.request.get_preferred_type(self.accepted_media_types)
            if accepted_type == "text/html":
                return response
            elif accepted_type == "application/json":
                return JsonResponse(form.errors, status=400)

        def form_valid(self, form):
            # We make sure to call the parent's form_valid() method because
            # it might do some processing (in the case of CreateView, it will
            # call form.save() for example).
            response = super().form_valid(form)
            accepted_type = self.request.get_preferred_type(self.accepted_media_types)
            if accepted_type == "text/html":
                return response
            elif accepted_type == "application/json":
                data = {
                    "pk": self.object.pk,
                }
                return JsonResponse(data)