• en
  • Language: fr

L’API des formulaires

À propos de ce document

Ce document aborde en détails l’API des formulaires de Django. Il est recommandé de lire d’abord l’introduction à l’utilisation des formulaires.

Formulaires liés et non liés

Une instance Form est soit liée (bound) à un jeu de données, soit non liée (unbound).

  • Si elle est liée à un jeu de données, elle est capable de valider ces données et d’afficher un formulaire en HTML en y incluant les données.

  • Si elle est non liée, elle ne peut pas procéder à la validation (car il n’y a aucune donnée à valider !), mais elle peut tout de même afficher un formulaire HTML vierge.

class Form

Pour créer une instance Form non liée, il suffit d’instancier la classe :

>>> f = ContactForm()

Pour lier des données au formulaire, transmettez ces données sous forme de dictionnaire comme premier paramètre au constructeur de la classe Form:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)

Dans ce dictionnaire, les clés sont les noms de champs qui correspondent aux attributs de la classe Form. Les valeurs sont les données que vous souhaitez valider. Il s’agit en principe de chaînes de caractères, mais ce n’est pas une obligation. Le type des données transmises dépend du champ Field, comme nous allons le voir dans un moment.

Form.is_bound

Si vous avez besoin de faire la différence entre des instances de formulaires liés et non liés au moment de l’exécution, vous pouvez vous baser sur l’attribut is_bound du formulaire :

>>> f = ContactForm()
>>> f.is_bound
False
>>> f = ContactForm({'subject': 'hello'})
>>> f.is_bound
True

Notez que le fait de transmettre un dictionnaire vide crée un formulaire lié avec des données vides :

>>> f = ContactForm({})
>>> f.is_bound
True

Si vous souhaitez modifier d’une quelconque manière les données d’une instance Form liée ou si vous aimeriez lier une instance Form non liée à certaines données, créez une nouvelle instance de Form. Il n’est pas possible de modifier les données dans une instance Form. Dès qu’une instance Form a été créée, ses données doivent être considérées comme immuables, que les données existent ou non.

Utilisation de formulaires pour valider des données

Form.clean()

L’implémentation d’une méthode clean() pour un formulaire se justifie lorsque de la validation personnalisée est nécessaire pour des champs interdépendants. Voir Nettoyage et validation de champs qui dépendent l’un de l’autre pour des exemples d’utilisation.

Form.is_valid()

La tâche principale d’un objet Form est de valider des données. Disposant d’une instance Form liée, appelez la méthode is_valid() pour procéder à la validation et renvoyer une valeur booléenne indiquant si les données sont valides :

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True

Essayons avec certaines données non valides. Dans ce cas, subject est vide (ce qui constitue une erreur, car tous les champs sont obligatoires par défaut) et sender n’est pas une adresse électronique valable :

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
Form.errors

Accédez à l’attribut errors pour obtenir un dictionnaire des messages d’erreur :

>>> f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}

Dans ce dictionnaire, les clés correspondent aux noms de champs et les valeurs à des listes de chaînes Unicode représentant les messages d’erreur. Ceux-ci sont stockés dans des listes car un champ peut générer plusieurs messages d’erreur.

Vous pouvez accéder à errors sans devoir appeler d’abord is_valid(). Les données du formulaire seront validées lors du premier appel à is_valid() ou du premier accès à errors.

Les routines de validation ne sont appelées qu’une seule fois, même si vous appelez plusieurs fois is_valid() ou que vous accédez plusieurs fois à errors. Cela signifie que si la validation provoque des effets de bord, ceux-ci ne sont produits qu’une seule fois.

Form.errors.as_data()
New in Django 1.7.

Renvoie un dictionnaire faisant correspondre les champs à leur instance ValidationError originale.

>>> f.errors.as_data()
{'sender': [ValidationError(['Enter a valid email address.'])],
'subject': [ValidationError(['This field is required.'])]}

Utilisez cette méthode chaque fois qu’il y a besoin d’identifier une erreur par son code. Cela permet des choses telles que la réécriture du message d’erreur ou l’écriture d’une logique personnalisée dans une vue lorsqu’une erreur donnée est présente. Elle peut également être utilisée pour sérialiser les erreurs dans un format personnalisé (par ex. XML) ; par exemple, as_json() se base sur as_data().

La nécessité de la méthode as_data() s’explique par la rétrocompatibilité. Précédemment, les instances ValidationError étaient perdues dès le moment où leurs messages d’erreur étaient ajoutés dans leur état rendu au dictionnaire Form.errors. Idéalement, Form.errors aurait dû stocker les instances ValidationError et des méthodes préfixées par as_ auraient pu produire leur rendu final, mais il a fallu procéder d’une manière inverse afin de ne pas casser du code qui s’attendait à trouver des messages d’erreur « finaux » dans Form.errors.

Form.errors.as_json(escape_html=False)
New in Django 1.7.

Renvoie les erreurs sérialisées en JSON.

>>> f.errors.as_json()
{"sender": [{"message": "Enter a valid email address.", "code": "invalid"}],
"subject": [{"message": "This field is required.", "code": "required"}]}

Par défaut, as_json() n’échappe pas son contenu. Si vous l’utilisez dans un contexte comme des requêtes AJAX vers une vue de formulaire où le client interprète la réponse et insère les erreurs dans la page, il faut vous assurer de bien échapper le contenu du côté client pour éviter l’éventualité d’une attaque de script intersite. C’est une opération triviale avec une bibliothèque JavaScript comme jQuery, il suffit d’utiliser $(el).text(errorText) plutôt que .html().

Si pour une raison précise vous ne souhaitez pas utiliser l’échappement du côté client, vous pouvez aussi définir escape_html=True et les messages d’erreur seront échappés afin de pouvoir les utiliser directement en HTML.

Form.add_error(field, error)
New in Django 1.7.

Cette méthode permet d’ajouter des erreurs à des champs spécifiques depuis la méthode Form.clean() elle-même ou carrément depuis l’extérieur du formulaire, par exemple depuis une vue.

Le paramètre field est le nom du champ auquel les erreurs seront attribuées. Si sa valeur est None, l’erreur est traitée comme une erreur non liée à un champ, et fera partie des erreurs renvoyées par Form.non_field_errors().

Le paramètre error peut être une simple chaîne ou de préférence une instance de ValidationError. Consultez Génération de ValidationError pour des conseils de bonnes pratiques lors de la définition d’erreurs de formulaire.

Notez que Form.add_error() enlève automatiquement les champs correspondants du dictionnaire cleaned_data.

Form.has_error(field, code=None)
New in Django 1.8.

Cette méthode renvoie une valeur booléenne indiquant si un champ contient une erreur avec un code d’erreur spécifique. Si code vaut None, la méthode renvoie True si le champ contient n’importe quelle erreur.

Pour vérifier la présence d’erreurs non liées aux champs, indiquez NON_FIELD_ERRORS dans le paramètre field.

Form.non_field_errors()

Cette méthode renvoie la liste des erreurs dans Form.errors qui ne sont pas associées à un champ particulier. Cela comprend les erreurs ValidationError qui sont générées dans Form.clean() et les erreurs ajoutées par Form.add_error(None, "...").

Comportement des formulaires non liés

Il n’y a pas de raison de vouloir valider un formulaire sans données, mais pour que cela soit dit, voici ce qui se passe avec des formulaires non liés :

>>> f = ContactForm()
>>> f.is_valid()
False
>>> f.errors
{}

Valeurs initiales dynamiques

Form.initial

Le paramètre initial permet de déclarer des valeurs initiales des champs de formulaire au moment de l’exécution. Par exemple, il peut être intéressant de pré-remplir un champ username avec le nom d’utilisateur de la session en cours.

Pour faire cela, utilisez le paramètre initial d’une instance Form. Quand il est présent, ce paramètre doit être un dictionnaire faisant correspondre des noms de champs à des valeurs initiales. N’incluez que les champs pour lesquels une valeur initiale existe ; il n’est pas nécessaire d’inclure tous les champs du formulaire. Par exemple :

>>> f = ContactForm(initial={'subject': 'Hi there!'})

Ces valeurs ne sont affichées que pour les formulaires non liés et elles ne servent pas à fournir des valeurs par défaut si un champ particulier n’est pas renseigné.

Notez que si un champ Field définit initial et que vous incluez initial lors de la création du formulaire, c’est ce dernier qui prend le dessus. Dans cet exemple, initial est renseigné à la fois au niveau du champ et au niveau de l’instance de formulaire, et c’est ce dernier qui a la priorité :

>>> from django import forms
>>> class CommentForm(forms.Form):
...     name = forms.CharField(initial='class')
...     url = forms.URLField()
...     comment = forms.CharField()
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
>>> print(f)
<tr><th>Name:</th><td><input type="text" name="name" value="instance" /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>

Contrôle des données de formulaires modifiées

Form.has_changed()

Lorsque vous avez besoin de savoir quelles données de formulaire ont été modifiées en référence aux données initiales, utilisez la méthode has_changed() du formulaire.

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data, initial=data)
>>> f.has_changed()
False

Lorsque le formulaire est envoyé, il est reconstruit et les données d’origine sont fournies afin que la comparaison puisse être faite :

>>> f = ContactForm(request.POST, initial=data)
>>> f.has_changed()

has_changed() renvoie True si les données de request.POST diffèrent de celles qui ont été fournies dans initial, sinon elle renvoie False. Le résultat est produit en appelant Field.has_changed() pour chaque champ du formulaire.

Form.changed_data

L’attribut changed_data renvoie une liste des noms de champs dont les valeurs dans les données liées au formulaire (habituellement request.POST) diffèrent de celles fournies initialement dans initial. La liste renvoyée est vide si les données sont parfaitement identiques.

>>> f = ContactForm(request.POST, initial=data)
>>> if f.has_changed():
...     print("The following fields changed: %s" % ", ".join(f.changed_data))

Accès aux champs depuis le formulaire

Form.fields

Vous pouvez accéder aux champs d’une instance de Form depuis son attribut fields:

>>> for row in f.fields.values(): print(row)
...
<django.forms.fields.CharField object at 0x7ffaac632510>
<django.forms.fields.URLField object at 0x7ffaac632f90>
<django.forms.fields.CharField object at 0x7ffaac3aa050>
>>> f.fields['name']
<django.forms.fields.CharField object at 0x7ffaac6324d0>

Vous pouvez modifier un champ d’une instance Form pour changer la façon dont il sera affiché dans le formulaire :

>>> f.as_table().split('\n')[0]
'<tr><th>Name:</th><td><input name="name" type="text" value="instance" /></td></tr>'
>>> f.fields['name'].label = "Username"
>>> f.as_table().split('\n')[0]
'<tr><th>Username:</th><td><input name="name" type="text" value="instance" /></td></tr>'

Faites attention de ne pas modifier l’attribut base_fields car cette modification influencerait toutes les instances ContactForm suivantes à l’intérieur du même processus Python :

>>> f.base_fields['name'].label = "Username"
>>> another_f = CommentForm(auto_id=False)
>>> another_f.as_table().split('\n')[0]
'<tr><th>Username:</th><td><input name="name" type="text" value="class" /></td></tr>'

Accès aux données « nettoyées »

Form.cleaned_data

Chaque champ d’une classe Form a non seulement la responsabilité de valider ses données, mais aussi de les « nettoyer », c’est-à-dire les normaliser dans un format cohérent. C’est une fonction bien utile, parce que cela permet de saisir les données d’un champ de plusieurs manières, tout en conservant une cohérence au niveau de la donnée résultante.

Par exemple, DateField normalise les saisies en un objet Python datetime.date. Que le contenu transmis soit une chaîne au format '1994-07-15', un objet datetime.date ou un autre format encore, DateField transformera toujours ce contenu en objet datetime.date, pour autant qu’il soit valide.

Après avoir créé une instance de Form avec un jeu de données et l’avoir validé, il est possible d’accéder aux données nettoyées par son attribut cleaned_data:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}

Notez que tout champ basé sur du texte, tel que CharField ou EmailField, nettoie toujours le contenu saisi pour en faire une chaîne Unicode. Nous aborderons les implications du codage plus loin dans ce document.

Si vos données ne sont pas toutes valides, le dictionnaire cleaned_data ne contient que les champs valides :

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there'}

cleaned_data ne contient toujours que des clés correspondant à des champs définis dans le formulaire, même si vous lui transmettez des données supplémentaires lors de la création du formulaire. Dans cet exemple, nous transmettons des données de champs supplémentaires au constructeur de ContactForm, mais cleaned_data ne contient que les données correspondant aux champs du formulaire :

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True,
...         'extra_field_1': 'foo',
...         'extra_field_2': 'bar',
...         'extra_field_3': 'baz'}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}

Lorsque le formulaire est valide, cleaned_data inclut une clé et une valeur pour tous ses champs, même si les données ne contenaient pas de valeur pour certains champs facultatifs. Dans cet exemple, le dictionnaire de données ne contient pas de valeur pour le champ nick_name, mais cleaned_data l’intègre tout de même, avec une valeur vide :

>>> from django.forms import Form
>>> class OptionalPersonForm(Form):
...     first_name = CharField()
...     last_name = CharField()
...     nick_name = CharField(required=False)
>>> data = {'first_name': 'John', 'last_name': 'Lennon'}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'nick_name': '', 'first_name': 'John', 'last_name': 'Lennon'}

Dans l’exemple ci-dessus, la valeur cleaned_data de nick_name correspond à une chaîne vide, car nick_name est un champ CharField, et ces champs considèrent la chaîne vide comme une valeur vide. Chaque type de champ connaît sa valeur vide, par exemple DateField emploie None au lieu de la chaîne vide. Pour plus de détails sur le comportement de chaque champ dans ce cas de figure, consultez la rubrique « Valeur vide » de chaque champ dans la section « Classes Field intégrées » ci-dessous.

Il est possible d’écrire du code pour effectuer la validation de certains champs de formulaires (en fonction de leur nom) ou pour le formulaire entier (prenant en compte la combinaison de différents champs). Vous trouverez davantage d’informations à ce sujet dans La validation de formulaires et de champs.

Affichage des formulaires en HTML

La seconde tâche d’un objet Form est de s’afficher lui-même au format HTML. Pour faire cela, il suffit de l’afficher avec print:

>>> f = ContactForm()
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" /></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>

Si le formulaire est lié à des données, le résultat HTML contiendra ces données comme il se doit. Par exemple, si un champ est représenté par un composant <input type="text">, les données figureront dans l’attribut value. Si un champ est représenté par un composant <input type="checkbox">, le HTML produit contiendra checked="checked" le cas échéant :

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" value="hello" /></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" value="Hi there" /></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" value="foo@example.com" /></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" checked="checked" /></td></tr>

Ce résultat par défaut est un tableau HTML à deux colonnes, avec une ligne <tr> par champ. Notez ceci :

  • Pour des raisons d’agilité, le résultat ne contient pas les balises <table> et </table>, ni les balises <form>, </form> ou <input type="submit">. C’est à vous de les fournir.

  • Chaque type de champ possède une représentation HTML par défaut. CharField est représenté par <input type="text"> et EmailField par <input type="email">. BooleanField est représenté par <input type="checkbox">. Notez qu’il ne s’agit que de valeurs par défaut raisonnables ; il est possible de définir le code HTML produit par un champ spécifique en utilisant des composants, ce que nous expliquerons tout à l’heure.

  • Le nom name HTML de chaque balise est directement dérivé de son nom d’attribut dans la classe ContactForm.

  • L’étiquette textuelle de chaque champ (par ex. 'Subject:', 'Message:' et 'Cc myself:') est généré à partir du nom de champ en convertissant tous les soulignements en espaces et en mettant en majuscule la première lettre. Encore une fois, il ne s’agit que de valeurs par défaut plus ou moins adéquates ; vous pouvez aussi définir ces étiquettes manuellement.

  • Chaque étiquette textuelle est intégrée dans une balise HTML <label> qui se réfère à son champ de formulaire par son attribut id. La valeur de celui-ci est générée en ajoutant le préfixe 'id_' au nom du champ. Les attributs id et les balises <label> sont compris dans le HTML produit par défaut pour être conforme aux bonnes pratiques, mais vous pouvez modifier ce comportement.

Bien que le résultat sous forme de <table> soit le style de résultat par défaut lorsqu’on affiche un formulaire avec print, d’autres styles de résultat sont disponibles. Chaque style est disponible par une méthode de l’objet formulaire et chaque méthode de rendu renvoie un objet Unicode.

as_p()

Form.as_p()

as_p() produit le formulaire par une série de balises <p>, chacune contenant un champ :

>>> f = ContactForm()
>>> f.as_p()
'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>'
>>> print(f.as_p())
<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="email" 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>

as_ul()

Form.as_ul()

as_ul() produit le formulaire par une série de balises <li>, chacune contenant un champ. Les balises <ul> et </ul> ne sont pas comprises, ce qui vous donne la flexibilité de définir vous-même des attributs à la balise <ul>:

>>> f = ContactForm()
>>> f.as_ul()
'<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></li>\n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li>\n<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" /></li>\n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></li>'
>>> print(f.as_ul())
<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li>
<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" /></li>
<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></li>

as_table()

Form.as_table()

Pour terminer, as_table() produit le formulaire dans une balise HTML <table>. C’est le même résultat qu’en l’affichant avec print. En fait, lorsqu’un objet formulaire est affiché avec print, c’est sa méthode as_table() qui est appelée en arrière-plan :

>>> f = ContactForm()
>>> f.as_table()
'<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>\n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>\n<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" /></td></tr>\n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>'
>>> print(f.as_table())
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" /></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>

Ajout de styles aux lignes de formulaire obligatoires ou erronées

Form.error_css_class
Form.required_css_class

Il est assez fréquent de devoir définir des styles particuliers s’appliquant aux lignes et champs de formulaire qui sont obligatoires ou qui contiennent des erreurs. Par exemple, on pourrait afficher en gras les lignes de formulaire obligatoires et afficher les erreurs en rouge.

La classe Form offre plusieurs points d’entrée permettant d’ajouter des attributs class aux lignes obligatoires ou aux lignes avec des erreurs : il suffit de compléter les attributs Form.error_css_class et Form.required_css_class:

from django.forms import Form

class ContactForm(Form):
    error_css_class = 'error'
    required_css_class = 'required'

    # ... and the rest of your fields here

Après avoir fait cela, les classes "error" et "required" seront attribuées aux lignes correspondantes. Le code HTML ressemblera à quelque chose comme :

>>> f = ContactForm(data)
>>> print(f.as_table())
<tr class="required"><th><label class="required" for="id_subject">Subject:</label>    ...
<tr class="required"><th><label class="required" for="id_message">Message:</label>    ...
<tr class="required error"><th><label class="required" for="id_sender">Sender:</label>      ...
<tr><th><label for="id_cc_myself">Cc myself:<label> ...
>>> f['subject'].label_tag()
<label class="required" for="id_subject">Subject:</label>
>>> f['subject'].label_tag(attrs={'class': 'foo'})
<label for="id_subject" class="foo required">Subject:</label>
Changed in Django 1.8:

La classe required_css_class est aussi ajoutée à la balise <label> comme on le voit ci-dessus.

Configuration des attributs id et des balises <label> dans le code HTML des formulaires

Form.auto_id

Par défaut, les méthodes de rendu HTML des formulaires comprennent :

  • Les attributs HTML id des éléments de formulaire.

  • Les balises <label> autour des étiquettes de champ. Une balise HTML <label> détermine quel texte descriptif est associé à un élément de formulaire. Cette petite amélioration rend les formulaires plus conviviaux et mieux adaptés aux techniques d’accessibilité. Il est recommandé de toujours utiliser des balises <label>.

Les valeurs d’attribut id sont générées en préfixant les noms de champ de formulaire par id_. Ce mécanisme peut cependant être configuré si vous souhaitez modifier la convention id ou supprimer complètement les attributs HTML id ou les balises <label>.

Utilisez le paramètre auto_id du constructeur de Form pour contrôler le comportement id et label. Ce paramètre doit valoir True, False ou contenir une chaîne.

Si auto_id vaut False, le rendu HTML du formulaire ne contiendra par de balises <label> ni d’attributs id:

>>> f = ContactForm(auto_id=False)
>>> print(f.as_table())
<tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" /></td></tr>
<tr><th>Message:</th><td><input type="text" name="message" /></td></tr>
<tr><th>Sender:</th><td><input type="email" name="sender" /></td></tr>
<tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr>
>>> print(f.as_ul())
<li>Subject: <input type="text" name="subject" maxlength="100" /></li>
<li>Message: <input type="text" name="message" /></li>
<li>Sender: <input type="email" name="sender" /></li>
<li>Cc myself: <input type="checkbox" name="cc_myself" /></li>
>>> print(f.as_p())
<p>Subject: <input type="text" name="subject" maxlength="100" /></p>
<p>Message: <input type="text" name="message" /></p>
<p>Sender: <input type="email" name="sender" /></p>
<p>Cc myself: <input type="checkbox" name="cc_myself" /></p>

Si auto_id est défini à True, le rendu HTML du formulaire contiendra des balises <label> et utilisera le nom du champ comme identifiant id pour chaque champ de formulaire :

>>> f = ContactForm(auto_id=True)
>>> print(f.as_table())
<tr><th><label for="subject">Subject:</label></th><td><input id="subject" type="text" name="subject" maxlength="100" /></td></tr>
<tr><th><label for="message">Message:</label></th><td><input type="text" name="message" id="message" /></td></tr>
<tr><th><label for="sender">Sender:</label></th><td><input type="email" name="sender" id="sender" /></td></tr>
<tr><th><label for="cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="cc_myself" /></td></tr>
>>> print(f.as_ul())
<li><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="message">Message:</label> <input type="text" name="message" id="message" /></li>
<li><label for="sender">Sender:</label> <input type="email" name="sender" id="sender" /></li>
<li><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself" /></li>
>>> print(f.as_p())
<p><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="message">Message:</label> <input type="text" name="message" id="message" /></p>
<p><label for="sender">Sender:</label> <input type="email" name="sender" id="sender" /></p>
<p><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself" /></p>

Si auto_id est défini à une chaîne contenant le caractère de format '%s', le rendu HTML du formulaire contiendra des balises <label> et produira des attributs id en fonction de la chaîne de format. Par exemple, compte tenu d’une chaîne de format 'champ_%s', l’attribut id d’un champ nommé``sujet`` sera 'champ_sujet'. En poursuivant notre exemple :

>>> f = ContactForm(auto_id='id_for_%s')
>>> print(f.as_table())
<tr><th><label for="id_for_subject">Subject:</label></th><td><input id="id_for_subject" type="text" name="subject" maxlength="100" /></td></tr>
<tr><th><label for="id_for_message">Message:</label></th><td><input type="text" name="message" id="id_for_message" /></td></tr>
<tr><th><label for="id_for_sender">Sender:</label></th><td><input type="email" name="sender" id="id_for_sender" /></td></tr>
<tr><th><label for="id_for_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></td></tr>
>>> print(f.as_ul())
<li><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" /></li>
<li><label for="id_for_sender">Sender:</label> <input type="email" name="sender" id="id_for_sender" /></li>
<li><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li>
>>> print(f.as_p())
<p><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" /></p>
<p><label for="id_for_sender">Sender:</label> <input type="email" name="sender" id="id_for_sender" /></p>
<p><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></p>

Si auto_id est défini à toute autre valeur évaluée à la valeur « vrai », comme par exemple une chaîne ne contenant pas de %s, la bibliothèque va considérer que auto_id vaut True.

Par défaut, auto_id contient la valeur 'id_%s'.

Form.label_suffix

Une chaîne traduisible (en anglais, la valeur par défaut est deux-points (:)) qui sera ajoutée à chaque nom d’étiquette lors du rendu HTML d’un formulaire.

Il est possible de personnaliser ce caractère ou de l’omettre entièrement en utilisant le paramètre label_suffix:

>>> f = ContactForm(auto_id='id_for_%s', label_suffix='')
>>> print(f.as_ul())
<li><label for="id_for_subject">Subject</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="id_for_message">Message</label> <input type="text" name="message" id="id_for_message" /></li>
<li><label for="id_for_sender">Sender</label> <input type="email" name="sender" id="id_for_sender" /></li>
<li><label for="id_for_cc_myself">Cc myself</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li>
>>> f = ContactForm(auto_id='id_for_%s', label_suffix=' ->')
>>> print(f.as_ul())
<li><label for="id_for_subject">Subject -></label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="id_for_message">Message -></label> <input type="text" name="message" id="id_for_message" /></li>
<li><label for="id_for_sender">Sender -></label> <input type="email" name="sender" id="id_for_sender" /></li>
<li><label for="id_for_cc_myself">Cc myself -></label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li>

Notez que le suffixe d’étiquette n’est ajouté que si le dernier caractère de l’étiquette n’est pas un caractère de ponctuation (en anglais, il s’agit de ., !, ? ou :).

New in Django 1.8.

Les champs peuvent aussi définir leur propre label_suffix. Cette définition a la priorité sur Form.label_suffix. Le suffixe peut aussi être surchargé au cours de l’exécution en utilisant le paramètre label_suffix de label_tag().

Notes sur le tri des champs

Dans les raccourcis as_p(), as_ul() et as_table(), les champs sont affichés dans l’ordre de leur définition dans la classe de formulaire. Par exemple, dans l’exemple de ContactForm, les champs sont définis dans l’ordre subject, message, sender, cc_myself. Pour changer cet ordre dans le résultat HTML, il suffit de changer l’ordre dans lequel ces champs apparaissent dans la classe.

Affichage des erreurs

Lorsque vous affichez un objet Form lié à des données, le fait de l’afficher va automatiquement procéder à la validation du formulaire s’il ne l’a pas encore été, et le résultat HTML contiendra les erreurs de validation sous forme d’une section <ul class="errorlist"> à côté du champ. Le positionnement précis des messages d’erreur dépend de la méthode de rendu HTML utilisée :

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data, auto_id=False)
>>> print(f.as_table())
<tr><th>Subject:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" /></td></tr>
<tr><th>Message:</th><td><input type="text" name="message" value="Hi there" /></td></tr>
<tr><th>Sender:</th><td><ul class="errorlist"><li>Enter a valid email address.</li></ul><input type="email" name="sender" value="invalid email address" /></td></tr>
<tr><th>Cc myself:</th><td><input checked="checked" type="checkbox" name="cc_myself" /></td></tr>
>>> print(f.as_ul())
<li><ul class="errorlist"><li>This field is required.</li></ul>Subject: <input type="text" name="subject" maxlength="100" /></li>
<li>Message: <input type="text" name="message" value="Hi there" /></li>
<li><ul class="errorlist"><li>Enter a valid email address.</li></ul>Sender: <input type="email" name="sender" value="invalid email address" /></li>
<li>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></li>
>>> print(f.as_p())
<p><ul class="errorlist"><li>This field is required.</li></ul></p>
<p>Subject: <input type="text" name="subject" maxlength="100" /></p>
<p>Message: <input type="text" name="message" value="Hi there" /></p>
<p><ul class="errorlist"><li>Enter a valid email address.</li></ul></p>
<p>Sender: <input type="email" name="sender" value="invalid email address" /></p>
<p>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></p>

Personnalisation du format de la liste d’erreurs

Par défaut, les formulaires utilisent django.forms.utils.ErrorList pour mettre en forme les erreurs de validation. Si vous aimeriez utiliser une autre classe pour afficher les erreurs, vous pouvez l’indiquer au moment de la construction du formulaire (remplacez __str__ par __unicode__ avec Python 2) :

>>> from django.forms.utils import ErrorList
>>> class DivErrorList(ErrorList):
...     def __str__(self):              # __unicode__ on Python 2
...         return self.as_divs()
...     def as_divs(self):
...         if not self: return ''
...         return '<div class="errorlist">%s</div>' % ''.join(['<div class="error">%s</div>' % e for e in self])
>>> f = ContactForm(data, auto_id=False, error_class=DivErrorList)
>>> f.as_p()
<div class="errorlist"><div class="error">This field is required.</div></div>
<p>Subject: <input type="text" name="subject" maxlength="100" /></p>
<p>Message: <input type="text" name="message" value="Hi there" /></p>
<div class="errorlist"><div class="error">Enter a valid email address.</div></div>
<p>Sender: <input type="email" name="sender" value="invalid email address" /></p>
<p>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></p>
Changed in Django 1.7:

django.forms.util a été renommé en django.forms.utils.

Un affichage plus fin

Les méthodes as_p(), as_ul() et as_table() ne sont que des raccourcis pour les développeurs fainéants, ce ne sont pas les seules façons d’afficher une objet formulaire.

class BoundField

Utilisé pour afficher en HTML un champ unique d’une instance de Form ou pour accéder à ses attributs.

La méthode __str__() (__unicode__ avec Python 2) de cet objet affiche le code HTML du champ.

Pour récupérer un seul BoundField, employez la syntaxe de consultation de dictionnaire sur le formulaire en utilisant le nom du champ comme clé :

>>> form = ContactForm()
>>> print(form['subject'])
<input id="id_subject" type="text" name="subject" maxlength="100" />

Pour obtenir tous les objets BoundField, faites une boucle sur le formulaire :

>>> form = ContactForm()
>>> for boundfield in form: print(boundfield)
<input id="id_subject" type="text" name="subject" maxlength="100" />
<input type="text" name="message" id="id_message" />
<input type="email" name="sender" id="id_sender" />
<input type="checkbox" name="cc_myself" id="id_cc_myself" />

Le résultat HTML spécifique de chaque champ respecte le réglage auto_id de l’objet formulaire :

>>> f = ContactForm(auto_id=False)
>>> print(f['message'])
<input type="text" name="message" />
>>> f = ContactForm(auto_id='id_%s')
>>> print(f['message'])
<input type="text" name="message" id="id_message" />

Pour obtenir la liste des erreurs d’un champ, accédez à l’attribut errors du champ.

BoundField.errors

Un objet apparenté à une liste qui s’affiche par une section <ul class="errorlist"> lorsqu’il est affiché en HTML :

>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''}
>>> f = ContactForm(data, auto_id=False)
>>> print(f['message'])
<input type="text" name="message" />
>>> f['message'].errors
['This field is required.']
>>> print(f['message'].errors)
<ul class="errorlist"><li>This field is required.</li></ul>
>>> f['subject'].errors
[]
>>> print(f['subject'].errors)

>>> str(f['subject'].errors)
''
BoundField.label_tag(contents=None, attrs=None, label_suffix=None)

Pour afficher séparément la balise label d’un champ de formulaire, on peut appeler sa méthode label_tag:

>>> f = ContactForm(data)
>>> print(f['message'].label_tag())
<label for="id_message">Message:</label>

Une autre variante possible est de fournir le paramètre contents qui remplacera la balise label générée automatiquement. Un dictionnaire attrs facultatif peut contenir des attributs supplémentaires de la balise <label>.

Le code HTML produit inclut la valeur label_suffix du formulaire (un caractère deux-points par défaut) ou, s’il est défini, la valeur label_suffix du champ spécifique. Le paramètre label_suffix facultatif permet de surcharger tout suffixe défini précédemment. Par exemple, vous pouvez utiliser une chaîne vide pour masquer l’étiquette pour certains champs précis. Si vous avez besoin de le faire dans un gabarit, il est possible d’écrire un filtre personnalisé afin de pouvoir passer des paramètres à label_tag.

Changed in Django 1.8:

L’étiquette contient required_css_class, le cas échéant.

BoundField.css_classes()

Lorsque vous utilisez les raccourcis d’affichage de Django, les classes CSS sont utilisées pour indiquer les champs de formulaire obligatoires ou les champs contenant des erreurs. Si vous affichez manuellement les champs de formulaire, ces classes CSS sont disponibles par la méthode css_classes:

>>> f = ContactForm(data)
>>> f['message'].css_classes()
'required'

Si vous souhaitez fournir des classes supplémentaires en plus des classes liées aux erreurs et aux champs obligatoires, il est possible d’indiquer ces classes en paramètre :

>>> f = ContactForm(data)
>>> f['message'].css_classes('foo bar')
'foo bar required'
BoundField.value()

Utilisez cette méthode pour afficher la valeur brute d’un champ telle qu’elle serait contenue dans un composant Widget:

>>> initial = {'subject': 'welcome'}
>>> unbound_form = ContactForm(initial=initial)
>>> bound_form = ContactForm(data, initial=initial)
>>> print(unbound_form['subject'].value())
welcome
>>> print(bound_form['subject'].value())
hi
BoundField.id_for_label

Utilisez cette propriété pour produire l’identifiant de ce champ. Par exemple, si vous construisez manuellement une balise <label> dans un gabarit (sans tenir compte que label_tag() le ferait très bien à votre place) :

<label for="{{ form.my_field.id_for_label }}">...</label>{{ my_field }}

Par défaut, il s’agira du nom du champ préfixé par id_ (“id_my_field” dans l’exemple ci-dessus). Il est possible de modifier cet identifiant en renseignant attrs pour le composant du champ. Par exemple, en définissant un champ comme ceci :

my_field = forms.CharField(widget=forms.TextInput(attrs={'id': 'myFIELD'}))

et en utilisant le gabarit ci-dessus, le résultat affiché donnera quelque chose comme :

<label for="myFIELD">...</label><input id="myFIELD" type="text" name="my_field" />

Liaison de fichiers téléversés avec un formulaire

Lorsqu’on a affaire à des champs de formulaire de type FileField ou ImageField, les choses se compliquent un peu plus.

Premièrement, pour pouvoir envoyer des fichiers, il est important que la balise <form> du formulaire définisse correctement son attribut enctype à "multipart/form-data":

<form enctype="multipart/form-data" method="post" action="/foo/">

Deuxièmement, au moment d’instancier le formulaire, il faut lier les données de type fichier. Ces données sont traitées de manière distincte des données habituelles de formulaire, ce qui fait que quand un formulaire contient un champ FileField ou ImageField, il doit recevoir un second paramètre au moment de faire la liaison entre le formulaire et les données. Ainsi, si nous étendons notre ContactForm pour qu’il contienne un champ ImageField nommé mugshot, nous devons lier les données de fichier contenant l’image mugshot:

# Bound form with an image field
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
>>> f = ContactFormWithMugshot(data, file_data)

En pratique, vous allez généralement indiquer request.FILES comme source des données de fichier (comme pour request.POST représentant la source des données de formulaire) :

# Bound form with an image field, data from the request
>>> f = ContactFormWithMugshot(request.POST, request.FILES)

La construction d’un formulaire non lié ne change pas, il suffit d’omettre aussi bien les données de formulaire que les données de fichier :

# Unbound form with an image field
>>> f = ContactFormWithMugshot()

Détection des formulaires composites

Form.is_multipart()

Si vous écrivez des vues ou des gabarits réutilisables, il peut arriver que l’on ne sache pas à l’avance si un formulaire est composite. La méthode is_multipart() indique si le formulaire nécessite un codage composite lors de son envoi :

>>> f = ContactFormWithMugshot()
>>> f.is_multipart()
True

Voici un exemple de la façon d’utiliser cette méthode dans un gabarit :

{% if form.is_multipart %}
    <form enctype="multipart/form-data" method="post" action="/foo/">
{% else %}
    <form method="post" action="/foo/">
{% endif %}
{{ form }}
</form>

Formulaires et sous-classes

Si vous avez plusieurs classes Form dont les champs sont partagés, il est possible d’utiliser l’héritage pour éviter la redondance.

Lorsque vous héritez d’une classe Form personnalisée, la sous-classe résultante inclut tous les champs des ses classes parentes, suivis des champs définis dans la sous-classe.

Dans cet exemple, ContactFormWithPriority contient tous les champs de ContactForm plus un champ supplémentaire, priority. Les champs de ContactForm apparaissent en premier :

>>> class ContactFormWithPriority(ContactForm):
...     priority = forms.CharField()
>>> f = ContactFormWithPriority(auto_id=False)
>>> print(f.as_ul())
<li>Subject: <input type="text" name="subject" maxlength="100" /></li>
<li>Message: <input type="text" name="message" /></li>
<li>Sender: <input type="email" name="sender" /></li>
<li>Cc myself: <input type="checkbox" name="cc_myself" /></li>
<li>Priority: <input type="text" name="priority" /></li>

Il est possible d’hériter de plusieurs formulaires, en considérant ces formulaires comme des « mixins ». Dans cet exemple, BeatleForm hérite à la fois de PersonForm et de InstrumentForm (dans cet ordre), et sa liste de champs contient les champs de ses classes parentes :

>>> from django.forms import Form
>>> class PersonForm(Form):
...     first_name = CharField()
...     last_name = CharField()
>>> class InstrumentForm(Form):
...     instrument = CharField()
>>> class BeatleForm(PersonForm, InstrumentForm):
...     haircut_type = CharField()
>>> b = BeatleForm(auto_id=False)
>>> print(b.as_ul())
<li>First name: <input type="text" name="first_name" /></li>
<li>Last name: <input type="text" name="last_name" /></li>
<li>Instrument: <input type="text" name="instrument" /></li>
<li>Haircut type: <input type="text" name="haircut_type" /></li>
New in Django 1.7.

Il est possible d’enlever de manière déclarative un champ Field hérité d’une classe parente en définissant son nom à None dans la sous-classe. Par exemple :

>>> from django import forms

>>> class ParentForm(forms.Form):
...     name = forms.CharField()
...     age = forms.IntegerField()

>>> class ChildForm(ParentForm):
...     name = None

>>> ChildForm().fields.keys()
... ['age']

Préfixes de formulaires

Form.prefix

Une balise <form> peut contenir plusieurs formulaires Django. Afin que chaque formulaire possède son propre espace de noms, utilisez le paramètre nommé prefix:

>>> mother = PersonForm(prefix="mother")
>>> father = PersonForm(prefix="father")
>>> print(mother.as_ul())
<li><label for="id_mother-first_name">First name:</label> <input type="text" name="mother-first_name" id="id_mother-first_name" /></li>
<li><label for="id_mother-last_name">Last name:</label> <input type="text" name="mother-last_name" id="id_mother-last_name" /></li>
>>> print(father.as_ul())
<li><label for="id_father-first_name">First name:</label> <input type="text" name="father-first_name" id="id_father-first_name" /></li>
<li><label for="id_father-last_name">Last name:</label> <input type="text" name="father-last_name" id="id_father-last_name" /></li>
Back to Top