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 simple de contact :
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
:
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
class ContactView(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 deTemplateResponseMixin
, ce qui fait qu’on peut utilisertemplate_name
à cet endroit.- L’implémentation par défaut de
form_valid()
redirige simplement verssuccess_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), il suffit de définir 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
:
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 :
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']
class AuthorUpdate(UpdateView):
model = Author
fields = ['name']
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
Note
Nous devons utiliser reverse_lazy()
ici, pas simplement reverse()
car la configuration d’URL n’est pas 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 :
from django.urls import path
from myapp.views import AuthorCreate, AuthorDelete, AuthorUpdate
urlpatterns = [
# ...
path('author/add/', AuthorCreate.as_view(), name='author-add'),
path('author/<int:pk>/', AuthorUpdate.as_view(), name='author-update'),
path('author/<int:pk>/delete/', AuthorDelete.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
etUpdateView
utilisentmyapp/author_form.html
DeleteView
utilisemyapp/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 :
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 :
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
Notez que vous devrez décorer cette vue par login_required()
ou trouver un autre moyen de gérer les accès d’utilisateurs non autorisés dans form_valid()
.
Exemple AJAX¶
Voici un exemple simple montrant une possible implémentation d’un formulaire fonctionnant aussi bien pour des requêtes AJAX 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 AjaxableResponseMixin:
"""
Mixin to add AJAX 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.is_ajax():
return JsonResponse(form.errors, status=400)
else:
return response
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.is_ajax():
data = {
'pk': self.object.pk,
}
return JsonResponse(data)
else:
return response
class AuthorCreate(AjaxableResponseMixin, CreateView):
model = Author
fields = ['name']