La création de formulaires avec Django est assez simple et pleine de fonctionnalités mais la construction de formulaires parfaitement adaptés peut s’avérer quelque peu délicate !

Vous pouvez générer des formulaires à partir de vos modèles ou bien les créer directement depuis des classes. On peut également utiliser des formulaires générés automatiquement mais avec un style plus élaboré comme Crispy Forms.

Création de formulaires en Django

Création d’une nouvelle app dans notre projet

Passons tout de suite à la pratique, et mettons cela en place. Pour plus de propreté, nous allons créer une nouvelle app dans notre projet, que nous appellerons “myform”.

Reprenons le projet GestionTaches après avoir initialisé votre environnement virtuel et créé l’application myform

./manage.py startapp myform

Comme nous l’avons déjà fait avec lesTaches, ajoutons cette application au fichier settings.py :

INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'django.contrib.admindocs',
'lesTaches',
'myform',
)

Modèles de l’app myform

Créons à présent le modèle de notre application, on ouvre pour cela le fichier models.py contenu dans le dossier de notre app “myform” et on l’édite comme ceci :

from django.contrib import admin
from django.db import models


class Contact(models.Model):
    name = models.CharField(max_length=200)
    firstname = models.CharField(max_length=200)
    email = models.EmailField(max_length=200)
    message = models.CharField(max_length=1000)

On crée donc une classe “Contact” qui contient un nom, un prénom, un email et un message, puis :

# Générons la en base de données:
./manage.py makemigrations myform
# vérifions le contenu de la table avec
./manage.py sqlmigrate myform 0001
# et réalisons la migration
./manage.py migrate

Et voilà, notre modèle est présent en base de données, passons à la création de notre contrôleur, afin de générer notre formulaire. On ouvre pour cela le fichier views.py présent dans notre application, et on l’édite :

from django.shortcuts import render
from django.forms import ModelForm
from myform.models import Contact

class ContactForm(ModelForm):
class Meta:
    model = Contact
    fields = ('name', 'firstname', 'email', 'message')

def contact(request):

contact_form = ContactForm()
return render(request,'contact.html', {'contact_form' : contact_form})

On importe notre modèle, un “helper”, la méthode render(), ainsi que la classe ModelForm. On définit alors notre classe qui va permettre de générer notre formulaire en la faisant dériver de ModelForm et en lui spécifiant le modèle à inclure Contact. Il ne nous reste plus qu’à déclarer une nouvelle variable qui sera une instance de la nouvelle classe créée, et à la passer en tant que donnée à notre template.

Routes pour l’app myform

Maintenant il va falloir créer les urls correspondantes :

Tout d’abord dans GestionTaches ajoutons dans urls.py la ligne suivante :

path('contacts/',include('myform.urls')),

et créons le fichier urls.py dans le répertoire myform :

from django.urls import path
from . import  views
urlpatterns=[
    path('',views.contact,name='contact'),
]

Créons maintenant notre template qui se nommera contact.html dans myform/templates/ :

{{ contact_form.as_p }}

Voilà ! Pas plus !

Nous affichons ici directement notre objet contact_form et on lui associe l’option d’affichage as_p qui signifie que le formulaire sera affiché en utilisant la balise <p>.

Test du premier formulaire

Nous pouvons à présent apprécier le résultat ! Lancez votre serveur :

./manage.py runserver

Et dirigez-vous à l’adresse : http://localhost:8000/contacts/ Vous devriez obtenir un formulaire (moche) avec les libellés de la classe Contact.

Notre formulaire html, utilisant des balises “<p>”, a été automatiquement généré ! Pour l’instant il reste encore assez rudimentaire, mais nous allons voir que nous allons pouvoir très vite tout personnaliser et avoir un bien meilleur rendu ! D’autres options d’affichage telles que as_ul et as_table auraient pu être utilisées.

Pour plus d’informations sur les forms, voir la doc officielle: https://docs.djangoproject.com/fr/3.1/topics/forms/

Création d’un formulaire à la main (sans Model)

Mais avant cela, nous allons nous intéresser à la création de formulaires sans utiliser de modèle, directement avec le module forms de Django ! Pour ceux qui souhaitent approfondir la création de formulaires à partir de modèles, voir la documentation officielle. Créons maintenant notre formulaire ContactForm2 sans utiliser de ModelForm, en modifiant comme suit le views.py de l’application “myform” :

from django.shortcuts import render
from django.forms import ModelForm
from models import Contact

# Avec ModelForm
class ContactForm(ModelForm):
    class Meta:
        model = Contact
        fields = ('name', 'firstname', 'email', 'message')

from django import forms

# Sans ModelForm
class ContactForm2(forms.Form):
    name = forms.CharField(max_length=200)
    firstname = forms.CharField(max_length=200)
    email = forms.EmailField(max_length=200)
    message = forms.CharField(max_length=1000)

def contact(request):
    contact_form = ContactForm()
    contact_form2 = ContactForm2()
    return render(request,'contact.html', {'myform/contact_form' : contact_form, 'contact_form2' : contact_form2})

On inclut directement le module “forms” de django et on crée une classe ContactForm2 dérivant de Form et non de ModelForm cette fois-ci. On crée ainsi notre classe, ce qui ressemble sensiblement à la déclaration de notre modèle. Puis on crée un objet contact_form2 dans notre fonction et on le passe en paramètre à notre template. Maintenant éditez votre fichier contact.html de cette façon :

{{contact_form.as_p  }}
<br/>
{{ contact_form2.as_p }}

On obtient donc deux formulaires identiques mais créés de deux façons différentes !

Personnalisation des formulaires et vérifications

Intéressons-nous maintenant à la personnalisation de ces formulaires avant de comprendre comment les utiliser et gérer leur validation. Les formulaires de type ModelForm ainsi que ceux de type Form peuvent contenir de nombreuses options de personnalisation.

On peut tout de suite remarquer certaines options telles querequired, label, initial, widget, help_text, error_messages, validator, localize ou

required. Ce dernier va spécifier si le champ du formulaire peut être ignoré ou non, par défaut tous les champs sont requis.

Cela implique que si l’utilisateur valide le formulaire avec des champs à vide ( » » ou None), une erreur sera levée. Pour illustrer cela, voici ce que donnent les tests suivants après avoir ouvert une console python en mode django et avoir importé le module forms :

Ouvrons un shell et testons:

>>>from django inport forms
>>> f=forms.CharField()
>>> f.clean('foo')
'foo'
>>> f.clean('')
''
ValidationError: ['This field is required.'] ...
>>> f = forms.CharField(required=False)
>>> f.clean(None)
''
ValidationError: ['This field is required.'] ...

Nous simulons ici la vérification de formulaire avec la méthode clean(). Comme vous pouvez le constater, une erreur est bien levée si nous passons une chaîne vide ou None à notre champ. label va permettre de réécrire la façon dont le label du champ est affiché, par défaut, cela prend le nom de la variable et lui ajoute une majuscule à la première lettre et remplace tous les underscores par des espaces.

Exemple :

>>>class CommentForm(forms.Form):
...     name = forms.CharField(label='Your name')
...     url = forms.URLField(label='Your Web site', required=False)
...     comment = forms.CharField()
...
>>> f = CommentForm()
>>> print(f)
<tr><th>
<label for="id_name">Your name:</label>
</th>
<td><input type="text" name="name" id="id\_name" />
</td></tr>
<tr><th>
<label for="id_url">Your Web site:</label>
</th><td>
<input type="text" name="url" id="id_url" /></td></tr>
<tr><th><label for="id_comment">Comment:</label>
</th><td>
<input type="text" name="comment" id=">>>
f = forms.CharField() /></td></tr>

Vous voyez également qu’ici on affiche notre form sans utiliser d’option d’affichage, il est donc affiché par défaut en utilisant une table. Pour plus de commodité, on aurait pu rajouter l’option auto_id à False, ce qui aurait pour effet de désactiver la création d’une balise label, et génèrerait ceci :

>>>f = CommentForm(auto_id=False)
>>> print(f)
<tr><th>Your name:</th>
<td><input type="text" name="name" /></td></tr>
<tr><th>Your Web site:</th><td>
<input type="text" name="url" />
</td></tr>
<tr><th>Comment:</th><td>
<input type="text" name="comment" />
</td></tr>

initial va permettre de donner des valeurs par défaut à vos champs, exemple :

>>>class CommentForm(forms.Form):
...     name = forms.CharField(initial='Your name')
...     url = forms.URLField(initial='http://')
...     comment = forms.CharField()
>>> f = CommentForm(auto_id=False)
>>> print f
<tr><th>Name:</th><td>
<input type="text" name="name" value="Your name" />
</td></tr>
<tr><th>Url:</th><td>
<input type="text" name="url" value="http://" /></td></tr>
<tr><th>Comment:</th><td>
<input type="text" name="comment" />
</td></tr>

Nous nous arrêterons à ces trois options pour le moment et verrons les autres dans des cas plus poussés. Par contre il est très intéressant de se pencher sur l’option widget car elle va vous permettre de réaliser très facilement des champs spécifiques :

django import forms
class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField(widget=forms.Textarea)

Ou bien :

from django.forms.fields import DateField, ChoiceField, MultipleChoiceField
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
from django.forms.extras.widgets import SelectDateWidget

BIRTH_YEAR_CHOICES = ('1999', '2000', '2001')
GENDER_CHOICES = (('m', 'Male'), ('f', 'Female'))
FAVORITE_COLORS_CHOICES = (('blue', 'Blue'),
                        ('green', 'Green'),
                        ('black', 'Black'))

class SimpleForm(forms.Form):
    birth_year = DateField(widget=SelectDateWidget(years=BIRTH_YEAR_CHOICES))
    gender = ChoiceField(widget=RadioSelect, choices=GENDER_CHOICES)
f   avorite_colors = forms.MultipleChoiceField(required=False,
    widget=CheckboxSelectMultiple, choices=FAVORITE_COLORS_CHOICES)

Un petit dernier …

       class CommentForm(forms.Form):
           name = forms.CharField(
                       widget=forms.TextInput(attrs={'class':'special'}))
           url = forms.URLField()
           comment = forms.CharField(
                       widget=forms.TextInput(attrs={'size':'40'}))


Ce qui donnera :
f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td>
<input type="text" name="name" class="special"/></td></tr>
<tr><th>Url:</th><td>
<input type="text" name="url"/></td></tr>
<tr><th>Comment:</th><td>
<input type="text" name="comment" size="40"/></td></tr>

N’hésitez pas à parcourir les différents types de widgets dans la doc de django et à les essayer pour découvrir ce qu’ils produisent.

ModelForms et templates

ModelForms

A noter que vous pouvez également utiliser les widgets avec des Form venant de Modèles :

from django.forms import ModelForm, Textarea

class AuthorForm(ModelForm):
class Meta:
    model = Author
    fields = ('name', 'title', 'birth_date')
    widgets = {
        'name': Textarea(attrs={'cols': 80, 'rows': 20}),
    }

Modifier les formulaires de l’application myform en utilisant les informations ci-dessus. Tester par exemple en modifiant la vue de myform comme ci-dessous :

from django.shortcuts import render
from django.forms import ModelForm, Textarea
from myform.models import Contact
from django import forms

class ContactForm(ModelForm):

class Meta:
    model = Contact
    fields =('name,'firstaname,'email','message')
        widgets = {

        'message': Textarea(attrs={'cols':60,'rows':10}),

    }

class ContactForm2(forms.Form):
    name = forms.CharField(max_length=200,initial="votre nom",label="nom")
    firstname = forms.CharField(max_length=200,initial="votre prenom",label="prenom")
    email = forms.EmailField(max_length=200,label="mel")
    message = forms.CharField(widget=forms.Textarea(attrs={'cols':60,'rows':10}))


def contact(request):

    contact_form = ContactForm()
    #return render(request,'myform/conctact.html',{'contact_form':contact_form})

    contact_form2 = ContactForm2()
    return render(request,'myform/conctact.html',{'contact_form':contact_form,'contact_form2':contact_form2})

Essayez d’autres possibilités. Enfin, nous allons nous intéresser à la validation des formulaires, comment gérer des messages pour la page suivante et comment travailler avec ceux-ci. Nous allons reprendre le fichier views.py de notre application “myform” et remplacer son contenu par celui-ci :

from django.shortcuts import render, redirect
from django.forms import ModelForm, Textarea
from myform.models import Contact
from django import forms
from django.urls import reverse
from django.http import HttpResponse
from django.contrib import messages

class ContactForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(ContactForm, self).__init__(*args, **kwargs)
        self.fields['name'].label = "Nom "
        self.fields['firstname'].label = "Prenom"
        self.fields['email'].label = "mél"
    class Meta:
        model = Contact
        fields = ('name', 'firstname', 'email','message')
        widgets = {'message': Textarea(attrs={'cols': 60, 'rows': 10}),}

    def contact(request):
    # on instancie un formulaire
    form = ContactForm()
    # on teste si on est bien en validation de formulaire (POST)
    if request.method == "POST":
        # Si oui on récupère les données postées
        form = ContactForm(request.POST)
        # on vérifie la validité du formulaire
        if form.is_valid():
            new_contact = form.save()
            # on prépare un nouveau message
            messages.success(request,'Nouveau contact '+new_contact.name+' '+new_contact.email)
            #return redirect(reverse('detail', args=[new_contact.pk] ))
            context = {'pers': new_contact}
            return render(request,'detail.html', context)
    # Si méthode GET, on présente le formulaire
    context = {'form': form}
    return render(request,'contact.html', context)

Avec une nouvelle vue detail :

def detail(request, cid):
    contact = Contact.objects.get(pk=cid)
    context = {'pers': contact}
    return render(request,'detail.html', context)

et un template detail.html :

{% extends "base.html" %}
    {% load static %}
    {% block title %}
    Contacts
    {% endblock %}
    {% block header %}
    {% if messages %}
        {% for message in messages %}
            <h3>{{message}}</h3>
        {% endfor %}
    {% endif %}
    {% endblock %}

    {% block content %}
    <ul>
        <li>{{pers.firstname}}</li>
        <li>{{pers.email}}</li>
        <li>{{pers.message}}</li>
    </ul>
    {% endblock %}

Et nous allons définir une nouvelle url pour la page « detail ».

Pour ce faire on modifiera le fichier urls.py de myform. Pour tester, il nous faudra également modifier le template contact.html :

{% extends "base.html" %}
    {% load static %}
    {% block title %}
    Nouveau Contact
    {% endblock %}

    {% block navtitle %}
        Nouveau Contact
    {% endblock %}

    {% block content%}
        <form action="/contacts/" method="POST">
        {% csrf_token %}
        {{ form }}
        <button type="submit">
            Sauvegarder
        </button>
        </form>
    {% endblock %}

CrispyForms

Pour bénéficier de formulaires plus jolis, nous allons installer et mettre en marche le module crispy-forms. Pour cela ajoutez le à votre venv :

pip install django-crispy-forms

puis ajoutez crispy-forms aux apps installées dans settings.py :

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'lesTaches',
    'myform',
    'crispy_forms',
]

Puis lisez la documentation des crispy-forms pour adapter vos formulaires et les rendre plus attrayants ! Pour contact.html par exemple :

{% extends "base.html" %}
    {% load static %}
    {% load crispy_forms_tags %}
    {% block title %}
    Nouveau Contact
    {% endblock %}

    {% block navtitle %}
        Nouveau Contact
    {% endblock %}

    {% block content%}
        <form action="/contacts/" method="POST">
        {% csrf_token %}
        {{ form | crispy }}
        <button type="submit" class="btn btn-success">
            Sauvegarder
        </button>
        </form>
    {% endblock %}

On peut aussi personnaliser davantage les différents widgets des formulaires avec des attributs spécialisés en utilisant la librairie widget-tweaks

App déployée sur Gitpod

Le code correspondant se trouve sur Github

Le projet est disponible, prêt à être utilisé et complété sur gitpod :

Gitpod Ready-to-Code