Gestion de formulaires avec les vues fondées sur les classesLien vers cette rubrique

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émentairesLien vers cette rubrique

Étant donné un formulaire de contact :

forms.py
Python
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
Python
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 :

Formulaires de modèlesLien vers cette rubrique

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.

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

models.py
Python
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
Python
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')

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
Python
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'),
]

Modèles et request.userLien vers cette rubrique

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
Python
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
Python
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 contenuLien vers cette rubrique

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 » :

Code
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']