Bonjour à tous et bienvenus à la deuxième partie du tutoriel “Comment réaliser une ToDo App avec Django”. Dans cet article, nous allons voir comment écrire les vues, les templates et les URLs, qui nous permettront de construire notre application CRUD avec Django.
Nous allons écrire les vues au travers desquelles nous pourrons réaliser les différentes opérations CRUD abordées en première partie de notre tutoriel. Pour cela, nous allons écrire trois vues :
- index() : qu’on utilisera pour ajouter/créer une tâche et lister toutes les tâches ;
- update() : qui nous permettra d’apporter des modification à un objet “tâche” ;
- delete() : qui sera utilisée pour supprimer une tâche.
Première page web
Nous allons commencer par écrire une simple vue sans interaction avec le modèle. On construira ensuite le Template associé à la vue. Puis nous procéderons au mapping URL pour accéder à la vue.
La vue
Comme nous l’avons mentionné dans cet article, une vue est une fonction qui reçoit des requêtes web et renvoie des réponses web.
Dans views.py
, on écrit notre première vue index()
comme ceci :
from django.shortcuts import render # Create your views here. def index(request): return render(request,"index.html")
La vue index()
fait appel à la fonction render()
, qui récupère la requête HTTP et retourne la page index.html
, que nous allons écrire juste après.
Le Template
On va créer le répertoire templates
, qui accueillera tous les templates de l’application, à la racine du projet todoapp
.
Dans le répertoire templates
on ajoutera le fichier index.html
, qui contiendra d’abord les lignes suivantes :
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ToDo App</title> </head> <body> <h1>Hello, this is my first ToDo App using Django!</h1> </body> </html>
Dans la liste TEMPLATES
se trouvant dans le fichier settings.py
on va ajouter le chemin vers le répertoire templates
.
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ['templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
Organisation des Templates
Réutilisation du code
Puisque nous allons avoir plusieurs pages HTML à écrire, il y’aura des parties de code communes à ces pages. Ainsi, au lieu de réécrire ce code dans chaque page, on crée une page de base dont le code sera réutilisé dans les autres pages. La réutilisation du code est particulièrement utile lorsqu’on voudra ajouter du code CSS ou du Javascript à nos pages.
Le Template de base
On crée dans le répertoire templates
le fichier base.html
qui va contenir la structure commune aux templates de l’application. Dans ce fichier, on va défnir des blocs à l’aide de tags ou de balises {% block [nom_du_bloc] %}
, à l’intérieur desquels seront définis les contenus spécifiques des templates de l’application qui hériteront de base.html
.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title> {% block title %} {% endblock title %} ToDo App</title> </head> <body> <div> {% block content %} {% endblock content %} </div> </body> </html>
On va alors changer le fichier index.html
: on inscrit une balise {% extend 'base.html' %}
qui va étendre le code de base commun, puis on ajoute les blocs title
et content
. C’est dans ces blocs que nous mettrons notre contenu.
{% extends 'base.html' %} {% block title %} Mes tâches | {% endblock title %} {% block content %} <h1>Hello, this is my first ToDo App using Django!</h1> {% endblock content %}
Pour en savoir plus sur ces balises, je vous recommande de lire la page dédiée à l’utilisation des balises intégrées dans les templates Django dans la documentation officielle de Django.
Le motif d’URL
Comme nous l’avons déjà vu dans cet article, les vues sont associées à des URLs, qui sont définies dans le fichier urls.py
de l’application. Un chemin d’URL est composé d’une route ou motif d’URL (URL pattern), d’une vue et du nom affecté à l’URL.
Dans le répertoire de l’application todolist
, on crée donc le fichier urls.py
et on y ajoute les lignes suivantes :
from django.urls import path from . import views urlpatterns = [ path('',views.index,name="index") ]
On doit ensuite importer (inclure) les routes (actuelle et futures) de l’application todolist
dans les routes du projet todoapp
. Pour cela, on ajoute la méthode path('', include('todolist.urls'))
à la liste urlpatterns
se trouvant dans le fichier urls.py
du répertoire de configuration du projet todoapp
.
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('todolist.urls')) ]
Vous pouvez jeter un coup d’œil à la documentation officielle pour comprendre comment utiliser les fonctions django.urls dans la configuration d’URL.
Lorsqu’on lance le serveur et qu’on se connecte à l’adresse http://127.0.0.1:8000
, on aura cette page web :
Les Opérations CRUD
En première partie, nous avons vu comment réaliser les opérations de base pour manipuler les données dans la console interactive de Django ainsi qu’au niveau de l’interface d’administration. Nous allons maintenant utiliser ces requêtes dans les vues de notre application.
Afficher les tâches
Nous allons commencer par apporter des modifications à la vue index()
. Nous voulons afficher toutes les tâches stockées dans la base de données. Pour cela, nous récupérons le résultat de la requête Task.objects.all()
dans la variable tasks
. On retourne ensuite la réponse web à l’aide de la fonction render()
qui combine le template index.html
à un dictionnaire contexte {"tasks": tasks}
.
from django.shortcuts import render from .models import Task # Create your views here. def index(request): tasks = Task.objects.all() return render(request, "index.html", {"tasks": tasks})
On modifie ensuite le fichier index.html
en conséquence. Pour afficher les objets du QuerySet résultant de notre requête et qui est envoyé sous forme de dictionnaire, on utilise une boucle qui va accéder à notre variable tasks
à l’aide de la clé "tasks"
qui lui est associée dans ledit dictionnaire contexte.
La boucle for
voit notre QuerySet comme une liste qu’elle doit parcourir pour nous donner accès à la valeur de chaque objet du QuerySet.
{% extends 'base.html' %} {% block title %} Mes tâches | {% endblock title %} {% block content %} <h1>Hello, this is my first ToDo App using Django!</h1> <!-- Liste des tâches--> <ol> {% for task in tasks %} <li>{{task.title}}</li> {% endfor %} </ol> {% endblock content %}
Il suffit de rafraichir la page web pour afficher la liste des tâches :
Ajouter une tâche
Pour créer une tâche, on a besoin d’un formulaire pour y entrer les données relatives à notre tâche. Pour ce faire, Django fournit des outils et des bibliothèques qui permettent de créer et gérer les formulaires de saisie. On peut utiliser la classe Form
qui facilite la création des champs ainsi que l’affichage et la gestion des données. Cependant, il est aussi possible d’utiliser une autre classe, qu’on appelle ModelForm
et qui permet de créer des formulaires liés aux modèles de données.
Dans ce tutoriel, nous allons créer un formulaire à partir de notre modèle Task
via ModelForm
et pour cela on commence par créer un fichier forms.py
dans le répertoire de notre application todolist
et y ajouter les lignes suivantes :
from django import forms from django.forms import ModelForm from .models import Task class TaskForm(forms.ModelForm): class Meta: model = Task fields = "__all__"
En se basant sur le modèle, ModelForm
construit les champs du formulaire TaskForm
à partir des champs du modèle Task
.
Nous allons placer notre formulaire au niveau du template index.html
et c’est pour cela que sa gestion se fera au niveau de la vue index()
à laquelle est associé ledit template.
Dans la vue index()
, on instancie le formulaire form=TaskForm()
et on le transmet au template avec le contexte {"task_form": form}
.
from django.shortcuts import render from .models import Task from .forms import TaskForm # Create your views here. def index(request): form = TaskForm() tasks = Task.objects.all() return render(request, "index.html", {"tasks": tasks,"task_form": form})
Dans index.html
, on place les balises <form>
et on utilise la méthode POST
pour transmettre les données. La balise {% csrf_token %}
doit être ajoutée pour éviter les attaques de type Cross-Site Request Forgery (CSRF). On accède au formulaire form
à l’aide de la clé task_form
. On ajoute un bouton pour soumettre les données entrées.
{% extends 'base.html' %} {% block title %} Mes tâches | {% endblock title %} {% block content %} <h1>Hello, this is my first ToDo App using Django!</h1> <!-- Formulaire d'ajout d'une tâche --> <form method="post"> {% csrf_token %} {{task_form.title}} <button type="submit">Ajouter</button> </form> <!-- Liste des tâches--> <ol> {% for task in tasks %} <li>{{task.title}}</li> {% endfor %} </ol> {% endblock content %}
On peut vérifier que le formulaire est bien visible à l’adresse http://127.0.0.1:8000
.
Si vous testez votre formulaire, vous verrez que rien ne se passe et cela est tout à fait normal. Pour rendre le formulaire fonctionnel, on va ajouter le code qui permettra d’enregistrer dans la base de données l’information saisie.
Lorsqu’on soumet le formulaire avec la méthode POST
, les données saisies sont envoyées à la vue index()
via l’attribut POST
de l’objet request
passé en paramètre à la vue.
A l’aide de l’attribut method
du paramètre request
, on vérifie le type de méthode HTTP (GET
ou POST
) utilisée pour envoyer les données du formulaire. Lorsque la requête est de type POST
, on crée une instance du formulaire avec les données récupérées via la méthode POST
. On vérifie la validité des données saisies et on met à jour notre base de données en enregistrant la nouvelle tâche. Enfin, on effectue une redirection vers l’URL que l’on souhaite avec la méthode redirect()
.
from django.shortcuts import render, redirect from .models import Task from .forms import TaskForm # Create your views here. def index(request): form = TaskForm() #vérifier la méthode HTTP if request.method == "POST": #instancier le formulaire avec les données form = TaskForm(request.POST) #tester la validité du formulaire if form.is_valid(): #enregister les données form.save() #rediriger vers l'URL "index" return redirect("index") tasks = Task.objects.all() return render(request, "index.html", {"tasks": tasks,"task_form": form})
On teste que le formulaire fonctionne correctement en entrant une nouvelle tâche dans la zone de texte :
On clique sur “Ajouter” et la tâche s’ajoute à la liste des tâches dans la page principale index.html
:
Mettre à jour une tâche
Nous allons maintenant voir comment modifier une tâche, que ce soit pour corriger son intitulé ou bien mettre à jour l’état de réalisation (faite ou non).
Pour cela, nous allons créer une vue update()
pour gérer l’opération, ainsi que le template update.html
correspondant et dans lequel se trouvera le formulaire de mise à jour de la tâche (utilisant la méthode POST
), puis nous ajouterons la route qui donnera accès à la page web.
Dans views.py
, on ajoute la vue update()
qui, en plus de la requête, prend l’argument pk
qui correspond au champ id
de la tâche à modifier, ce qui permettra de l’identifier de façon unique. On récupère la tâche task
à l’aide de son identifiant et on passe cette instance en argument dans le formulaire form
pour pouvoir la modifier. Le formulaire form
est transmis au template udpate.html
avec le contexte {"edit_task_form": form}
.
Lorsque les données sont entrées dans le formulaire, on vérifie que celui-ci utilise la méthode POST
pour envoyer les données, que l’on récupère avec l’instance de la tâche à mettre à jour. Ensuite, on teste la validité des données avant de les enregistrer dans la base de données. Enfin, on effectue une redirection vers la page index
.
from django.shortcuts import render, redirect from .models import Task from .forms import TaskForm # Create your views here. def index(request): form = TaskForm() if request.method == "POST": form = TaskForm(request.POST) if form.is_valid(): form.save() return redirect("index") tasks = Task.objects.all() return render(request, "index.html", {"tasks": tasks,"task_form": form}) def update(request, pk): task = Task.objects.get(id=pk) form = TaskForm(instance=task) if request.method == "POST": form = TaskForm(request.POST, instance=task) if form.is_valid(): form.save() return redirect("index") return render(request, "update.html", {"edit_task_form": form})
Dans notre répertoire templates
, on crée le fichier update.html
dans lequel se trouvera le formulaire de mise à jour.
{% extends 'base.html' %} {% block title %} Modifier une tâche | {% endblock title %} {% block content %} <form method="post"> {% csrf_token %} {{edit_task_form}} <button type="submit">Modifier</button> </form> {% endblock content %}
Dans le fichier urls.py
de notre application, on ajoute la route pour notre page update
, qui diffère un peu de celle de la page index. Le schéma d’URL contient le paramètre pk
converti en entier et inscrit entre deux chevrons pour être envoyé à la vue update()
.
from django.urls import path from . import views urlpatterns = [ path('', views.index, name="index"), path('update/<int:pk>/',views.update, name="update") ]
Dans notre page index
, ajoutons un lien vers la page update
à l’aide duquel nous pourrons faire passer l’identifiant d’une tâche task.id
. Pour cela, on définit le lien à l’aide du tag url
qui renvoie une URL correspondant à une vue (le nom d’un motif d’URL) et des paramètres facultatifs.
Dans la page index
, à l’intérieur de la boucle for
qui parcourt les objets task
, on crée le lien <a>
avec le tag url
qui prend comme premier argument le nom du schéma d’URL de la page update
, indiqué dans urls.py
avec name="update"
et comme second argument l’identifiant de la tâche task.id
.
{% extends 'base.html' %} {% block title %} Mes tâches | {% endblock title %} {% block content %} <h1>Hello, this is my first ToDo App using Django!</h1> <form method="post"> {% csrf_token %} {{task_form.title}} <button type="submit">Ajouter</button> </form> <ol> {% for task in tasks %} <li>{{task.title}} | <a href="{% url 'update' task.id %}">Modifier</a></li> {% endfor %} </ol> {% endblock content %}
Pour voir les changements apportés, rechargez la page index. Vous pouvez tester la nouvelle fonctionnalité qu’on vient d’ajouter.
En cliquant sur le lien “Modifier”, vous êtes dirigés par la page update
et vous remarquez que l’identifiant de la tâche que vous voulez modifier est indiqué dans l’URL. Le formulaire de la page contient un champs texte dans lequel s’affiche l’intitulé title
de la tâche et que vous pouvez modifier, ainsi qu’une case à cocher (checkbox input) associée au champ completed
pour indiquer si la tâche a été effectuée ou non.
Après avoir éditée la tâche, on clique sur Modifier et on est redirigé vers la page index
, où on peut voir le changement apporté à l’intitulé de la tâche qui a été mise à jour.
Améliorer l’affichage
Dans l’affichage actuel, on remarque que nous ne pouvons pas différencier les tâches réalisées de celles qui ne le sont pas. Pour remédier à cela, nous devons porter quelques modifications au fichier index.html
.
On utilise la balise if
pour évaluer le champ completed
de la tâche (True
ou False
). Si dans la page update
la case completed
est cochée pour une tâche (task.completed == True
) alors on barre cette tâche avec la balise <strike>
.
{% extends 'base.html' %} {% block title %} Mes tâches | {% endblock title %} {% block content %} <h1>Hello, this is my first ToDo App using Django!</h1> <form method="post"> {% csrf_token %} {{task_form.title}} <button type="submit">Ajouter</button> </form> <ol> {% for task in tasks %} <li> {% if task.completed == True %} <strike> {{task.title}} </strike> {% else %} {{task.title}} {% endif %} | <a href="{% url 'update' task.id %}">Modifier</a> </il> {% endfor %} </ol> {% endblock content %}
Rafraichissez la page index
pour vérifier les changements apportés.
Supprimer une tâche
Comme pour l’opération de mise à jour, dans views.py
on va créer une nouvelle vue delete()
pour supprimer une tâche, dont elle récupère l’identifiant en paramètre task = Task.objects.get(id=pk)
à partir de la page index. A la vue sera associé un template delete.html
qui recevra l’information de la tâche à supprimer avec le contexte {"task":task}
.
Lorsque la suppression est confirmée à travers le formulaire de la page delete
, on supprime la tâche avec la méthode delete()
.
def delete(request, pk): task = Task.objects.get(id=pk) if request.method == "POST": task.delete() return redirect("index") return render(request,"delete.html",{"task":task})
Dans le fichier urls.py
de l’application, on ajoute la route pour la page delete
. Le schéma d’URL contient le paramètre pk
à envoyer à la vue delete()
.
from django.urls import path from . import views urlpatterns = [ path('', views.index, name="index"), path('update/<int:pk>/',views.update, name="update"), path('delete/<int:pk>/',views.delete, name="delete"), ]
Dans le répertoire templates
, on crée le fichier delete.html
. On y ajoute un formulaire qui contiendra un <input>
de type submit
pour confirmer la suppression de la tâche. On peut aussi y mettre un lien <a>
avec le tag {% url 'index' %}
pour annuler l’opération et revenir à la page index
.
{% extends 'base.html' %} {% block title %} Supprimer une tâche | {% endblock title %} {% block content %} <p>Veux-tu supprimer la tâche : <strong>{{task}}</strong>?</p> <a href="{% url 'index' %}"> Annuler</a> <form method="post"> {% csrf_token %} <input type="submit" value="Confirmer"> </form> {% endblock content %}
Dans index.html
, on ajoute un lien avec un tag url
qui nous permettra de passer le paramètre task.id
à la page delete
.
{% extends 'base.html' %} {% block title %} Mes tâches | {% endblock title %} {% block content %} <h1>Hello, this is my first ToDo App using Django!</h1> <form method="post"> {% csrf_token %} {{task_form.title}} <button type="submit">Ajouter</button> </form> <ol> {% for task in tasks %} <li> {% if task.completed == True %} <strike> {{task.title}} </strike> {% else %} {{task.title}} {% endif %} | <a href="{% url 'update' task.id %}">Modifier</a> | <a href="{% url 'delete' task.id %}">Supprimer</a> </il> {% endfor %} </ol> {% endblock content %}
Rafraichissez la page principale et tentez de supprimer une tâche.
Vous arriverez sur la page delete
qui vous proposera d’annuler ou de confirmer la suppression de la tâche.
En appuyant sur “Confirmer” la tâche est supprimée et vous êtes redirigés vers la page index
.
Si vous êtes arrivés jusqu’ici, je vous félicite! Vous avez réussi à construire votre ToDo App: une application CRUD totalement fonctionnelle créée avec Django. Il nous faut cependant avouer qu’elle n’est pas très attrayante. Mais ne vous en faites pas! C’est ce que nous verrons dans la prochaine et dernière partie de notre tutoriel.