Envoyez des données du navigateur au serveur avec un formulaire
Dans le précédent chapitre, nous avons abordé le « R » de notre interface CRUD : Read ou lire les données. C'était une bonne opération pour commencer, car la lecture des données est l'opération la plus simple de toutes. Nous travaillons uniquement avec des données qui existent déjà dans la base de données. C'est pourquoi on parle parfois d'une opération en lecture seule.
Les autres opérations : Créer, Mettre à jour, Supprimer, sont des opérations d'écriture, car elles modifient toutes d'une manière ou d'une autre les données existantes.
Si nous voulons que nos utilisateurs puissent créer un nouvel objet Band
, ils ont besoin d'une interface dans le navigateur qui peut envoyer un nom, un genre et d'autres champs Band
au serveur.
C'est là que les formulaires entrent en jeu. Les formulaires permettent d'envoyer des données du front-end au back-end. Nous les utiliserons pour toutes les opérations d'écriture.
Les formulaires vont nous présenter quelques nouveaux concepts, donc dans ce chapitre nous allons faire un détour par rapport à notre interface CRUD pour nous concentrer sur les formulaires, et comment les utiliser dans Django, afin d'être bien préparé pour les chapitres suivants.
Nous allons donc apprendre à créer un formulaire de « Contact » qui permet aux utilisateurs d'envoyer un message aux administrateurs de l'application.
Définissez un formulaire dans Django
Voici à quoi va ressembler notre formulaire de « Contact » :
Nous commençons par définir le formulaire comme une classe. Créez le fichier « listings/forms.py » et ajoutez ce code :
# listings/forms.py
from django import forms
class ContactUsForm(forms.Form):
name = forms.CharField(required=False)
email = forms.EmailField()
message = forms.CharField(max_length=1000)
Nous avons défini trois champs de formulaire dans notre ContactUsForm
. Les champs de formulaire sont similaires aux champs de modèle : nous avons différents types de champs pour différents types de données. Nous pouvons également préciser quand les champs doivent être facultatifs avec required=False
, ici, nous permettons à l'utilisateur de rester anonyme en rendant le champ name
facultatif. Et nous pouvons définir max_length
, tout comme nous le faisons dans un modèle.
Ensuite, utilisons notre ContactUsForm
dans la vue contact
que nous avons construite dans la Partie 2 :
# listings/views.py
…
from listings.forms import ContactUsForm
…
def contact(request):
form = ContactUsForm() # ajout d’un nouveau formulaire ici
return render(request,
'listings/contact.html',
{'form': form}) # passe ce formulaire au gabarit
Et maintenant nous éditons notre gabarit « contact.html » :
# listings/templates/listings/contact.html
…
<p>Nous sommes là pour vous aider.</p>
<form action="" method="post" novalidate>
{% csrf_token %}
{{ form }}
<input type="submit" value="Envoyer">
</form>
…
Ajoutons aussi cette page à notre urlpatterns
en mettant à jour merchex/urls.py.
urlpatterns = [
...
path('contact-us/', views.contact, name='contact'),
]
Jetons un coup d'œil à cela dans le navigateur avant de décomposer ce que nous venons de faire dans le gabarit :
Dans notre gabarit, nous avons ajouté une balise <form>
avec un <input>
de type="submit"
à l'intérieur. C'est la norme pour tout formulaire HTML que vous pouvez créer. Mais maintenant les choses deviennent intéressantes.
Au lieu de taper manuellement les balises <input>
pour chacun des champs de notre formulaire, nous tapons simplement {{ form }}
. Nous ajoutons également {% csrf_token % }
que nous aborderons dans le résumé de la Partie 4.
Pour comprendre ce qui se passe ici, examinons le HTML généré dans les outils de développement de notre navigateur :
Nous pouvons voir que Django a automatiquement généré un <label>
et un <input>
pour chacun de nos champs de formulaire : name
, email
et message
. C'est génial : cela signifie que chaque fois que nous voulons ajouter un nouveau champ à ce formulaire, nous l'ajoutons simplement à la classe ContactUsForm
, et Django s'occupera du HTML pour nous.
Une petite modification s'impose : les champs seraient plus beaux s'ils étaient affichés en pile plutôt que de gauche à droite :
# listings/templates/listings/contact.html
...
{{ form.as_p }}
...
Cela encadre chaque paire balise de champ dans une balise <p>
, les empilant ainsi :
Regardez à nouveau la balise <form>
que nous avons utilisée :
# listings/templates/listings/contact.html
...
<form action="" method="post" novalidate>
...
L'attribut
action
désigne « l'URL où nous allons envoyer les données du formulaire ». Si vous donnez à cet attribut la valeur d'une chaîne vide, ou si vous l'omettez complètement, il renverra à l'URL de la page où nous nous trouvons déjà, c'est-à-dire "http://127.0.0.1:8000/contact-us/". Cela signifie que nous allons gérer les données du formulaire dans notre vuecontact
.La valeur de
method
estpost
: ce qui signifie que les données seront envoyées comme une requête HTTP POST. C'est un peu différent des requêtes GET que nous avons utilisées jusqu'à présent, car en plus d'une « méthode » et d'un « chemin », elle comprend également un « corps » de requête qui contient les données du formulaire.novalidate
désactive la validation de formulaire de votre navigateur. Il s'agit d'une fonctionnalité utile de la plupart des navigateurs, et nous la réactiverons plus tard, mais nous devons d'abord vérifier que notre formulaire fonctionne correctement sans elle.
Découvrez toutes les étapes permettant d'inclure des formulaires dans vos pages Django en suivant le screencast.
Maintenant que nous savons que les données de notre formulaire arriveront au serveur sous forme de requête POST, voyons comment gérer les requêtes POST dans notre vue.
Gérez les requêtes POST dans une vue Django
Pour avoir un aperçu plus clair du fonctionnement des données POST dans une vue, nous allons utiliser un peu de journalisation dans le terminal, avec des instructions print
.
…
def contact(request):
# ajoutez ces instructions d'impression afin que nous puissions jeter un coup d'oeil à « request.method » et à « request.POST »
print('La méthode de requête est : ', request.method)
print('Les données POST sont : ', request.POST)
form = ContactUsForm()
…
Tout d'abord, appelons cette vue comme une requête GET. Le moyen le plus sûr de le faire est de cliquer dans la barre d'adresse du navigateur et d'appuyer sur Entrée . (Cliquer sur « recharger » peut provoquer un POST si vous le faites immédiatement après avoir soumis le formulaire) Maintenant, regardez dans le terminal :
La méthode de requête est : GET
Les données POST sont : <QueryDict: {}>
Nous pouvons voir que pendant une requête GET, request.POST
est un QueryDict
vide (qui est un type spécial de dict
Python).
Maintenant, demandons la même vue en tant que requête POST. Pour ce faire, nous saisissons des données dans les champs du formulaire, puis nous cliquons sur « Envoyer » :
La méthode de requête est : POST
Les données POST sont : <QueryDict: {'csrfmiddlewaretoken': ['OUsGBpHaOq8L6t9KiICU6a84HCTOfRdlEhzHDKg1OonioK5BbvItiiHVMzScJyv9'], 'name': ['Patrick'], 'email': ['patrick@exemple.com'], 'message': ['J'adore ce site !’]}>
Cette fois, nous pouvons voir que notre QueryDict
contient les données de notre formulaire ! (Y compris le mystérieux jeton CSRF, que nous expliquerons bientôt).
Ensuite, nous devons d'une manière ou d'une autre gérer les deux scénarios de demande dans notre vue :
S'il s'agit d'une requête GET, nous devons afficher un formulaire vide à l'utilisateur.
S'il s'agit d'une demande POST, nous devons examiner les données et voir si elles sont valides. (Nous reviendrons sur l'envoi effectif de l'e-mail bien assez tôt).
Nous avons donc besoin d'une instruction if
:
def contact(request):
# ...nous pouvons supprimer les déclarations de journalisation qui étaient ici...
if request.method == 'POST':
# créer une instance de notre formulaire et le remplir avec les données POST
form = ContactUsForm(request.POST)
else:
# ceci doit être une requête GET, donc créer un formulaire vide
form = ContactUsForm()
return render(request,
'listings/contact.html',
{'form': form})
Dans les deux branches de l'instruction if
, nous créons un formulaire qui est transmis au modèle, mais dans le cas d'une requête POST, nous remplissons également le formulaire avec les données POST.
Maintenant, lorsque nous soumettons un formulaire rempli, les données sont toujours visibles dans le navigateur lorsque la page est rechargée. Mais ce n'est pas tout : si nous soumettons des données non valides, le formulaire affichera des messages d'erreur :
Ce que nous voyons ici est une validation côté serveur : notre formulaire Django a validé les champs par rapport à nos règles, a généré des messages d'erreur en cas de problème, puis les a retournés dans le modèle comme faisant partie du formulaire.
L'utilisateur peut alors modifier les valeurs et soumettre à nouveau le formulaire, et Django vérifiera à nouveau le formulaire. Ce cycle peut se répéter autant de fois que nécessaire jusqu'à ce que tous les champs du formulaire soient valides.
À ce stade, nous sommes enfin prêts à effectuer l'action que nous voulions faire en premier lieu : envoyer un e-mail !
Exécutez une action lorsque tous les champs du formulaire sont valides
Nous avons maintenant un peu plus de logique à mettre en œuvre dans notre vue :
si c'est une requête POST...
si les données du formulaire sont valides, envoyer un e-mail ;
si les données du formulaire ne sont pas valides, afficher à nouveau le formulaire avec des messages d'erreur (comme nous le faisons déjà).
Nous avons besoin d'une instruction if
imbriquée :
from django.core.mail import send_mail
...
def contact(request):
if request.method == 'POST':
# créer une instance de notre formulaire et le remplir avec les données POST
form = ContactUsForm(request.POST)
if form.is_valid():
send_mail(
subject=f'Message from {form.cleaned_data["name"] or "anonyme"} via MerchEx Contact Us form',
message=form.cleaned_data['message'],
from_email=form.cleaned_data['email'],
recipient_list=['admin@merchex.xyz'],
)
# si le formulaire n'est pas valide, nous laissons l'exécution continuer jusqu'au return
# ci-dessous et afficher à nouveau le formulaire (avec des erreurs).
else:
# ceci doit être une requête GET, donc créer un formulaire vide
form = ContactUsForm()
return render(request,
'listings/contact.html',
{'form': form})
Nous importons la fonction send_mail
de Django au début. Ensuite, nous insérons l'instruction if
imbriquée, commençant par : if form.is_valid():
.
Si tous les champs de notre formulaire contiennent des données valides, alors form.is_valid()
renvoie True
et nous appelons send_mail
pour envoyer notre e-mail.
Donc, je dois vérifier ma boîte de réception pour les e-mails maintenant ?
L'envoi d'un véritable e-mail implique la configuration d'un serveur SMTP, que nous n'avons malheureusement pas le temps de couvrir ici ! Mais nous pouvons utiliser le serveur de messagerie fictif de Django pour tester notre formulaire. Ceci affichera tous les e-mails envoyés par Django dans le terminal. Ajoutez cette ligne au tout début du fichier settings.py :
# merchex/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Maintenant nous pouvons soumettre notre formulaire et regarder le résultat apparaître dans le terminal :
Et il y a notre formulaire de « contact » !
Nous avons presque terminé, mais il y a encore une chose à faire pour améliorer la convivialité de notre formulaire...
Redirigez le navigateur après un POST pour éviter les POST dupliqués
Essayez ceci : immédiatement après avoir soumis le formulaire et vu votre e-mail apparaître dans le terminal (refaites-le si vous avez fermé le navigateur entretemps), essayez de cliquer sur le bouton d'actualisation du navigateur. Qu'est-ce que vous voyez ?
La plupart des navigateurs modernes affichent un avertissement comme celui-ci :
Vous êtes-vous déjà demandé pourquoi cet avertissement apparaît ? Il n'apparaît que lorsque vous essayez de rafraîchir immédiatement après un POST. Parce que nous ne voulons généralement pas répéter les actions d’un POST :
nous ne voulons pas envoyer un e-mail en double ;
ou publier un article de blog en double ;
nous ne voulons absolument pas faire un paiement en double.
Cette boîte d'avertissement est donc utile, mais nous pouvons aller un peu plus loin. Nous pouvons rediriger le navigateur de la page d'origine vers une nouvelle page. Une redirection est un type de réponse HTTP qui demande au navigateur de charger une nouvelle page. Les avantages de cette démarche sont doubles :
nous réduisons la probabilité d'un POST dupliqué ;
nous pouvons améliorer l'expérience de l'utilisateur en affichant une page de confirmation au lieu de simplement recharger la même page.
Pour implémenter la redirection, nous allons utiliser la fonction redirect
.
redirect
est un raccourci pratique. Nous pouvons lui fournir un modèle d'URL avec des arguments ou directement un chemin d'URL.
Ajoutons cette déclaration d'importation et une autre ligne de code dans notre déclaration if
:
from django.shortcuts import redirect # ajoutez cet import
…
if form.is_valid():
send_mail(
subject=f'Message from {form.cleaned_data["name"] or "anonyme"} via MerchEx Contact Us form',
message=form.cleaned_data['message'],
from_email=form.cleaned_data['email'],
recipient_list=['admin@merchex.xyz'],
)
return redirect('email-sent') # ajoutez cette instruction de retour
Lorsque le formulaire est valide et que le courriel a été envoyé, cette redirection guidera le navigateur de la page du formulaire vers une page de confirmation. C'est une page qui a un motif URL avec le nom email-sent
. Bien entendu, cette page n'a pas encore été créée. Vous devriez maintenant être en mesure de créer vous-même un modèle d'URL, une vue et un gabarit pour cette page de confirmation, je vous laisse donc vous en charger !
Maintenant que vous avez terminé le chapitre, consultez le screencast pour vous assurer que vous avez tout compris correctement.
C'est à vous ! Ajoutez des pages supplémentaires à la barre de navigation
Maintenant que la page « contact » est terminée, nous devons ajouter un lien vers celle-ci dans la barre de navigation, ainsi qu'un lien vers la page « à propos ».
En résumé
Pour créer un formulaire dans Django, nous commençons par définir le formulaire comme une classe dans forms.py. C'est ici que nous définissons les différents types de champs du formulaire (CharField, EmailField, etc.), comme nous le faisons pour un modèle.
Nous créons une instance du formulaire dans notre vue et nous la passons au gabarit. Lorsque nous générons le formulaire avec
{{ form }}
, Django génère une grande partie du balisage HTML du formulaire pour nous.Dans la vue, nous gérons deux scénarios : une requête GET qui affiche un nouveau formulaire vide et une requête POST qui valide la saisie de l'utilisateur et exécute l'action du formulaire.
Maintenant que vous avez les bases de la conception de formulaires dans Django, vous êtes prêt à l'appliquer aux différentes opérations d'écriture, en commençant par le « C » de CRUD : Create, la création.