Bonjour à tous et bienvenus à la troisième et dernière partie du tutoriel “Comment réaliser une ToDo App avec Django”. Cette fois-ci, nous apprendrons à créer le design d’une Todo App avec Bootstrap et Django. En effet, dans la seconde partie de ce tutoriel, nous avions une application fonctionnelle mais pas très jolie. Dans cet article, nous allons travailler le graphisme de l’application. Et pour cela, on va y ajouter du “Style” pour un effet plus professionnel à l’aide du Framework Bootstrap.
Le Framework Bootstrap
Pour gérer l’aspect de notre application, nous aurons besoin du langage CSS (Cascading Style Sheets). Pour ce faire, nous allons utiliser dans ce tutoriel le célèbre Framework CSS : Bootstrap V5.0.
Bootstrap est un ensemble de fichiers CSS et Javascript. Il nous fournit les outils nécessaires pour construire facilement et rapidement un design propre et élaboré pour notre application.
Pour commencer, nous utiliserons des composants ne nécessitant pas de fonctionner avec Javacript. Pour l’instant, nous n’aurons donc besoin que d’un fichier CSS minifié que nous inclurons dans notre fichier base.html
. Il vous faut aller sur le site getbootstrap.com et dans la section jsDelivr copier le lien vers le fichier CSS.
Ajoutez le code copié dans la section <head>
du fichier base.html
se trouvant dans le répertoire templates
du projet, ainsi le style sera étendu aux autres fichiers : index.html
, update.html
et delete.html
.
Dans le fichier de base, nous avons aussi modifié l’élément <div>
dans lequel nous avons inscrit le bloc content
. En effet, nous avons associé à la balise <div>
la classe .container
qui est un élément de mise en page basique de Bootstrap. Cet élément est un conteneur qui permet de recentrer le contenu de la page web.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- CSS only --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous"> <title> {% block title %} {% endblock title %} ToDo App</title> </head> <body> <div class="container"> {% block content %} {% endblock content %} </div> </body> </html>
En rafraichissant la page index par exemple, on remarque les changements de typographie et de disposition du contenu.
Structuration des templates
Accueil
Pour notre page index (page d’accueil), on peut utiliser un élément conteneur flexible et très pratique pour nous aider à structurer notre liste de tâche : l’élément .card
(carte, en français). Les cartes sont généralement composées d’un en-tête .card-header
, d’un corps .card-body
et d’un pied .card-footer
. Dans notre exemple, nous avons mis dans l’en-tête .card-header
le titre de l’application et dans le corps .card-body
le formulaire de saisie ainsi que la liste des tâches.
Nous plaçons les champs du formulaire <form>
dans une <div>
à laquelle on associe la classe groupe d’entrée .group-input
qui permet de placer le bouton AJOUTER du côté droit de l’entrée {{task_form.title}}
. Pour gérer la taille des éléments du formulaire, on utilise les classes de dimensionnement associé à .group-input
(exemple : .mb-3
pour définir la taille par défaut d’un groupe d’entrée).
Pour les boutons de formulaires <button>
, <input>
ou les liens <a>
, on utilise la classe bouton .btn
, à laquelle on peut associer d’autres classes pour gérer la taille (exemple : .btn-lg
) et la couleur (exemple : .btn-primary
).
{% extends 'base.html' %} {% block title %} Mes tâches | {% endblock title %} {% block content %} <div class="card"> <div class="card-header"> <h1 class="text-center">My ToDo App</h1> </div> <div class="card-body"> <form method="post"> {% csrf_token %} <div class="input-group mb-3"> {{task_form.title}} <button type="submit" class="btn btn-primary btn-lg">AJOUTER</button> </div> </form> <h4 class="bg-secondary text-light py-2 rounded text-center">Ma liste de Tâches </h4> <ul class="list-group"> {% for task in tasks %} <li class="list-group-item list-group-item-action"> {% if task.completed == True %} <strike>{{task.title}} </strike> {% else %} {{task.title}} {% endif %} <div class="float-end"> <a href="{% url 'update' task.id %}" class="btn btn-sm btn-warning">Modifier</a> <a href="{% url 'delete' task.id %}" class="btn btn-sm btn-danger">Supprimer</a> </div> </li> {% endfor %} </ul> </div> </div> {% endblock content %}
En modifiant votre fichier index.html
, comme indiqué ci-dessus, vous obtenez la page d’accueil (page principale) suivante :
Mise à jour
Pour la page update.html
, nous structurons celle-ci à l’aide de la classe .card
de la même manière que pour la page d’accueil. Concernant le formulaire, nous utilisons aussi la classe .input-group
pour placer la case à cocher du champ completed
à gauche du champ de title
et le bouton Modifier à sa droite.
{% extends 'base.html' %} {% block title %} Modifier une tâche | {% endblock title %} {% block content %} <div class="card"> <div class="card-header"> <h1 class="text-center">ToDo App</h1> </div> <div class="card-body"> <form method="post"> {% csrf_token %} <div class="input-group mb-3"> <div class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tâche réalisée"> {{edit_task_form.completed}} </div> {{edit_task_form.title}} <button type="submit" class="btn btn-lg btn-info">Modifier</button> </div> </form> </div> </div> {% endblock content %}
Infobulle Bootstrap
Nous inscrivons la case à cocher {{edit_task_form.compelted}}
dans une <div>
utilisée pour afficher une infobulle (tooltip, en anglais) qui, au survol de la case à cocher, nous informe du sens de la case cochée (Tâche réalisée) à l’aide de l’attribut data-bs
. Pour faire fonctionner l’infobulle, nous aurons besoin de CSS et de Javascript. Nous allons définir l’infobulle dès à présent. Puis nous ajouterons le code Javascript nécessaire pour la faire fonctionner plus tard (dans la dernière section de ce tutoriel).
Avec data-bs-toggle="tooltip"
, on indique que l’on veut afficher une infobulle et avec data-bs-placement="bottom"
, on indique que l’on souhaite que celle-ci s’affiche en bas de l’élément survolé. Le texte à afficher dans l’info-bulle est défini à l’aide de l’attribut title
.
Sans le code Javascript, nous obtenons la page ci-après. En survolant la case à cocher, l’information “Tâche réalisée” s’affiche grâce à l’attribut title
, qui est un attribut HTML interprétable par les navigateurs web.
Suppression
Comme pour les deux autres pages, nous utiliserons la classe .card
pour structurer le fichier delete.html
. Nous allons afficher le lien d’annulation <a>
et l’élément de confirmation <input>
sous forme de boutons .btn
alignés à l’aide de la classe d-inline-block
.
{% extends 'base.html' %} {% block title %} Supprimer une tâche | {% endblock title %} {% block content %} <div class="card"> <div class="card-header"> <h1 class="text-center">ToDo App</h1> </div> <div class="card-body"> <h4>Veux-tu supprimer la tâche : <strong>{{task}}</strong>?</h4> <form method="post"> {% csrf_token %} <div class="d-inline-block"> <a href="{%url 'index'%}" class="btn btn-lg btn-secondary"> Annuler</a> </div> <div class="d-inline-block"> <input type="submit" class="btn btn-lg btn-danger" value="Confirmer"> </div> </form> </div> </div> {% endblock content %}
Voici à quoi devra ressembler la page de suppression.
Améliorer les formulaires
Nous venons de voir avec quelle facilité il est possible de gérer le graphisme grâce à Bootstrap. Cependant, vous avez probablement remarqué que les champs de saisie de texte ne sont pas terribles. Nous allons y remédier en apportant quelques changements au fichier forms.py.
Dans Django, les champs de formulaires sont associés à des composants de formulaires, appelés plus communément Widgets. Ces derniers gèrent la production de code HTML des éléments de saisie, ainsi que l’extraction des données envoyées.
Par exemple un champ de formulaire forms.CharField()
est associé à un widget Django par défaut forms.widgets.TextInput()
qui génère une balise HTML d’entrée de texte <input type="text" ...>
.
Une classe widget
fournit l’attribut attrs
qui correspond à un dictionnaire contenant les attributs HTML à définir pour le composant de formulaire à afficher.
A travers ce dictionnaire, on a utilisé la classe .form-control
pour mettre en forme le champs title
ainsi que la classe .form-control-lg
pour fixer sa taille, grande en l’occurrence. On a aussi défini l’attribut HTML placeholder
pour renseigner le texte indicatif "Nouvelle tâche …"
dans le champs du formulaire, qui s’efface au moment de la saisie d’un texte.
from django import forms from django.forms import ModelForm from .models import Task class TaskForm(forms.ModelForm): class Meta: model = Task fields = "__all__" #fields = ('title',) title = forms.CharField( widget=forms.TextInput( attrs={ "class": "form-control form-control-lg", "placeholder": "Nouvelle tâche ...", }),)
Après avoir procédé aux modifications du fichier forms.py
, vous pouvez rafraîchir la page d’accueil pour voir la mise en forme du formulaire de saisie d’une nouvelle tâche.
Cliquer sur le bouton Modifier d’une tâche de la liste pour être redirigé vers la page de mise à jour. Vous constaterez là aussi la nouvelle mise en forme du formulaire.
Les fichiers statiques
Généralement les sites web servent des fichiers comme des images, des fichiers CSS ou encore Javascript. On appelle ces derniers fichiers statiques. Dans Django, on gère ces fichiers avec django.contrib.staticfiles
.
Dans cette section, nous allons voir comment personnaliser le style à notre goût en ajoutant du code CSS. Nous verrons ensuite comment ajouter du code Javascript pour faire fonctionner l’infobulle, que nous avions définie plus haut.
Pour ce faire, nous allons créer à la racine du répertoire ToDoAppProject
le répertoire static
. On y crée le dossier css
qui contiendra le fichier style.css
et le dossier js
dans lequel on créera le fichier script.js
.
Personnaliser le style
Pour ce tutoriel, nous allons apporter de légères modifications au style. Ajoutez le code ci-après dans le fichier style.css
pour changer la couleur d’arrière plan du corps des pages et gérer la position et la taille du conteneur principal.
body{ background-color: #547086; } .container{ margin-top: 100px; max-width: 700px; }
Infobulle Bootstrap
Pour faire fonctionner l’infobulle de la case à cocher du formulaire de mise à jour, nous ajouterons du code Javascript. Pour cela, il faut d’abord vous reconnecter à la page getbootstrap.com. Une fois sur le site, copiez le lien vers les plugins Javascript et Popper (utilisé par les infobulles).
On ajoute le code copié à la fin de la page base.html
juste avant la balise </body>
.
... <body> <div class="container"> {% block content %} {% endblock content %} </div> <!-- JavaScript Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script> </body> </html>
Dans le fichier update.html
, on ajoute un identifiant id="competed"
à la <div>
utilisée pour afficher l’infobulle.
... <form method="post"> {% csrf_token %} <div class="input-group mb-3"> <div id="completed" class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tâche réalisée"> {{edit_task_form.completed}} </div> {{edit_task_form.title}} <button type="submit" class="btn btn-lg btn-info">Modifier</button> </div> </form> ...
Dans le fichier script.js
, on ajoute le script suivant pour activer l’infobulle.
var exampleEl = document.getElementById('completed') var tooltip = new bootstrap.Tooltip(exampleEl, {boundary: 'window'})
Chargement des fichiers statiques
On doit définir le chemin vers le dossier static
en ajoutant la liste STATICFILES_DIRS
du fichier de configuration settings.py
.
STATICFILES_DIRS = [ BASE_DIR / "static", ]
Pour servir les fichiers statiques dans les templates, on doit les charger avec la balise {% load static %}
qu’il faut placer en haut du ficher base.html
. On ajoute ensuite le lien vers le fichier CSS
dans la section <head>
au-dessus de <title>
à l’aide de la balise {% static %}
qui génère le chemin vers le fichier style.css
. De façon similaire, on ajoute la balise <script>
à la fin du fichier et on génère le chemin vers le fichier script.js
.
{% load static %} <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- CSS only --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous"> <link rel="stylesheet" href="{% static 'css/style.css'%}"> <title> {% block title %} {% endblock title %} ToDo App </title> </head> <body> <div class="container"> {% block content %} {% endblock content %} </div> <!-- JavaScript Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script> <!-- tooltip --> <script src="{% static 'js/script.js'%}"></script> </body> </html>
Vous pouez recharger la page d’accueil une dernière fois et vérifier que les changement CSS on été appliqués correctement.
Sur la page de mise à jour, vous pouvez vérifier que l’infobulle apparaît au survol de la case à cocher.
N’oubliez pas de vérifier que la page de suppression est fonctionnelle.
Conclusion
Félicitations ! En suivant ce tutoriel, découpé en trois parties, vous avez réussi à créer une application ToDoApp totalement fonctionnelle. Vous avez créé votre projet Django, appris à définir les modèles de données et interagi avec la base de données. Vous avez aussi réussi à créer les vues, les templates et les URLs de l’application. Enfin, vous avez utilisé Bootstrap pour gérer le graphisme et appris à servir les fichiers statiques de l’application.