Blog ENI : Toute la veille numérique !
-25€ dès 75€ sur les livres en ligne, vidéos... avec le code FUSEE25. J'en profite !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Django
  3. Les formulaires Django
Extrait - Django Industrialisez vos développements Python
Extraits du livre
Django Industrialisez vos développements Python Revenir à la page d'achat du livre

Les formulaires Django

Introduction

Dans ce chapitre, nous allons voir comment construire et utiliser efficacement des formulaires avec Django.

Gestion manuelle

Dans cette section, nous allons construire un formulaire de manière classique ; d’un côté le code HTML et d’un autre le code Django. Pourquoi faire cela, alors que Django propose des outils plus puissants ? Simplement d’une part pour savoir le faire, par exemple si le formulaire n’est pas issu de Django mais d’un site tiers avec une redirection et d’autre part pour avoir en tête les mécanismes de base qui sont utilisés par Django dans la suite de ce chapitre car leurs détails d’implémentation sont cachés.

Tout d’abord, créez un template HTML gérant le formulaire : fichier formulaire.html.


{% if message %}  
    <h2>{{ message }}</h2>  
{% endif %}  
<form action="" method="POST">  
    {% csrf_token %}  
    <input name="entree" value="{{ entree}}">  
    <input type="submit" name="go.">  
</form>  
  
{% if value %}  
     <h2>{{ entree }} élevé au carré donne : {{ value }}</h2>  
 {% endif %} 
 

Puis créez une vue pour traiter le formulaire et installez-la dans les URL....

Utilisation des formulaires Django

Dans cette section, nous allons voir la classe Django Form. Cette classe ne règle pas tous les problèmes évoqués précédemment, mais elle prend en charge nombre d’entre eux :

1. Générer le formulaire avec les bons types de champs.

2. Contrôler le formulaire en Python et/ou avec les templates Django, pas en HTML.

3. Vérifier les données.

4. Gérer la conversion des données en Python.

5. Fournir des outils pour gérer certains aspects du workflow.

Avant de détailler tout cela, reprenons l’exemple précédent, mais à l’aide de Django Forms.

Fichier template :


<form action="" method="post">  
    {% csrf_token %}  
    {{ form }}  
    <input type="submit" value="go." />  
</form>  
  
{% if value %}  
    <h2>{{ entree }} élevé au carré donne : {{ value }}</h2>  
{% endif %} 
 

Fichier forms.py :


from django import forms  
  
class MonForm(forms.Form):   
    entree = forms.IntegerField(label='Entrée') 
 

Fichier views.py :


from django.shortcuts import render  
from django.http import HttpResponseRedirect  
from . import MonForm  
 
def formulaire_form(request):  
    value = None  
   if request.method == 'POST':  # On récupère les données du 
formulaire 
       # créer un formulaire (instance de Form) et l'initialiser  
avec les données de la requête. 
        form = MonForm(request.POST)  
        if form.is_valid():  # Vérifier la validité des données.  
            # Traiter les données  
            entree = form.cleaned_data['entree']  
            value =entree * entree  
            # Afficher le résultat  
            # return HttpResponseRedirect('/resultats/')...

Les types de champs d’un formulaire

Tous les types de champs de formulaires dérivent de la classe de base class Field(**kwargs).

1. Les arguments et les attributs de Field

Un Field (type de champ) s’initialise avec des arguments nommés. La plupart de ces arguments correspondent aux attributs des objets Field, attributs que l’on pourra utiliser et manipuler dans le code et dans les templates.

Field.label

Cet attribut est un label, qui est un texte qui sera affiché dans la page HTML devant la zone de saisie du champ pour que l’utilisateur sache de quoi il s’agit.

Si vous ne donnez pas de valeur à cet attribut, Django fabriquera un label automatiquement à partir du nom du champ en remplaçant les underscores par des espaces et en mettant en majuscule la première lettre du nom.

Exemple :


from django import forms  
class MonFormulaire(forms.Form):  
    nom = forms.CharField(label='Nom')  
    autre_champ = forms.CharField() 
 

donnera :


<li><label for="id_nom">Nom:</label> <input id="id_nom" name="nom"  
type="text" /></li>  
<li><label for="id_autre_champ">Autre champ:</label> <input  
id="id_autre_champ" name="id_autre_champ" type="text" /></li>
 

Field.initial

Cet attribut permet de définir la valeur initiale que vous souhaitez donner au champ, lorsque l’on démarre avec un formulaire vide. On peut lui passer en paramètre une valeur ou une fonction (Python callable/appelable).

Si vous passez une fonction, l’évaluation de la fonction aura lieu à l’affichage du formulaire et non pas lors de sa création, comme c’est le cas pour une valeur.

Il ne faut pas confondre cet attribut avec la fourniture de données à l’initialisation du formulaire. Les deux approches ci-dessous ne font pas la même chose :


valeur_initiale = 'Saisissez un entier'  
class MonFormulaire(forms.Form):  
    mon_champ = forms.IntegerField()  
  
data = { 'champ' : valeur_initiale, }  
form = MonFormulaire(data) 
 
images/11RI03.png

et :


valeur_initiale = 'Saisissez un entier'  
class MonFormulaire(forms.Form):  
    mon_champ = forms.IntegerField(initial=...

Créer un formulaire à partir d’un modèle

Vous aurez sûrement remarqué que les champs des formulaires ressemblent beaucoup aux champs des modèles de l’ORM, et ce n’est pas un hasard.

Les formulaires peuvent avoir deux objectifs : saisir des informations diverses (comme pour l’exemple de la newsletter), informations qui ne sont pas « connectées » avec un modèle ou au contraire effectuer la saisie d’informations pour renseigner (modifier ou créer) un modèle. Dans ce dernier cas, on va se retrouver à redéfinir à peu près les mêmes champs dans le formulaire que dans le modèle. Django propose évidemment une solution qui permet de créer directement un formulaire à partir d’un modèle. Django crée le formulaire avec un champ approprié et de même nom que chaque champ du modèle. En plus, Django permet de choisir les champs du modèle qui seront présents dans le formulaire.

Exemple :


from django.forms import ModelForm  
from simpleblog.models import Article  
  
# Création du formulaire.  
class ArticleForm(ModelForm):  
     class Meta:  
         model = Article  
         fields = ['date_de_publication', 'titre', 'texte',]  
  
# Créer un formulaire « vide » pour, par exemple, ajouter un article.  
form = ArticleForm()  # Unbound Form  
# Créer un formulaire rempli avec les données d'un article, pour   
# par exemple modifier un article.  
article = Article.objects.get(pk=1)  
form = ArticleForm(instance=article)  # Bound Form  

Pour définir un formulaire à partir d’un modèle c’est simple : créez une classe dérivée de ModelForm et paramétrez-la à l’aide de sa métaclasse.

1. Équivalence entre les types de champs de formulaires  et ceux du modèle

En fonction du type de champ de votre modèle, Django va utiliser un champ de formulaire. Voici une liste sommaire des équivalences :

Champ du modèle

Champ d’un formulaire

Widget

AutoField

Non représenté

N/A

BigIntegerField...

CSS et JavaScript dans des formulaires : la classe Media

Aujourd’hui, en matière de développement web professionnel, le niveau d’exigence attendu du rendu d’un formulaire HTML est élevé. Il est difficile d’atteindre ce niveau uniquement en HTML. Vous allez donc probablement devoir ajouter du code JavaScript et des feuilles de style CSS3 à vos formulaires. Nous allons voir comment associer aux formulaires et/ou aux widgets utilisés des scripts JavaScript et des feuilles de style.

Pour effectuer des recherches sur ce sujet en anglais, ou consulter la documentation Django, il est fait référence pour ce sujet à la notion d’« assets » concernant les fichiers JavaScript et CSS3. La traduction française « commune » d’assets est « actifs », au sens des actifs financiers. Dans la suite de chapitre, nous préférerons parler de ressources.

La méthode proposée par Django est indifférente aux librairies ou toolkits JavaScript que vous utilisez, vous pourrez donc utiliser celui qui vous convient le mieux, comme jQuery par exemple.

Cependant, l’interface d’administration fournit déjà un certain nombre de widgets avec leurs ressources, par exemple pour les dates et heures ou les relations n-m. Widgets que vous pouvez utiliser dans votre code sous réserve d’inclure les ressources JavaScript et CSS de l’application django.contrib.admin.

1. Définir des ressources associées à un Widget

Les ressources peuvent être définies de deux manières : soit de manière statique en donnant des valeurs à des attributs de la classe interne Media, soit dynamiquement par programmation.

Définition statique par la classe interne Media

Pour cela, surclassez un widget existant et donnez-lui une classe Media interne avec les attributs spécifiques : css, js et extend.

Exemple :


from django import forms  
  
class JoliTexte(forms.TextInput):  
    class Media:  
        css = {  
            'all': ('joli.css',)  
        }  
        js = ('animations.js',) 
 

Ce code fabrique un widget permettant de saisir un texte dans...

Les ensembles de formulaires (formsets)

Les formsets Django sont une couche abstraite (la classe BaseFormSet) qui permet de gérer un regroupement de formulaires identiques mais contenant des données différentes affichées dans la même page.

Imaginons que l’on souhaite créer plusieurs articles d’un seul coup (comme dans l’Admin), on va créer un formset et ainsi pouvoir gérer le groupe de formulaires de saisie/modification.


from django.forms.formsets import formset_factory  
ArticleFormSet = formset_factory(ArticleForm)
 

Une instance formset est itérable et chaque itération renvoie un formulaire.


formset = ArticleFormSet()  
print(len(formset))  
for form in formset:  
    print(type(form))  
  
1  
<ArticleForm Instance> 
 

Il existe de nombreuses analogies entre les modèles inline de l’Admin et les formset. C’est normal car l’Admin utilise les formset. Lorsque l’on crée un formset sans donnée, il ne contient qu’un seul formulaire vide dédié à la création (comme pour l’Admin, le paramètre extra contrôle le nombre de formulaires additionnels).


ArticleFormSet = formset_factory(ArticleForm, extra=2)
 

Si on affiche les formulaires :

print(len(formset)) 
for form in formset: 
    print(form.as_table())
 

...


2  
<tr><th><label for="id_form-0- 
titre">Titre:</label></th><td><input type="text" name="form-0- 
titre" id="id_form-0-titre" /></td></tr>  
<tr><th><label for="id_form-0- 
date_de_publication">date_de_publication:</label></th><td><input  
type="text" name="form-0-date_de_publication" id="id_form-0- 
date_de_publication" /></td></tr>  
Etc.  
  
<tr><th><label for="id_form-1- 
titre">Titre:</label></th><td><input type="text" name="form-1- 
titre" id="id_form-1-titre" /></td></tr>  
...  
 

On peut remarquer au passage que les noms des champs en HTML ont été modifiés, le champ titre devient form-<N°>-titre. D’une manière...

Les vues génériques basées sur des classes   et les formulaires

Nous avons étudié les vues génériques au chapitre Les requêtes HTTP, les URL et les vues. Depuis la version 1.3 de Django, elles sont basées sur des classes. Ces classes sont regroupées par fonctionnalités. Pour traiter les formulaires, on trouve dans django.views.generic.edit les vues suivantes : FormView, CreateView, UpdateView et DeleteView.

Hiérarchie partielle des vues génériques relatives aux formulaires :

images/11RI08b.PNG

Pour avoir des explications sur le principe des vues basées sur des classes, reportez-vous au chapitre Les requêtes HTTP, les URL et les vues.

Lors du traitement d’un formulaire, nous allons devoir réaliser les étapes suivantes :

  • Traiter la requête GET initiale et fabriquer un formulaire, avec ou sans données initiales.

  • Traiter la requête POST et, en cas d’erreur, réaffirmer le formulaire.

  • Traiter la requête POST correcte, traiter les données et les enregistrer si nécessaire puis rediriger la réponse.

On peut représenter cela avec une vue basée sur une fonction par le pseudo-code suivant :


form .forms import MonFormulaire  
  
def ma_vue_avec_mon_formulaire(request,*args, **kwargs):  
    if request.method == "POST":  
        # Créer un formulaire à partir des données du POST  
        forms = MonFormulaire(request.POST)  
        if forms.is_valid():  # La saisie est-elle correcte ?  
            # Réaliser des traitements et sauver si nécessaire  
            # ? # formset.save()  
            # Protéger des doubles saisies dues à l'utilisation de « back » 
            return HttpResponseRedirect( tout_va_bien )  
    else:  # C'est une requête GET (ou HEAD ou ?)  
        if on_est_en_edition:  
            data = initialiser(*args, **kwargs)  
            form...

La gestion des fichiers

Dans n’importe quel formulaire HTML traité par Django, les fichiers sont placés dans la variable request.FILES, sous réserve bien sûr que vous n’ayez pas oublié d’ajouter le type d’encryption à votre formulaire HTML (enctype="multipart/form-data").

Commençons par un exemple :


from django import forms  
  
class FormulaireAvecFichier(forms.Form):  
   fichier = forms.FileField() 
 

Le traitement dans la vue devrait ressembler à cela :


from django.http import HttpResponseRedirect  
from django.shortcuts import render_to_response  
from .forms import FormulaireAvecFichier  
  
def traiter_le_fichier(f):  
    with open(f.name, 'wb+') as fichier:  
        for chunk in f.chunks():  
            fichier.write(chunk)  
  
def recuperer_le_fichier(request):  
    if request.method == 'POST':  
        form = FormulaireAvecFichier(request.POST, request.FILES)  
        if form.is_valid():  
            traiter_le_fichier(request.FILES['fichier'])  
            return HttpResponseRedirect('/fichier_ok/')  
    else:  
       ...

Formulaires de type software wizard (assistant logiciel)

Exemple d’écran d’assistant logiciel :

images/11RI08c.PNG

En informatique, un software wizard (de l’anglais wizard, signifiant « sorcier » ou « magicien »), parfois appelé setup assistant ou assistant logiciel en français, est un programme qui permet d’automatiser certaines tâches, comme l’installation ou le paramétrage, à l’aide d’une série de boîtes de dialogues guidant l’utilisateur à travers les étapes.

Django contient une application gérant des "form wizard" qui va permettre de découper une suite de formulaires sur plusieurs pages tout en maintenant la consistance des données à chaque étape, et ce jusqu’à l’étape finale, dans laquelle l’ensemble des données saisies à toutes les étapes sera retourné au programmeur.

Vous pouvez utiliser cette solution si vous avez un très gros formulaire, pour le découper en plusieurs formulaires plus petits et plus cohérents.

1. Principe de fonctionnement

On commence par la première page.

Première étape

La page est affichée, l’utilisateur la remplit.

Le serveur vérifie les données. Si elles sont invalides, la page est réaffirmée avec les erreurs, sinon le serveur sauvegarde les données saisies et passe à l’étape suivante.

Étape suivante

S’il reste des pages à afficher, on recommence à la première étape.

Si on est arrivé à la dernière page de l’assistant, toutes les données ont été soumises et tous les formulaires validés. L’assistant termine lors de son processus en traitant les données puis en vous rendant la main dans la méthode done() du wizard, méthode que vous aurez écrite.

2. Utilisation en cinq étapes

La classe WizardView est une vue basée sur les classes. Pour plus d’informations sur les vues basées sur les classes, consultez le chapitre Les requêtes HTTP, les URL et les vues.

1 - Créez autant de formulaires que d’étapes que vous souhaitez gérer.

2 - Créez une classe MonWizardView dérivée...

La localisation des formulaires

1. La traduction

Pour traduire un formulaire il faut comme pour toutes les traductions Django faire appel au module django.utils.translation. Mais contrairement à la plupart des utilisations où l’on utilise directement ugettext() pour la traduction et du fait du fonctionnement particulier des formulaires, il faut utiliser la fonction ugettext_lazy().

ugettext_lazy() fonctionne en deux étapes : une première qui va marquer les champs pour la traduction mais qui ne réalisera pas la traduction, puis dans une deuxième étape, la traduction sera réalisée à l’affichage du formulaire, c’est-à-dire au moment où on connaît la langue d’affichage. Tous les champs texte sont traduisibles ainsi.

Exemple :


from django.utils.translation import ugettext_lazy as _   
  
class AuteurForm(ModelForm):  
    class Meta:  
        model = Auteur  
        fields = ('nom', 'prenom', 'date_de_naissance')  
        labels = {  
            'name': _('Auteur'),  
        }  
        help_texts = {  
            'name': _('Saisissez votre nom de famille.')...