Travailler avec des formulaires

À propos de ce document

Ce document fournit une introduction aux fonctions Django de gestion de formulaire. Pour plus de détails sur des domaines plus spécifiques de l’API Formulaires, voir The Forms API, Form fields, et Form and field validation.

django.forms est la biliothèque Django de gestion de formulaires.

Bien qu’il soit possible de traiter la soumission des formulaires en n’utilisant que la classe HttpRequest de Django, utiliser la bibliothèque de formulaires permet la prise en charge de nombre d’opérations relatives aux formulaires. En l’utilisant, vous pouvez:

  1. Afficher un formulaire HTML avec des widgets générés automatiquement.
  2. Vérifier les données envoyées, grâce à un ensemble de règles de validation.
  3. Ré-afficher une formulaire en cas d’erreurs de validation.
  4. Convertir les données du formulaire envoyé en types de données Python pertinentes.

Aperçu

La bibliothèque couvre ces concepts:

Widget
Une classe qui correspond à un widget de formulaire HTML, par exemple, <input type="text"> ou <textarea>. Gère le rendu du widget en HTML.
Field
Une classe responsable de la validation. Par exemple, un EmailField s’assure qu’on indique bien une adresse e-mail valide.
Form
Une collection de champs qui savent se valider eux-mêmes et s’afficher sous forme HTML.
Form Media
Les ressources CSS et JavaScript requises pour produire le formulaire.

La bibliothèque est découplée des autres composants Django, comme la couche de base de données, les vues et les templates. Elle n’est liée qu’aux paramètres Django, à quelques fonctions d’assistance django.utils et aux hooks d’internationalisation de Django (mais vous n’êtes pas obligé d’utiliser les fonctionnalités d’internationalisation pour utiliser cette bibliothèque).

Objets de formulaire

Un objet de formulaire encapsule une séquence de champs de formulaire et une collection de règles de validation qui doivent être satisfaites pour que le formulaire soit accepté. Les classes Formulaire sont créées en tant que sous-classes de django.forms.Form et utilisent un style déclaratif auquel vous êtes familiarisé si vous avez déjà utilisé les modèles base de données de Django.

Par exemple, étudions un formulaire utilisé pour implémenter une fonctionnalité “contact me” sur un site Web perso:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)

Un formulaire est composé d’objets Field. Dans cet exemple, notre formulaire a quatre champs: subject, message, sender et cc_myself.

CharField, EmailField et BooleanField sont juste trois des types de champs disponibles; la liste complète se trouve dans Form fields.

Si votre formulaire est utilisé pour ajouter ou éditer directement un modèle Django, vous pouvez utiliser un ModelForm, ce qui vous évitera de dupliquer la description de votre modèle.

Utiliser un formulaire dans une vue

L’organisation habituelle pour traiter un formulaire dans une vue ressemble à ça:

from django.shortcuts import render
from django.http import HttpResponseRedirect

def contact(request):
    if request.method == 'POST': # Si le formulaire a été envoyé...
        form = ContactForm(request.POST) # A form bound to the POST data
        if form.is_valid(): # All validation rules pass
            # Process the data in form.cleaned_data
            # ...
            return HttpResponseRedirect('/thanks/') # Redirect after POST
    else:
        form = ContactForm() # An unbound form

    return render(request, 'contact.html', {
        'form': form,
    })

Il y a là trois cheminements de code:

  1. Si le formulaire n’a pas été soumis, une instance non liée de ContactForm est créée et passée au template.
  2. Si le formulaire a été soumis, une instance liée du formulaire est créée en utilisant request.POST. Si les données envoyées sont valides, le formulaire est traité et l’utilisateur est redirigé vers une page de remerciement.
  3. Si le formulaire a été soumis mais n’est pas valide, l’instance liée du formulaire est passée au template.

La distinction entre formulaires liés et non liés est importante. Un formulaire non lié n’a pas de données qui lui sont associées; lorsqu’il est mis en forme pour l’utilisateur, il sera vide ou contiendra des valeurs par défaut.

Un formulaire lié a soumis des données et peut donc être utilisé pour dire si les données sont valides. Si un formulaire lié invalide est mis en forme, il peut contenir des messages d’erreur disant ce qui ne va pas à l’utilisateur.

Voir Bound and unbound forms pour plus d’informations sur les différences entre formulaires liés et non liés.

Gérer l’upload de fichier avec un formulaire

Pour voir comment gérer l’upload de fichier avec votre formulaire, voir Binding uploaded files to a form.

Traiter les données d’un formulaire

Une fois que is_valid() renvoie True, vous pouvez traiter l’envoi du formulaire en sachant qu’il est conforme aux règles de validation qu’il définissait.

Bien que vous puissiez directement accéder à request.POST à ce stade, il est préférable d’accéder à form.cleaned_data. Ces données n’ont pas seulement été validées mais ont également été converties pour vous en types Python pertinents.

Dans l’exemple précédent, cc_myself sera une valeur booléenne. De même, des champs tels que IntegerField et FloatField convertissent respectivement les valeurs en int et float Python. Notez que les champs en lecture seule ne sont pas disponibles dans form.cleaned_data (et paramétrer une valeur dans une méthode clean() personnalisée sera sans effet) parce que ces champs sont affichés en tant que texte au lieu de l’être en éléments de saisie, ils ne sont par conséquent pas envoyés au serveur.

En complétant l’exemple précédent, voici comment les données du formulaire peuvent être traitées:

if form.is_valid():
    subject = form.cleaned_data['subject']
    message = form.cleaned_data['message']
    sender = form.cleaned_data['sender']
    cc_myself = form.cleaned_data['cc_myself']

    recipients = ['info@example.com']
    if cc_myself:
        recipients.append(sender)

    from django.core.mail import send_mail
    send_mail(subject, message, sender, recipients)
    return HttpResponseRedirect('/thanks/') # Redirect after POST

Pour en savoir plus sur l’envoi d’e-mail à partir de Django, voir Sending email.

Afficher un formulaire en utilisant un template

Les formulaires sont conçus pour travailler avec le langage de template de Django. Dans l’exemple précédent, nous avons passé notre instance ContactForm au template en utilisant la variable de contexte form. Voici un simple exemple de template:

<form action="/contact/" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>

Le formulaire n’affiche que ses propres champs; il vous appartient de fournir les balises <form> et le bouton d’envoi.

Formulaires et protection Cross Site Request Forgery

Django embarque une protection contrel les attaques Cross Site Request Forgeries simple à utiliser. Lors de l’envoi d’un formulaire via POST avec la protection CSRF activée, vous devez utiliser la balise de template csrf_token comme dans l’exemple précédent. Cependant, la protection CSRF n’étant pas directement attachée aux formulaires dans les templates, nous avons omis cette balise dans les exemples qui suivent dans ce document.

form.as_p affichera le formulaire avec chacun de ses champs et un label d’accompagnement inclus dans un paragraphe. Voici la sortie pour notre template exemple:

<form action="/contact/" method="post">
<p><label for="id_subject">Subject:</label>
    <input id="id_subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="id_message">Message:</label>
    <input type="text" name="message" id="id_message" /></p>
<p><label for="id_sender">Sender:</label>
    <input type="text" name="sender" id="id_sender" /></p>
<p><label for="id_cc_myself">Cc myself:</label>
    <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>
<input type="submit" value="Submit" />
</form>

Notez que chaque champ du formulaire a un attribut ID paramétré à id_<field-name>, qui est référencé par le label d’accompagnement. C’est important pour que les formulaires soient accessibles aux technologies d’assistance, comme les logiciels lecteurs d’écrans. Vous pouvez aussi personnaliser la façon dont les labels et les ids sont générés.

Vous pouvez aussi utiliser form.as_table pour sortir des rangées de tableau (vous devrez fournir vos propres balises <table>) et form.as_ul pour sortir des éléments de liste.

Personnaliser le template du formulaire

Si l’HTML généré par défaut ne vous convient pas, vous pouvez personnaliser la présentation du formulaire totalement en utilisant le langage de template de Django. En complétant l’exemple précédent:

<form action="/contact/" method="post">
    {{ form.non_field_errors }}
    <div class="fieldWrapper">
        {{ form.subject.errors }}
        <label for="id_subject">Email subject:</label>
        {{ form.subject }}
    </div>
    <div class="fieldWrapper">
        {{ form.message.errors }}
        <label for="id_message">Your message:</label>
        {{ form.message }}
    </div>
    <div class="fieldWrapper">
        {{ form.sender.errors }}
        <label for="id_sender">Your email address:</label>
        {{ form.sender }}
    </div>
    <div class="fieldWrapper">
        {{ form.cc_myself.errors }}
        <label for="id_cc_myself">CC yourself?</label>
        {{ form.cc_myself }}
    </div>
    <p><input type="submit" value="Send message" /></p>
</form>

Chaque form-field nommé peut être sorti sur le template en utilisant {{ form.name_of_field }}, qui produira l’HTML nécessaire à l’affichage du widget de formulaire. Utiliser {{ form.name_of_field.errors }} affiche une liste des erreurs du formulaire, sous forme de liste non ordonnée. Elle pourrait ressembler à ça:

<ul class="errorlist">
    <li>Sender is required.</li>
</ul>

La liste a une classe CSS errorlist pour vous permettre de personnaliser son apparence. Si vous voulez personnaliser davantage l’affichage des erreurs, vous pouvez le faire en bouclant dessus:

{% if form.subject.errors %}
    <ol>
    {% for error in form.subject.errors %}
        <li><strong>{{ error|escape }}</strong></li>
    {% endfor %}
    </ol>
{% endif %}

Boucle sur les champs de formulaire

Si vous utilisez le même HTML pour chacun des champs du formulaire, vous pouvez diminuer le code dupliqué en bouclant sur chacun des champs avec {% for %}:

<form action="/contact/" method="post">
    {% for field in form %}
        <div class="fieldWrapper">
            {{ field.errors }}
            {{ field.label_tag }}: {{ field }}
        </div>
    {% endfor %}
    <p><input type="submit" value="Send message" /></p>
</form>

Dans cette boucle, {{ field }} est une instance de BoundField. BoundField dispose également des attributs suivants, qui peuvent être pratiques dans vos templates:

{{ field.label }}
Le label du champ, par exemple Email address.
{{ field.label_tag }}
Le label du champ, entouré de la balise HTML <label> adéquate, par exemple <label for="id_email">Email address</label>
{{ field.value }}
La valeur du champ. Par exemple someone@example.com
{{ field.html_name }}
Le nom du champ qui sera utilisé dans le nom de l’élément de saisie. Tient compte du préfixe de formulaire, s’il a été paramètré.
{{ field.help_text }}
Un texte d’aide associé au champ.
{{ field.errors }}
Soit un <ul class="errorlist"> contenant les erreurs de validation du champ. Vous pouvez personnaliser la présentation des erreurs avec une boucle {% for error in field.errors %}. Dans ce cas chaque objet dans la boucle est une simple chaîne contenant le message d’erreur.
field.is_hidden

Cet attribut est True si le champ est un champ caché, et False autrement. Ce n’est pas forcément utile comme variable de template, mais peut s’avérer utile dans des tests conditionnels comme:

{% if field.is_hidden %}
   {# Do something special #}
{% endif %}

Boucles sur les champs visibles et cachés

Si vous faites la mise en page d’un formulaire manuellement dans un template, par opposition à la mise en page par défaut de la couche formulaire de Django, vous voudrez peut-être traiter les champs <input type="hidden"> différemment des champs non cachés. Par exemple, puisque les champs cachés n’affichent rien, mettre des messages d’erreur “à côté” de ces champs peut être source de confusion pour les utilisateurs – les erreurs de ces champs devront donc être gérées différemment.

Django fournit deux méthodes sur un formulaire qui vous permettent de boucler indépendamment sur les champs visibles et cachés: hidden_fields() et visible_fields(). Voici le précédent exemple, modifié pour utiliser ces deux méthodes:

<form action="/contact/" method="post">
    {# Include the hidden fields #}
    {% for hidden in form.hidden_fields %}
    {{ hidden }}
    {% endfor %}
    {# Include the visible fields #}
    {% for field in form.visible_fields %}
        <div class="fieldWrapper">
            {{ field.errors }}
            {{ field.label_tag }}: {{ field }}
        </div>
    {% endfor %}
    <p><input type="submit" value="Send message" /></p>
</form>

Cet exemple ne gère aucune erreur des champs cachés. Habituellement, une erreur sur un champ caché est signe de falsification du formulaire, puisque, lors d’une interaction normale avec un formulaire, on ne touche pas aux champs cachés. Vous pouvez néanmoins leur ajouter facilement un affichage d’erreur.

Templates de formulaires réutilisables

Si votre site emploie en différents endroits la même logique de mise en forme pour les formulaires, vous pouvez réduire la duplication de code en sauvegardant la boucle de formulaire dans un template autonome, et utiliser la balise include pour le réutiliser dans d’autres templates:

<form action="/contact/" method="post">
    {% include "form_snippet.html" %}
    <p><input type="submit" value="Send message" /></p>
</form>

# In form_snippet.html:

{% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }}: {{ field }}
    </div>
{% endfor %}

Si l’objet formulaire passé au template à un nom différent, dépendant du contexte, vous pouvez créer un alias en utilisant l’argument with de la balise include:

<form action="/comments/add/" method="post">
    {% include "form_snippet.html" with form=comment_form %}
    <p><input type="submit" value="Submit comment" /></p>
</form>

Si vous vous retrouvez à agir souvent ainsi, vous devriez songer à créer une balise d’inclusion personnalisée.

Contact