Menu

Comment réaliser une “ToDo App” avec Django 3.1 – Partie II

ToDo APP avec Django

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.

Arborescence du projet Django

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 %}

INFO 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'))
]

INFO 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 :

Page principale de l'application Django

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 :

Lister les tâches - ToDo App - Application CRUD sous Django

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.

Formulaire de saisie d'une tâche - ToDo App - Application CRUD sous Django

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 :

Saisie d'une tâche - ToDo App - Application CRUD sous Django

On clique sur “Ajouter” et la tâche s’ajoute à la liste des tâches dans la page principale index.html :

Ajouter une tâche - ToDo App - Application CRUD sous Django

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.

lien de modification d'une tâche - ToDo App - Application CRUD sous Django

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.

Modifier une tâche - ToDo App - Application CRUD sous Django

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.

Afficher la tâche modifiée - ToDo App - Application CRUD sous Django

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.

Barrer la tâche réalisée - ToDo App - Application CRUD sous Django

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.

Afficher le lien de suppression d'une tâche - ToDo App - Application CRUD sous Django

Vous arriverez sur la page delete qui vous proposera d’annuler ou de confirmer la suppression de la tâche.

Formulaire de suppression d'une tâche - ToDo App - Application CRUD sous Django

En appuyant sur “Confirmer” la tâche est supprimée et vous êtes redirigés vers la page index.

Lister les tâches non supprimées - ToDo App - Application CRUD sous Django

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.