« Django et base de données » : différence entre les versions
De GBLL, TAL, ALAO, etc.
Aucun résumé des modifications |
|||
(27 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 3 : | Ligne 3 : | ||
[[https://www.djangoproject.com/ Site officiel de Django]] | [[https://www.djangoproject.com/ Site officiel de Django]] | ||
Pour installer Django et faire ses premier pas, jetez un œil à [Cours:Initiation_à_Django]. | Pour installer Django et faire ses premier pas, jetez un œil à [[Cours:Initiation_à_Django]]. | ||
Dans ce tutoriel, nous allons utiliser la base de données native de Django. Nous allons créer une page Todo list, sur laquelle sera lister les items de notre todo list. Nous pourrons en ajouter et en supprimer depuis cette même page via une requête AJAX. | Dans ce tutoriel, nous allons utiliser la base de données native de Django. Nous allons créer une page Todo list, sur laquelle sera lister les items de notre todo list. Nous pourrons en ajouter et en supprimer depuis cette même page via une requête AJAX. | ||
Ligne 11 : | Ligne 11 : | ||
== Création d'une page dédiée pour notre Todo list == | == Création d'une page dédiée pour notre Todo list == | ||
1. Créer un template html pour notre page todo list. La balise ligne 1 permet d'injecter le contenu de cette page dans la page principale base.html que nous avons créée dans le tuto précédent. La balise ligne 2 permettra de charger le fichier javascript todolist.js, que nous n'avons pas encore créé. Pour l'instant, il n'y a qu'un titre sur la page, et une div "items" vide. | |||
(todolist.html) | (todolist.html) | ||
Ligne 24 : | Ligne 24 : | ||
{% endblock %} | {% endblock %} | ||
{% block | {% block javascript %} | ||
<script src="{% static 'scripts/todolist.js' %}"></script> | <script src="{% static 'scripts/todolist.js' %}"></script> | ||
{% endblock %} | {% endblock %} | ||
</syntaxhighlight> | </syntaxhighlight> | ||
2. Créer une view pour afficher cette page dans views.py. | |||
(view.py) | (view.py) | ||
Ligne 37 : | Ligne 37 : | ||
</syntaxhighlight> | </syntaxhighlight> | ||
3. Ajouter une url pour pouvoir appeler cette view dans urls.py. | |||
(urls.py) | (urls.py) | ||
Ligne 49 : | Ligne 49 : | ||
path('todolist/', myApp.todolist), | path('todolist/', myApp.todolist), | ||
</syntaxhighlight> | </syntaxhighlight> | ||
4. Vous pouvez éventuellement ajouter un lien dans votre barre de navigation dans base.html pour accéder facilement à cette nouvelle page. | |||
(base.html) | |||
<syntaxhighlight lang="html" highlight="24" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
... | |||
<body> | |||
<!-- NAVBAR --> | |||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> | |||
<div class="container-fluid"> | |||
<a class="navbar-brand" href="#">Navbar</a> | |||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation"> | |||
<span class="navbar-toggler-icon"></span> | |||
</button> | |||
<div class="collapse navbar-collapse" id="navbarColor01"> | |||
<ul class="navbar-nav me-auto"> | |||
<li class="nav-item"> | |||
<a class="nav-link active" href="/">Accueil | |||
<span class="visually-hidden">(current)</span> | |||
</a> | |||
</li> | |||
<li class="nav-item"> | |||
<a class="nav-link" href="/spacy/">Spacy</a> | |||
</li> | |||
<li class="nav-item"> | |||
<a class="nav-link" href="/variables/">Variables</a> | |||
</li> | |||
<li class="nav-item"> | |||
<a class="nav-link" href="/todolist/">Todo list</a> | |||
</li> | |||
</div> | |||
</div> | |||
</nav> | |||
</syntaxhighlight> | |||
5. Vous pouvez maintenant afficher la page todolist (avec juste un titre qui s'affiche). | |||
== Créer une table Item dans la base de données == | |||
1. Voilà, c'est maintenant que ça commence vraiment. Chaque tables de votre base de données est définie dans le fichier models.py de votre application. Chaque table est une classe qui peut avoir des propriétés et des méthodes. Ici on va se limiter à juste 3 ou 4 tables avec quelques propriétés. Commençons par créer une table Item. Chaque item que nous créerons ensuite sera une instance de la classe Item, et aura donc les mêmes propriétés. Faisons basique pour l'instant : chaque item aura un champs de titre et un champs de texte. On ajoutera d'autres propriétés plus tard. | |||
(models.py) | |||
<syntaxhighlight lang="python" highlight="3,4,5" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
from django.db import models | |||
class Item(models.Model): | |||
titre = models.CharField(max_length=50) #ici on indique que ça dépassera pas 50 caractères | |||
texte = models.TextField() | |||
</syntaxhighlight> | |||
2. Il faut maintenant mettre à jour la base de données, comme à chaque fois que nous modifierons le fichier models.py. Pour cela, deux commandes à retenir (stopper le serveur pour pouvoir les exécuter) : | |||
- {{code|python manage.py makemigrations|bash}} : pour préparer la mise à jour | |||
- {{code|python manage.py migrate|bash}} : pour effectuer la mise à jour | |||
3. À partir de là, on peut ajouter/supprimer/modifier des éléments dans la base, et toutes les modifications seront stockées dans le fichier db.sqlite du projet. Les données sont persistantes, elles sont donc conservées même si on redémarre le serveur. Pour ajouter des données, Django propose plusieurs solutions. Le plus classique, c'est de créer des views dédiées à l'ajout, la suppression, la recherche dans la base de données et afficher le résultat soit directement dans une page avec render(), soit en les envoyant dans une JsonResponse() comme on l'a fait pour Spacy. Django propose aussi une fonctionnalité très utile pour les administrateurs du site, et on va commencer par regarder ça. En gros c'est un genre de PhpMyAdmin en mieux pour Django. | |||
=== Gérer la base de données avec l'interface admin du site === | |||
- L'interface admin est présente par défaut. L'url par défaut est [http://127.0.0.1:8000/admin/]. On vous demande alors de vous connecter (l'accès est réservé à ceux qui en ont le droit. Or, il n'y a pas encore d'utilisateur enregistré sur notre appli. On va donc commencer par créer un superutilisateur qui aura tous les droits. Stoppez votre serveur et exécutez la commande suivante : | |||
{{code|python manage.py createsuperuser|bash}} (il faut avoir fait au moins une fois les migrations de votre base de données pour que la table User soit initialisée) | |||
- Ensuite, relancez votre serveur et connectez-vous à la page admin. Par défaut sont affichées la table des utilisateurs et celles des groupes. Elles sont présentes par défaut dans tous les projets Django. On va pouvoir afficher sur cette page toutes les tables qu'on veut. Pour cela, il faut ajouter une ligne dans le fichier admin.py : | |||
(admin.py) | |||
<syntaxhighlight lang="python" highlight="2,5" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
from django.contrib import admin | |||
from .models import Item | |||
# Register your models here. | |||
admin.site.register(Item) | |||
</syntaxhighlight> | |||
- Maintenant, la table Item devrait apparaître sur la page admin. D'ici, vous pouvez directement lister, ajouter, modifier ou supprimer des objets de votre base de données. Ajoutez un item pour voir. | |||
=== Lister les items depuis votre site === | |||
Bon OK, c'est bien beau le site admin, mais c'est pour les admins. Or on aimerait éditer la base de données directement depuis le site, sans avoir à se connecter par exemple. C'est parti. On va commencer par lister les items présents dans la base sur la page todolist. | |||
On pourrait requéter la table Item et injecter la liste des résultats directement lors du render() de la page todolist.html, dans la view todolist. Ça marchera, mais il faudra recharger la page à chaque fois qu'on veut mettre à jour la liste des items. On va donc préférer faire une requête depuis la page todolist.html, pour récupérer la liste des items et les afficher dans la div "items" prévue à cet effet. On pourra le faire autant de fois qu'on veut, sans recharger la page. | |||
1. Créer un fichier javascript todolist.js, et y mettre une fonction getItems() qui requêtera le serveur comme on l'a fait pour Spacy. L'url pour lister les items sera /todolist/list. | |||
(todolist.js) | |||
<syntaxhighlight lang="javascript" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
async function getItems() { | |||
// PARAMÈTRES DE LA REQUÊTE | |||
const requete = { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/json' | |||
} | |||
}; | |||
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE | |||
const response = await fetch('/todolist/list', requete) | |||
const data = await response.json(); | |||
console.table(data['items']); | |||
} | |||
</syntaxhighlight> | |||
2. Donc cette fonction javascript requête une url que nous n'avons pas encore ajoutée, donc ajoutons-là dans urls.py. Elle appellera une view listItems qu'on créera juste après. La réponse sera envoyée en json, penser donc au csrf_exempt(). | |||
(urls.py) | |||
<syntaxhighlight lang="python" highlight="8" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
urlpatterns = [ | |||
path('admin/', admin.site.urls), | |||
path('', myApp.home), | |||
path('variables/', myApp.variables), | |||
path('spacy/', myApp.spacy), | |||
path('analyze/', csrf_exempt(myApp.analyze)), | |||
path('todolist/', myApp.todolist), | |||
path('todolist/list', csrf_exempt(myApp.listItems)), | |||
</syntaxhighlight> | |||
3. Puis ajouter une view qui va requêter la base de données et renvoyer la liste des items. Le truc un peu chiant ici, c'est qu'on ne peut pas envoyer directement le résultat de la recherche (liste des objets items), il faut les mettre un par un dans une liste (en fait on ne peut pas envoyer d'objets en json). | |||
(views.py) | |||
<syntaxhighlight lang="python" highlight="4-15" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
def todolist(request): | |||
return render(request, 'todolist.html') | |||
def listItems(request): | |||
items = Item.objects.all() | |||
list_items = [] | |||
for item in items: | |||
list_items.append( {"titre":item.titre, "texte":item.texte, "id":item.id } ) | |||
reponse = { | |||
"items": list_items | |||
} | |||
return JsonResponse(reponse) | |||
</syntaxhighlight> | |||
La fonction {{code|Item.objects.all()}} récupère tous les items de la table Item. Ensuite, on ajoute chaque item dans une liste list_items. Je propose de représenter ici chaque item par un dictionnaire à deux clés (ou un objet à deux propriétés) : un titre et un texte. Et on renvoie ça en JsonResponse(). | |||
4. Il y a maintenant tout ce qu'il faut ! Sur la page todolist, lancez la fonction getItems() (via la console javascript en cliquant sur F12 et en affichant l'onglet console. Vous devriez voir s'afficher un tableau listant les items présents dans votre base de données. Il y en a peut-être qu'un pour l'instant, n'hésitez pas à en ajouter un ou deux via la page admin pour voir si la page todolist les récupère bien. Pas besoin de recharger la page, il suffit de relancer la fonction getItems(). | |||
[[Fichier:GetItems.png|cadre|centré|résultat dans la console javascript de la fonction getitems(), avec un seul item dans la base de données]] | |||
5. Affichons directement ce résultat dans la div "items" de notre html, et ajoutons un bouton pour exécuter getItems() sans devoir passer par la console. | |||
(todolist.html) | |||
<syntaxhighlight lang="javascript" highlight="4" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
{% block content %} | |||
<h1>Ma todo list</h1> | |||
<button type="button" class="btn btn-primary" onclick="getItems()">Lister les items</button> | |||
<div id="items"></div> | |||
{% endblock %} | |||
</syntaxhighlight> | |||
(todolist.js) | |||
<syntaxhighlight lang="javascript" highlight="16-27" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
async function getItems() { | |||
// PARAMÈTRES DE LA REQUÊTE | |||
const requete = { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/json' | |||
} | |||
}; | |||
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE | |||
const response = await fetch('/todolist/list', requete) | |||
const data = await response.json(); | |||
console.table(data['items']); | |||
var itemsDiv = document.getElementById('items'); | |||
itemsDiv.innerHTML = ""; // on vide la div pour la réinitialiser | |||
data['items'].forEach( (item) => { | |||
itemsDiv.innerHTML += `<div class="card border-primary m-3" style="width: 30rem;"> | |||
<div class="card-header">Item id ${item.id}</div> | |||
<div class="card-body"> | |||
<h4 class="card-title">${item.titre}</h4> | |||
<p class="card-text">${item.texte}</p> | |||
</div> | |||
</div>` | |||
}) | |||
} | |||
</syntaxhighlight> | |||
[[Fichier:Listitems.png|cadre|centré|Aperçu des items sur la page todolist/]] | |||
=== Ajouter des items depuis la page todolist === | |||
Nous allons ajouter un petit formulaire avec deux champs sur la page html, pour renseigner les nouveaux items. Le bouton valider du formulaire lancera une fonction javascript qui récupère les informations du formulaire (titre et texte renseignés) et fait une requête d'ajout d'item au serveur. Côté serveur, il faudra donc une view dédiée à l'ajout d'un item, et l'url associée. | |||
1. Ajouter un petit formulaire sur la page todolist.html. On propose d'afficher deux colonnes sur la page : le formulaire à gauche et la liste des items à droite. | |||
(todolist.html) | |||
<syntaxhighlight lang="html" highlight="4-27, 30-31" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
{% block content %} | |||
<h1>Ma todo list</h1> | |||
<div class="row"> | |||
<div class="col"> | |||
<div id="addItem" class="m-4"> | |||
<form> | |||
<fieldset> | |||
<legend>Ajouter un nouvel item</legend> | |||
<div class="form-group row m-2"> | |||
<label for="addItemTitre" class="col-sm-2 col-form-label">Titre</label> | |||
<div class="col-sm-10"> | |||
<input type="text" class="form-control" id="addItemTitre"> | |||
</div> | |||
</div> | |||
<div class="form-group row m-2"> | |||
<label for="addItemTexte" class="col-sm-2 col-form-label">Texte</label> | |||
<div class="col-sm-10"> | |||
<textarea class="form-control" id="addItemTexte" rows="3"></textarea> | |||
</div> | |||
</div> | |||
</fieldset> | |||
<center><button type="button" class="btn btn-success m-2" style="margin:auto" onclick="addItem()">Enregistrer !</button></center> | |||
</form> | |||
</div> | |||
</div> | |||
<div class="col"> | |||
<button type="button" class="btn btn-primary" onclick="getItems()">Lister les items</button> | |||
<div id="items"></div> | |||
</div> | |||
</div> | |||
{% endblock %} | |||
</syntaxhighlight> | |||
=== Supprimer des items depuis la page todolist === | |||
1. Commençons par ajouter un bouton supprimer sur chaque item. On va le mettre dans le header de chaque item, lors de l'affichage de la liste des items. Le bouton supprimer appellera une fonction delItem(id) qui prendra en argument l'id de l'item à supprimer. Pour le logo de la poubelle, j'ai copié le code svg correspondant sur la page Icons de Bootstrap : https://icons.getbootstrap.com/ | |||
(todolist.js) | |||
<syntaxhighlight lang="javascript" highlight="21-29" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
async function getItems() { | |||
// PARAMÈTRES DE LA REQUÊTE | |||
const requete = { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/json' | |||
} | |||
}; | |||
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE | |||
const response = await fetch('/todolist/list', requete) | |||
const data = await response.json(); | |||
console.table(data['items']); | |||
var itemsDiv = document.getElementById('items'); | |||
itemsDiv.innerHTML = ""; // on vide la div pour la réinitialiser | |||
data['items'].forEach( (item) => { | |||
itemsDiv.innerHTML += `<div class="card border-primary m-3" style="width: 30rem;"> | |||
<div class="card-header d-flex justify-content-between"> | |||
<div>Item id ${item.id}</div> | |||
<button type="button" class="btn btn-danger" onclick="delItem(${item.id})"> | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> | |||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/> | |||
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/> | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="card-body"> | |||
<h4 class="card-title">${item.titre}</h4> | |||
<p class="card-text">${item.texte}</p> | |||
</div> | |||
</div>` | |||
}) | |||
} | |||
</syntaxhighlight> | |||
2. Ajoutons la fonction delItem(id) : | |||
(todolist.js) | |||
<syntaxhighlight lang="javascript" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
async function delItem(id) { | |||
var colis = { | |||
identifiant: id | |||
} | |||
console.log('Envoi colis:',colis); | |||
// PARAMÈTRES DE LA REQUÊTE | |||
const requete = { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/json' | |||
}, | |||
body: JSON.stringify(colis) | |||
}; | |||
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE | |||
const response = await fetch('/todolist/del', requete) | |||
const data = await response.json(); | |||
console.log(data); | |||
getItems(); // on reliste les items pour mettre à jour la page. | |||
} | |||
</syntaxhighlight> | |||
3. Ajoutons maintenant une view dédiée à la suppression des items dans views.py | |||
(views.py) | |||
<syntaxhighlight lang="python" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
def delItem(request): | |||
colis = json.loads(request.body) | |||
identifiant = colis["identifiant"] | |||
item = Item.objects.get(id=identifiant) | |||
item.delete() | |||
reponse = { | |||
"resultat": 1 | |||
} | |||
return JsonResponse(reponse) | |||
</syntaxhighlight> | |||
4. ...et l'url pour appeler cette view dans urls.py : | |||
(urls.py) | |||
<syntaxhighlight lang="python" highlight="10" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
urlpatterns = [ | |||
path('admin/', admin.site.urls), | |||
path('', myApp.home), | |||
path('variables/', myApp.variables), | |||
path('spacy/', myApp.spacy), | |||
path('analyze/', csrf_exempt(myApp.analyze)), | |||
path('todolist/', myApp.todolist), | |||
path('todolist/list', csrf_exempt(myApp.listItems)), | |||
path('todolist/add', csrf_exempt(myApp.addItem)), | |||
path('todolist/del', csrf_exempt(myApp.delItem)), | |||
] | |||
</syntaxhighlight> | |||
Et c'est bon ! | |||
== Créer une table Categorie pour étiqueter les items de la todo-list == | |||
Donc jusque là, on a vu comment créer une table dans la base de données (la classe Item), et comment ajouter/supprimer des éléments. Nos items sont très basiques : il n'y a qu'un champ titre et un champ texte. Maintenant on souhaite ajouter un champ catégorie, pour pouvoir catégoriser chaque item, par exemple "perso", "famille", "travail". On va donc créer une table qu'on appellera Categorie, et un champ dans chaque item qui pointera vers cette table. | |||
1. Ajouter la classe Categorie dans models.py. Il faut l'ajouter avant la classe Item, puisque celle-ci pointera vers Categorie, qui doit donc être définie en amont. Ajoutons aussi le champs d'Item qui pointe vers Categorie. {{code|null=True}} permet de laisser ce champ optionnel (on peut avoir des items sans catégorie, c'est d'autant plus nécessaire qu'on a déjà des items dans la base de données. Si on paramètre ce champ pour qu'il soit obligatoire, il faudrait mettre une valeur par défaut. {{code|on_delete=models.SET_NULL}} indique que le champ passe à NULL (pas de catégorie) si la catégorie en question est supprimée. | |||
(models.py) | |||
<syntaxhighlight lang="python" highlight="3,4,9" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
from django.db import models | |||
class Categorie(models.Model): | |||
nom = models.CharField(max_length=30) | |||
class Item(models.Model): | |||
titre = models.CharField(max_length=50) | |||
texte = models.TextField() | |||
categorie = models.ForeignKey(Categorie, on_delete=models.SET_NULL, null=True) | |||
</syntaxhighlight> | |||
2. Comme la structure de la base de données a été modifiée, il faut la mettre à jour. Coupez le serveur, puis exécutez les commandes {{code|python manage.py makemigrations}} puis {{code|python manage.py migrate}}. Puis relancez le serveur. | |||
3. On peut ajouter la nouvelle classe dans l'interface admin : | |||
(admin.py) | |||
<syntaxhighlight lang="python" highlight="2,6" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
from django.contrib import admin | |||
from .models import Item, Categorie | |||
# Register your models here. | |||
admin.site.register(Item) | |||
admin.site.register(Categorie) | |||
</syntaxhighlight> | |||
4. Dans l'interface admin (http://127.0.0.1:8000/admin/), vous pouvez maintenant ajouter les catégories "perso", "famille", "travail". Et dans Items, vous avez maintenant un menu déroulant permettant de choisir une catégorie. Par contre ce n'est pas très pratique à utiliser tel quel car les instances sont indiquées par leur identifiant numérique. Ce serait plus simple d'afficher le nom de la catégorie plutôt. Pour cela, ajoutons une méthode {{code|__str__}}, et tant qu'à faire, dans la classe Item également. | |||
(models.py) | |||
<syntaxhighlight lang="python" highlight="6,7,14,15" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
from django.db import models | |||
class Categorie(models.Model): | |||
nom = models.CharField(max_length=30) | |||
def __str__(self): | |||
return self.nom | |||
class Item(models.Model): | |||
titre = models.CharField(max_length=50) | |||
texte = models.TextField() | |||
categorie = models.ForeignKey(Categorie, on_delete=models.SET_NULL, null=True) | |||
def __str__(self): | |||
return self.titre | |||
</syntaxhighlight> | |||
5. Et voilà ça apparaît maintenant de manière plus lisible dans l'interface admin. Vous pouvez créer vos catégories et indiquer la catégorie que vous voulez pour les items existants. | |||
6. Il est maintenant possible de filtrer les items par catégorie dans views.py, par exemple. Dans le code ci-dessous, nous allons simplement récupérer la catégorie (et plus précisément son champs nom) de chaque item pour l'afficher sur la page du client, et j'ajoute en commentaire quelques commandes pour filtrer si ça vous intéresse. Il existe plein de filtres pour requêter votre base de données, n'hésitez pas consulter [https://docs.djangoproject.com/en/4.1/topics/db/queries/ la doc de Django]. | |||
(views.py) | |||
<syntaxhighlight lang="python" highlight="11" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
def listItems(request): | |||
items = Item.objects.all() | |||
# items = Item.objects.filter(categorie="perso") # Récupérer seulement les items dont la categorie est "perso" | |||
# item = Item.objects.first() # Récupérer le dernier item ajouté (le premier de la liste) | |||
# item = Item.objects.filter(categorie="famille").first() # idem parmi les items de la catégorie "famille" | |||
list_items = [] | |||
for item in items: | |||
list_items.append( {"titre":item.titre, "texte":item.texte, "id":item.id, "categorie":item.categorie.nom} ) | |||
reponse = { | |||
"items": list_items | |||
} | |||
return JsonResponse(reponse) | |||
</syntaxhighlight> | |||
7. La catégorie est maintenant bien envoyée au client, il suffit de l'afficher d'une manière ou d'une autre. On propose de l'afficher dans un petit badge bootstrap juste après le numéro d'item. On peut définir une classe pour chaque catégorie dans une css, ou bien directement dans la classe Categorie en ajoutant un champ couleur. Ici on propose de le faire par la css, en appelant la classe {{code|badge-<nomDeLaCatégorie>}}. | |||
(todolist.js) | |||
<syntaxhighlight lang="javascript" highlight="23" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
async function getItems() { | |||
// PARAMÈTRES DE LA REQUÊTE | |||
const requete = { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/json' | |||
} | |||
}; | |||
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE | |||
const response = await fetch('/todolist/list', requete) | |||
const data = await response.json(); | |||
console.table(data['items']); | |||
var itemsDiv = document.getElementById('items'); | |||
itemsDiv.innerHTML = ""; // on vide la div pour la réinitialiser | |||
data['items'].forEach( (item) => { | |||
itemsDiv.innerHTML += `<div class="card border-primary m-3" style="width: 30rem;"> | |||
<div class="card-header d-flex justify-content-between"> | |||
<div>Item id ${item.id}</div> | |||
<span class="badge badge-${item.categorie} me-auto ms-3">${item.categorie}</span> | |||
<button type="button" class="btn btn-danger" onclick="delItem(${item.id})"> | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> | |||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/> | |||
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/> | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="card-body"> | |||
<h4 class="card-title">${item.titre}</h4> | |||
<p class="card-text">${item.texte}</p> | |||
</div> | |||
</div>` | |||
}) | |||
} | |||
</syntaxhighlight> | |||
(todolist.css) | |||
<syntaxhighlight lang="css" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
.badge-perso { | |||
background-color: darkblue; | |||
height:fit-content | |||
} | |||
.badge-famille { | |||
background-color: darkgray; | |||
height:fit-content | |||
} | |||
.badge-travail { | |||
background-color: darkred; | |||
height:fit-content | |||
} | |||
</syntaxhighlight> | |||
== Choisir une catégorie lors de l'ajout d'un item sur l'interface client == | |||
Pour permettre à l'utilisateur de choisir une catégorie lors de l'ajout d'un item, il faut savoir quelles catégories existent dans la base de données. Pour cela, il suffit de récupérer la liste des catégories dans la fonction todolist() de views.py (la fonction qui est appelée pour afficher la page html) et envoyer la liste telle quelle avec la page html, comme on l'a fait avec la page "variables" du premier tutoriel. Ensuite, on pourra looper sur cette liste de catégorie pour afficher toutes options d'un menu déroulant dans notre formulaire d'ajout d'item. Lors de l'ajout d'un item, il faudra que la fonction javascript addItem() récupère sa valeur pour l'envoyer au serveur. | |||
1. Envoyer la liste brute des catégories lors de l'affichage de la page todolist.html. Contrairement à JsonResponse, on peut ici envoyer directement le résultat de la requête de la base de données sans passer par un dictionnaire texte. | |||
(views.py) | |||
<syntaxhighlight lang="python" highlight="2,3" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
def todolist(request): | |||
categories = Categorie.objects.all() | |||
return render(request, 'todolist.html', {"categories":categories}) | |||
</syntaxhighlight> | |||
2. Afficher un menu déroulant dans le formulaire côté client, en loopant sur la variable "categories" : | |||
(todolist.html) | |||
<syntaxhighlight lang="html" highlight="16-25" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
<form> | |||
<fieldset> | |||
<legend>Ajouter un nouvel item</legend> | |||
<div class="form-group row m-2"> | |||
<label for="addItemTitre" class="col-sm-2 col-form-label">Titre</label> | |||
<div class="col-sm-10"> | |||
<input type="text" class="form-control" id="addItemTitre"> | |||
</div> | |||
</div> | |||
<div class="form-group row m-2"> | |||
<label for="addItemTexte" class="col-sm-2 col-form-label">Texte</label> | |||
<div class="col-sm-10"> | |||
<textarea class="form-control" id="addItemTexte" rows="3"></textarea> | |||
</div> | |||
</div> | |||
<div class="form-group row m-2"> | |||
<label for="selectCategorie" class="col-sm-2 col-form-label">Catégorie</label> | |||
<div class="col-sm-10"> | |||
<select id="selectCategorie" class="form-select" style="width:fit-content"> | |||
{% for categorie in categories %} | |||
<option value="{{ categorie.id }}">{{ categorie.nom }}</option> | |||
{% endfor %} | |||
</select> | |||
</div> | |||
</div> | |||
</fieldset> | |||
<center><button type="button" class="btn btn-success m-2" style="margin:auto" onclick="addItem()">Enregistrer !</button></center> | |||
</form> | |||
</syntaxhighlight> | |||
3. Récupérer la valeur de la catégorie lors de l'ajout d'un item, dans la fonction addItem() du javascript : | |||
(todolist.js) | |||
<syntaxhighlight lang="javascript" highlight="5,10" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
async function addItem() { | |||
// on récupère les variables à envoyer au serveur | |||
var titre = document.getElementById('addItemTitre').value; | |||
var texte = document.getElementById('addItemTexte').value; | |||
var categorie = document.getElementById('selectCategorie').value; | |||
var colis = { | |||
titre, | |||
texte, | |||
categorie | |||
} | |||
console.log('Envoi colis:',colis); | |||
... | |||
</syntaxhighlight> | |||
4. Enregistrer la catégorie côté serveur, dans views.py : | |||
(views.py) | |||
<syntaxhighlight lang="python" highlight="5,6" style="border: 1px solid #dfdfdf;background-color:#f8f8f8;font-size: 90%;padding:10px"> | |||
def addItem(request): | |||
colis = json.loads(request.body) | |||
titre = colis["titre"] | |||
texte = colis["texte"] | |||
categorie = colis["categorie"] | |||
newItem = Item(titre=titre,texte=texte,categorie=Categorie.objects.get(id=categorie)) | |||
newItem.save() | |||
reponse = { | |||
"resultat": 1 | |||
} | |||
return JsonResponse(reponse) | |||
</syntaxhighlight> | |||
Résultat à ce stade : | |||
[[Fichier:DjangoTodolist.png|cadre|centré]] | |||
Si vous êtes arrivé⋅e jusque là, bravo ! Si vous avez des questions, n'hésitez pas à me demander en cours, ou par mail, mais regardez aussi la doc ou les forums, vous trouverez sans doute plus rapidement la réponse à votre problème ! |
Dernière version du 28 août 2023 à 01:11
Django est un framework python qui vous permet de rapidement créer un site web, une application web ou une API.
Pour installer Django et faire ses premier pas, jetez un œil à Cours:Initiation_à_Django.
Dans ce tutoriel, nous allons utiliser la base de données native de Django. Nous allons créer une page Todo list, sur laquelle sera lister les items de notre todo list. Nous pourrons en ajouter et en supprimer depuis cette même page via une requête AJAX.
Todo list de base pour commencer :)
Création d'une page dédiée pour notre Todo list
1. Créer un template html pour notre page todo list. La balise ligne 1 permet d'injecter le contenu de cette page dans la page principale base.html que nous avons créée dans le tuto précédent. La balise ligne 2 permettra de charger le fichier javascript todolist.js, que nous n'avons pas encore créé. Pour l'instant, il n'y a qu'un titre sur la page, et une div "items" vide.
(todolist.html)
{% extends "base.html" %}
{% load static %}
{% block content %}
<h1>Ma todo list</h1>
<div id="items"></div>
{% endblock %}
{% block javascript %}
<script src="{% static 'scripts/todolist.js' %}"></script>
{% endblock %}
2. Créer une view pour afficher cette page dans views.py.
(view.py)
def todolist(request):
return render(request, 'todolist.html')
3. Ajouter une url pour pouvoir appeler cette view dans urls.py.
(urls.py)
urlpatterns = [
path('admin/', admin.site.urls),
path('', myApp.home),
path('variables/', myApp.variables),
path('spacy/', myApp.spacy),
path('analyze/', csrf_exempt(myApp.analyze)),
path('todolist/', myApp.todolist),
4. Vous pouvez éventuellement ajouter un lien dans votre barre de navigation dans base.html pour accéder facilement à cette nouvelle page.
(base.html)
...
<body>
<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor01">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="/">Accueil
<span class="visually-hidden">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/spacy/">Spacy</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/variables/">Variables</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/todolist/">Todo list</a>
</li>
</div>
</div>
</nav>
5. Vous pouvez maintenant afficher la page todolist (avec juste un titre qui s'affiche).
Créer une table Item dans la base de données
1. Voilà, c'est maintenant que ça commence vraiment. Chaque tables de votre base de données est définie dans le fichier models.py de votre application. Chaque table est une classe qui peut avoir des propriétés et des méthodes. Ici on va se limiter à juste 3 ou 4 tables avec quelques propriétés. Commençons par créer une table Item. Chaque item que nous créerons ensuite sera une instance de la classe Item, et aura donc les mêmes propriétés. Faisons basique pour l'instant : chaque item aura un champs de titre et un champs de texte. On ajoutera d'autres propriétés plus tard.
(models.py)
from django.db import models
class Item(models.Model):
titre = models.CharField(max_length=50) #ici on indique que ça dépassera pas 50 caractères
texte = models.TextField()
2. Il faut maintenant mettre à jour la base de données, comme à chaque fois que nous modifierons le fichier models.py. Pour cela, deux commandes à retenir (stopper le serveur pour pouvoir les exécuter) :
- python manage.py makemigrations
: pour préparer la mise à jour
- python manage.py migrate
: pour effectuer la mise à jour
3. À partir de là, on peut ajouter/supprimer/modifier des éléments dans la base, et toutes les modifications seront stockées dans le fichier db.sqlite du projet. Les données sont persistantes, elles sont donc conservées même si on redémarre le serveur. Pour ajouter des données, Django propose plusieurs solutions. Le plus classique, c'est de créer des views dédiées à l'ajout, la suppression, la recherche dans la base de données et afficher le résultat soit directement dans une page avec render(), soit en les envoyant dans une JsonResponse() comme on l'a fait pour Spacy. Django propose aussi une fonctionnalité très utile pour les administrateurs du site, et on va commencer par regarder ça. En gros c'est un genre de PhpMyAdmin en mieux pour Django.
Gérer la base de données avec l'interface admin du site
- L'interface admin est présente par défaut. L'url par défaut est [1]. On vous demande alors de vous connecter (l'accès est réservé à ceux qui en ont le droit. Or, il n'y a pas encore d'utilisateur enregistré sur notre appli. On va donc commencer par créer un superutilisateur qui aura tous les droits. Stoppez votre serveur et exécutez la commande suivante :
python manage.py createsuperuser
(il faut avoir fait au moins une fois les migrations de votre base de données pour que la table User soit initialisée)
- Ensuite, relancez votre serveur et connectez-vous à la page admin. Par défaut sont affichées la table des utilisateurs et celles des groupes. Elles sont présentes par défaut dans tous les projets Django. On va pouvoir afficher sur cette page toutes les tables qu'on veut. Pour cela, il faut ajouter une ligne dans le fichier admin.py :
(admin.py)
from django.contrib import admin
from .models import Item
# Register your models here.
admin.site.register(Item)
- Maintenant, la table Item devrait apparaître sur la page admin. D'ici, vous pouvez directement lister, ajouter, modifier ou supprimer des objets de votre base de données. Ajoutez un item pour voir.
Lister les items depuis votre site
Bon OK, c'est bien beau le site admin, mais c'est pour les admins. Or on aimerait éditer la base de données directement depuis le site, sans avoir à se connecter par exemple. C'est parti. On va commencer par lister les items présents dans la base sur la page todolist.
On pourrait requéter la table Item et injecter la liste des résultats directement lors du render() de la page todolist.html, dans la view todolist. Ça marchera, mais il faudra recharger la page à chaque fois qu'on veut mettre à jour la liste des items. On va donc préférer faire une requête depuis la page todolist.html, pour récupérer la liste des items et les afficher dans la div "items" prévue à cet effet. On pourra le faire autant de fois qu'on veut, sans recharger la page.
1. Créer un fichier javascript todolist.js, et y mettre une fonction getItems() qui requêtera le serveur comme on l'a fait pour Spacy. L'url pour lister les items sera /todolist/list.
(todolist.js)
async function getItems() {
// PARAMÈTRES DE LA REQUÊTE
const requete = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE
const response = await fetch('/todolist/list', requete)
const data = await response.json();
console.table(data['items']);
}
2. Donc cette fonction javascript requête une url que nous n'avons pas encore ajoutée, donc ajoutons-là dans urls.py. Elle appellera une view listItems qu'on créera juste après. La réponse sera envoyée en json, penser donc au csrf_exempt().
(urls.py)
urlpatterns = [
path('admin/', admin.site.urls),
path('', myApp.home),
path('variables/', myApp.variables),
path('spacy/', myApp.spacy),
path('analyze/', csrf_exempt(myApp.analyze)),
path('todolist/', myApp.todolist),
path('todolist/list', csrf_exempt(myApp.listItems)),
3. Puis ajouter une view qui va requêter la base de données et renvoyer la liste des items. Le truc un peu chiant ici, c'est qu'on ne peut pas envoyer directement le résultat de la recherche (liste des objets items), il faut les mettre un par un dans une liste (en fait on ne peut pas envoyer d'objets en json).
(views.py)
def todolist(request):
return render(request, 'todolist.html')
def listItems(request):
items = Item.objects.all()
list_items = []
for item in items:
list_items.append( {"titre":item.titre, "texte":item.texte, "id":item.id } )
reponse = {
"items": list_items
}
return JsonResponse(reponse)
La fonction Item.objects.all()
récupère tous les items de la table Item. Ensuite, on ajoute chaque item dans une liste list_items. Je propose de représenter ici chaque item par un dictionnaire à deux clés (ou un objet à deux propriétés) : un titre et un texte. Et on renvoie ça en JsonResponse().
4. Il y a maintenant tout ce qu'il faut ! Sur la page todolist, lancez la fonction getItems() (via la console javascript en cliquant sur F12 et en affichant l'onglet console. Vous devriez voir s'afficher un tableau listant les items présents dans votre base de données. Il y en a peut-être qu'un pour l'instant, n'hésitez pas à en ajouter un ou deux via la page admin pour voir si la page todolist les récupère bien. Pas besoin de recharger la page, il suffit de relancer la fonction getItems().
5. Affichons directement ce résultat dans la div "items" de notre html, et ajoutons un bouton pour exécuter getItems() sans devoir passer par la console.
(todolist.html)
{% block content %}
<h1>Ma todo list</h1>
<button type="button" class="btn btn-primary" onclick="getItems()">Lister les items</button>
<div id="items"></div>
{% endblock %}
(todolist.js)
async function getItems() {
// PARAMÈTRES DE LA REQUÊTE
const requete = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE
const response = await fetch('/todolist/list', requete)
const data = await response.json();
console.table(data['items']);
var itemsDiv = document.getElementById('items');
itemsDiv.innerHTML = ""; // on vide la div pour la réinitialiser
data['items'].forEach( (item) => {
itemsDiv.innerHTML += `<div class="card border-primary m-3" style="width: 30rem;">
<div class="card-header">Item id ${item.id}</div>
<div class="card-body">
<h4 class="card-title">${item.titre}</h4>
<p class="card-text">${item.texte}</p>
</div>
</div>`
})
}
Ajouter des items depuis la page todolist
Nous allons ajouter un petit formulaire avec deux champs sur la page html, pour renseigner les nouveaux items. Le bouton valider du formulaire lancera une fonction javascript qui récupère les informations du formulaire (titre et texte renseignés) et fait une requête d'ajout d'item au serveur. Côté serveur, il faudra donc une view dédiée à l'ajout d'un item, et l'url associée.
1. Ajouter un petit formulaire sur la page todolist.html. On propose d'afficher deux colonnes sur la page : le formulaire à gauche et la liste des items à droite.
(todolist.html)
{% block content %}
<h1>Ma todo list</h1>
<div class="row">
<div class="col">
<div id="addItem" class="m-4">
<form>
<fieldset>
<legend>Ajouter un nouvel item</legend>
<div class="form-group row m-2">
<label for="addItemTitre" class="col-sm-2 col-form-label">Titre</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="addItemTitre">
</div>
</div>
<div class="form-group row m-2">
<label for="addItemTexte" class="col-sm-2 col-form-label">Texte</label>
<div class="col-sm-10">
<textarea class="form-control" id="addItemTexte" rows="3"></textarea>
</div>
</div>
</fieldset>
<center><button type="button" class="btn btn-success m-2" style="margin:auto" onclick="addItem()">Enregistrer !</button></center>
</form>
</div>
</div>
<div class="col">
<button type="button" class="btn btn-primary" onclick="getItems()">Lister les items</button>
<div id="items"></div>
</div>
</div>
{% endblock %}
Supprimer des items depuis la page todolist
1. Commençons par ajouter un bouton supprimer sur chaque item. On va le mettre dans le header de chaque item, lors de l'affichage de la liste des items. Le bouton supprimer appellera une fonction delItem(id) qui prendra en argument l'id de l'item à supprimer. Pour le logo de la poubelle, j'ai copié le code svg correspondant sur la page Icons de Bootstrap : https://icons.getbootstrap.com/
(todolist.js)
async function getItems() {
// PARAMÈTRES DE LA REQUÊTE
const requete = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE
const response = await fetch('/todolist/list', requete)
const data = await response.json();
console.table(data['items']);
var itemsDiv = document.getElementById('items');
itemsDiv.innerHTML = ""; // on vide la div pour la réinitialiser
data['items'].forEach( (item) => {
itemsDiv.innerHTML += `<div class="card border-primary m-3" style="width: 30rem;">
<div class="card-header d-flex justify-content-between">
<div>Item id ${item.id}</div>
<button type="button" class="btn btn-danger" onclick="delItem(${item.id})">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
</svg>
</button>
</div>
<div class="card-body">
<h4 class="card-title">${item.titre}</h4>
<p class="card-text">${item.texte}</p>
</div>
</div>`
})
}
2. Ajoutons la fonction delItem(id) :
(todolist.js)
async function delItem(id) {
var colis = {
identifiant: id
}
console.log('Envoi colis:',colis);
// PARAMÈTRES DE LA REQUÊTE
const requete = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(colis)
};
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE
const response = await fetch('/todolist/del', requete)
const data = await response.json();
console.log(data);
getItems(); // on reliste les items pour mettre à jour la page.
}
3. Ajoutons maintenant une view dédiée à la suppression des items dans views.py
(views.py)
def delItem(request):
colis = json.loads(request.body)
identifiant = colis["identifiant"]
item = Item.objects.get(id=identifiant)
item.delete()
reponse = {
"resultat": 1
}
return JsonResponse(reponse)
4. ...et l'url pour appeler cette view dans urls.py :
(urls.py)
urlpatterns = [
path('admin/', admin.site.urls),
path('', myApp.home),
path('variables/', myApp.variables),
path('spacy/', myApp.spacy),
path('analyze/', csrf_exempt(myApp.analyze)),
path('todolist/', myApp.todolist),
path('todolist/list', csrf_exempt(myApp.listItems)),
path('todolist/add', csrf_exempt(myApp.addItem)),
path('todolist/del', csrf_exempt(myApp.delItem)),
]
Et c'est bon !
Créer une table Categorie pour étiqueter les items de la todo-list
Donc jusque là, on a vu comment créer une table dans la base de données (la classe Item), et comment ajouter/supprimer des éléments. Nos items sont très basiques : il n'y a qu'un champ titre et un champ texte. Maintenant on souhaite ajouter un champ catégorie, pour pouvoir catégoriser chaque item, par exemple "perso", "famille", "travail". On va donc créer une table qu'on appellera Categorie, et un champ dans chaque item qui pointera vers cette table.
1. Ajouter la classe Categorie dans models.py. Il faut l'ajouter avant la classe Item, puisque celle-ci pointera vers Categorie, qui doit donc être définie en amont. Ajoutons aussi le champs d'Item qui pointe vers Categorie. permet de laisser ce champ optionnel (on peut avoir des items sans catégorie, c'est d'autant plus nécessaire qu'on a déjà des items dans la base de données. Si on paramètre ce champ pour qu'il soit obligatoire, il faudrait mettre une valeur par défaut.
indique que le champ passe à NULL (pas de catégorie) si la catégorie en question est supprimée.
(models.py)
from django.db import models
class Categorie(models.Model):
nom = models.CharField(max_length=30)
class Item(models.Model):
titre = models.CharField(max_length=50)
texte = models.TextField()
categorie = models.ForeignKey(Categorie, on_delete=models.SET_NULL, null=True)
2. Comme la structure de la base de données a été modifiée, il faut la mettre à jour. Coupez le serveur, puis exécutez les commandes python manage.py makemigrations
puis python manage.py migrate
. Puis relancez le serveur.
3. On peut ajouter la nouvelle classe dans l'interface admin : (admin.py)
from django.contrib import admin
from .models import Item, Categorie
# Register your models here.
admin.site.register(Item)
admin.site.register(Categorie)
4. Dans l'interface admin (http://127.0.0.1:8000/admin/), vous pouvez maintenant ajouter les catégories "perso", "famille", "travail". Et dans Items, vous avez maintenant un menu déroulant permettant de choisir une catégorie. Par contre ce n'est pas très pratique à utiliser tel quel car les instances sont indiquées par leur identifiant numérique. Ce serait plus simple d'afficher le nom de la catégorie plutôt. Pour cela, ajoutons une méthode __str__
, et tant qu'à faire, dans la classe Item également.
(models.py)
from django.db import models
class Categorie(models.Model):
nom = models.CharField(max_length=30)
def __str__(self):
return self.nom
class Item(models.Model):
titre = models.CharField(max_length=50)
texte = models.TextField()
categorie = models.ForeignKey(Categorie, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.titre
5. Et voilà ça apparaît maintenant de manière plus lisible dans l'interface admin. Vous pouvez créer vos catégories et indiquer la catégorie que vous voulez pour les items existants.
6. Il est maintenant possible de filtrer les items par catégorie dans views.py, par exemple. Dans le code ci-dessous, nous allons simplement récupérer la catégorie (et plus précisément son champs nom) de chaque item pour l'afficher sur la page du client, et j'ajoute en commentaire quelques commandes pour filtrer si ça vous intéresse. Il existe plein de filtres pour requêter votre base de données, n'hésitez pas consulter la doc de Django.
(views.py)
def listItems(request):
items = Item.objects.all()
# items = Item.objects.filter(categorie="perso") # Récupérer seulement les items dont la categorie est "perso"
# item = Item.objects.first() # Récupérer le dernier item ajouté (le premier de la liste)
# item = Item.objects.filter(categorie="famille").first() # idem parmi les items de la catégorie "famille"
list_items = []
for item in items:
list_items.append( {"titre":item.titre, "texte":item.texte, "id":item.id, "categorie":item.categorie.nom} )
reponse = {
"items": list_items
}
return JsonResponse(reponse)
7. La catégorie est maintenant bien envoyée au client, il suffit de l'afficher d'une manière ou d'une autre. On propose de l'afficher dans un petit badge bootstrap juste après le numéro d'item. On peut définir une classe pour chaque catégorie dans une css, ou bien directement dans la classe Categorie en ajoutant un champ couleur. Ici on propose de le faire par la css, en appelant la classe badge-<nomDeLaCatégorie>
.
(todolist.js)
async function getItems() {
// PARAMÈTRES DE LA REQUÊTE
const requete = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
// ENVOI ET RÉCUPÉRATION DE LA RÉPONSE
const response = await fetch('/todolist/list', requete)
const data = await response.json();
console.table(data['items']);
var itemsDiv = document.getElementById('items');
itemsDiv.innerHTML = ""; // on vide la div pour la réinitialiser
data['items'].forEach( (item) => {
itemsDiv.innerHTML += `<div class="card border-primary m-3" style="width: 30rem;">
<div class="card-header d-flex justify-content-between">
<div>Item id ${item.id}</div>
<span class="badge badge-${item.categorie} me-auto ms-3">${item.categorie}</span>
<button type="button" class="btn btn-danger" onclick="delItem(${item.id})">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
</svg>
</button>
</div>
<div class="card-body">
<h4 class="card-title">${item.titre}</h4>
<p class="card-text">${item.texte}</p>
</div>
</div>`
})
}
(todolist.css)
.badge-perso {
background-color: darkblue;
height:fit-content
}
.badge-famille {
background-color: darkgray;
height:fit-content
}
.badge-travail {
background-color: darkred;
height:fit-content
}
Choisir une catégorie lors de l'ajout d'un item sur l'interface client
Pour permettre à l'utilisateur de choisir une catégorie lors de l'ajout d'un item, il faut savoir quelles catégories existent dans la base de données. Pour cela, il suffit de récupérer la liste des catégories dans la fonction todolist() de views.py (la fonction qui est appelée pour afficher la page html) et envoyer la liste telle quelle avec la page html, comme on l'a fait avec la page "variables" du premier tutoriel. Ensuite, on pourra looper sur cette liste de catégorie pour afficher toutes options d'un menu déroulant dans notre formulaire d'ajout d'item. Lors de l'ajout d'un item, il faudra que la fonction javascript addItem() récupère sa valeur pour l'envoyer au serveur.
1. Envoyer la liste brute des catégories lors de l'affichage de la page todolist.html. Contrairement à JsonResponse, on peut ici envoyer directement le résultat de la requête de la base de données sans passer par un dictionnaire texte.
(views.py)
def todolist(request):
categories = Categorie.objects.all()
return render(request, 'todolist.html', {"categories":categories})
2. Afficher un menu déroulant dans le formulaire côté client, en loopant sur la variable "categories" :
(todolist.html)
<form>
<fieldset>
<legend>Ajouter un nouvel item</legend>
<div class="form-group row m-2">
<label for="addItemTitre" class="col-sm-2 col-form-label">Titre</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="addItemTitre">
</div>
</div>
<div class="form-group row m-2">
<label for="addItemTexte" class="col-sm-2 col-form-label">Texte</label>
<div class="col-sm-10">
<textarea class="form-control" id="addItemTexte" rows="3"></textarea>
</div>
</div>
<div class="form-group row m-2">
<label for="selectCategorie" class="col-sm-2 col-form-label">Catégorie</label>
<div class="col-sm-10">
<select id="selectCategorie" class="form-select" style="width:fit-content">
{% for categorie in categories %}
<option value="{{ categorie.id }}">{{ categorie.nom }}</option>
{% endfor %}
</select>
</div>
</div>
</fieldset>
<center><button type="button" class="btn btn-success m-2" style="margin:auto" onclick="addItem()">Enregistrer !</button></center>
</form>
3. Récupérer la valeur de la catégorie lors de l'ajout d'un item, dans la fonction addItem() du javascript :
(todolist.js)
async function addItem() {
// on récupère les variables à envoyer au serveur
var titre = document.getElementById('addItemTitre').value;
var texte = document.getElementById('addItemTexte').value;
var categorie = document.getElementById('selectCategorie').value;
var colis = {
titre,
texte,
categorie
}
console.log('Envoi colis:',colis);
...
4. Enregistrer la catégorie côté serveur, dans views.py :
(views.py)
def addItem(request):
colis = json.loads(request.body)
titre = colis["titre"]
texte = colis["texte"]
categorie = colis["categorie"]
newItem = Item(titre=titre,texte=texte,categorie=Categorie.objects.get(id=categorie))
newItem.save()
reponse = {
"resultat": 1
}
return JsonResponse(reponse)
Résultat à ce stade :
Si vous êtes arrivé⋅e jusque là, bravo ! Si vous avez des questions, n'hésitez pas à me demander en cours, ou par mail, mais regardez aussi la doc ou les forums, vous trouverez sans doute plus rapidement la réponse à votre problème !