Tutoriel Django - 6e partie : Vues génériques pour les listes et les détails
page(Doc) not found /fr/docs/Learn/Server-side/Django/SessionsCe tutoriel améliore notre site web LocalLibrary, en ajoutant des pages de listes et de détails pour les livres et les auteurs. Ici nous allons apprendre les vues génériques basées sur des classes, et montrer comment elles peuvent réduire le volume de code à écrire pour les cas ordinaires. Nous allons aussi entrer plus en détail dans la gestion des URLs, en montrant comment réaliser des recherches de patterns simples.
Prérequis: |
Avoir terminé tous les tutoriels précédents, y compris Django Tutorial Part 5: Creating our home page. |
---|---|
Objectif: |
Comprendre où et comment utiliser des vues génériques basées sur classes, et comment extraire des patterns dans des URLs pour transmettre les informations aux vues. |
Aperçu
Dans ce tutoriel, nous allons terminer la première version du site web LocalLibrary, en ajoutant des pages de listes et de détails pour les livres et les auteurs (ou pour être plus précis, nous allons vous montrer comment implémenter les pages concernant les livres, et vous faire créer vous-mêmes les pages concernant les auteurs !).
Le processus est semblable à celui utilisé pour créer la page d'index, processus que nous avons montré dans le tutoriel précédent. Nous allons avoir de nouveau besoin de créer des mappages d'URLs, des vues et des templates. La principale différence est que, pour la page des détails, nous allons avoir le défi supplémentaire d'extraire de l'URL des informations que nous transmettrons à la vue. Pour ces pages, nous allons montrer comment utiliser un type de vue complètement différent : des vues "listes" et "détails" génériques et basées sur des classes. Cela peut réduire significativement la somme de code nécessaire, les rendant ainsi faciles à écrire et à maintenir.
La partie finale de ce tutoriel montrera comment paginer vos données quand vous utilisez des vues "listes" génériques basées sur des classes.
Page de liste de livres
La page de liste des livres va afficher une liste de tous les enregistrements de livres disponibles, en utilisant l'URL: catalog/books/
. La page va afficher le titre et l'auteur pour chaque enregistrement, et le titre sera un hyperlien vers la page de détails associée. La page aura la même structure et la même zone de navigation que les autres pages du site, et nous pouvons dès lors étendre le template de base (base_generic.html) que nous avons créé dans le tutoriel précédent.
Mappage d'URL
Ouvrez le fichier /catalog/urls.py, et copiez-y la ligne en gras ci-dessous. Comme pour la page d'index, cette fonction path()
définit un pattern destiné à identifier l'URL ('books/'), une fonction vue qui sera appelée si l'URL correspond (views.BookListView.as_view()
), et un nom pour ce mappage particulier.
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
]
Comme discuté dans le tutoriel précédent, l'URL doit auparavant avoir identifié la chaîne /catalog
, aussi la vue ne sera réellement appelée que pour l'URL complète: /catalog/books/
.
La fonction vue a un format différent de celui que nous avions jusqu'ici : c'est parce que cette vue sera en réalité implémentée sous forme de classe. Nous allons la faire hériter d'une fonction vue générique existante, qui fait la plus grande partie de ce que nous souhaitons réaliser avec cette vue, plutôt que d'écrire notre propre fonction à partir de zéro.
En Django, on accède à la fonction appropriée d'une vue basée sur classe en appelant sa méthode de classe as_view()
. Cela a pour effet de créer une instance de la classe, et de s'assurer que les bonnes méthodes seront appelées lors de requêtes HTTP.
Vue (basée sur classe)
Nous pourrions assez aisément écrire la vue "liste de livres" comme une fonction ordinaire (comme notre précédente vue "index"), qui interrogerait la base de données pour tous les livres, et qui ensuite appellerait render()
pour passer la liste à un template spécifique. À la place, cependant, nous allons utiliser une vue "liste" générique, basée sur une classe (ListView
), une classe qui hérite d'une vue existante. Parce que la vue générique implémente déjà la plupart des fonctionnalités dont nous avons besoin et suit les meilleures pratiques Django, nous pourrons créer une vue "liste" plus robuste avec moins de code, moins de répétition, et au final moins de maintenance.
Ouvrez le fichier catalog/views.py, et copiez-y le code suivant à la fin :
from django.views import generic
class BookListView(generic.ListView):
model = Book
C'est tout ! La vue générique va adresser une requête à la base de données pour obtenir tous les enregistrements du modèle spécifié (Book
), et ensuite rendre un template situé à l'adresse /locallibrary/catalog/templates/catalog/book_list.html (que nous allons créer ci-dessous). À l'intérieur du template vous pouvez accéder à la liste de livres grâce à la variable de template appelée object_list
OU book_list
(c'est-à-dire l'appellation générique "the_model_name_list
").
Note :
Ce chemin étrange vers le lieu du template n'est pas une faute de frappe : les vues génériques cherchent les templates dans /application_name/the_model_name_list.html
(catalog/book_list.html
dans ce cas) à l'intérieur du répertoire /application_name/templates/
(/catalog/templates/
).
Vous pouvez ajouter des attributs pour changer le comportement par défaut utilisé ci-dessus. Par exemple, vous pouvez spécifier un autre fichier de template si vous souhaitez avoir plusieurs vues qui utilisent ce même modèle, ou bien vous pourriez vouloir utiliser un autre nom de variable de template, si book_list n'est pas intuitif par rapport à l'usage que vous faites de vos templates. Probablement, le changement le plus utile est de changer/filtrer le sous-ensemble des résultats retournés : au lieu de lister tous les livres, vous pourriez lister les 5 premiers livres lus par d'autres utilisateurs.
class BookListView(generic.ListView):
model = Book
context_object_name = 'my_book_list' # your own name for the list as a template variable
queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location
Ré-écrire des méthodes dans des vues basées sur classes
Bien que nous n'ayons pas besoin de le faire ici, sachez qu'il vous est possible de ré-écrire des méthodes de classe.
Par exemple, nous pouvons ré-écrire la méthode get_queryset()
pour changer la liste des enregistrements retournés. Cette façon de faire est plus flexible que simplement définir l'attribut queryset
, comme nous l'avons fait dans le précédent fragment de code (bien qu'il n'y ait pas vraiment d'intérêt dans ce cas) :
class BookListView(generic.ListView):
model = Book
def get_queryset(self):
return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
Nous pourrions aussi réécrire get_context_data()
, afin d'envoyer au template des variables de contexte supplémentaires (par défaut c'est la liste de livres qui est envoyée). Le bout de code ci-dessous montre comment ajouter une variable appelée "some_data
" au contexte (elle sera alors accessible comme variable de template).
class BookListView(generic.ListView):
model = Book
def get_context_data(self, **kwargs):
# Call the base implementation first to get the context
context = super(BookListView, self).get_context_data(**kwargs)
# Create any data and add it to the context
context['some_data'] = 'This is just some data'
return context
Quand vous faites cela, il est important de suivre la procédure indiquée ci-dessus :
- D'abord récupérer auprès de la superclasse le contexte existant.
- Ensuite ajouter la nouvelle information de contexte.
- Enfin retourner le nouveau contexte (mis à jour).
Note : Voyez dans Built-in class-based generic views (doc de Django) pour avoir beaucoup plus d'exemples de ce que vous pouvez faire.
Créer le template pour la Vue Liste
Créez le fichier HTML /locallibrary/catalog/templates/catalog/book_list.html, et copiez-y le texte ci-dessous. Comme nous l'avons dit ci-dessus, c'est ce fichier que va chercher par défaut la classe générique "liste" basée sur une vue (dans le cas d'un modèle appelé Book
, dans une application appelée catalog
).
Les templates pour vues génériques sont exactement comme les autres templates (cependant, bien sûr, le contexte et les informations envoyées au templates peuvent être différents). Comme pour notre template index, nous étendons notre template de base à la première ligne, et remplaçons ensuite le bloc appelé content
.
{% extends "base_generic.html" %}
{% block content %}
<h1>Book List</h1>
{% if book_list %}
<ul>
{% for book in book_list %}
<li>
<a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}})
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no books in the library.</p>
{% endif %}
{% endblock %}
La vue envoie le contexte (liste de livres), en utilisant par défaut les alias object_list
et book_list
; l'un et l'autre fonctionnent.
Exécution conditionnelle
Nous utilisons les balises de templates if
, else
, et endif
pour vérifier que la book_list
a été définie et n'est pas vide. Si book_list
est vide, alors la condition else
affiche un texte expliquant qu'il n'y a pas de livres à lister. Si book_list
n'est pas vide, nous parcourons la liste de livres.
{% if book_list %}
<!-- code here to list the books -->
{% else %}
<p>There are no books in the library.</p>
{% endif %}
La condition ci-dessus ne vérifie qu'un seul cas, mais vous pouvez ajouter d'autres tests grâce à la balise de template elif
(par exemple {% elif var2 %}
). Pour plus d'information sur les opérateurs conditionnels, voyez ici : if, ifequal/ifnotequal, et ifchanged dans Built-in template tags and filters (Django Docs).
Boucles for
Le template utilise les balises de template for et endfor
pour boucler à travers la liste de livres, comme montré ci-dessous. Chaque itération peuple la variable de template book
avec l'information concernant l'élément courant de la liste.
{% for book in book_list %}
<li> <!-- code here get information from each book item --> </li>
{% endfor %}
Bien que nous ne l'utilisions pas ici, Django, à l'intérieur de la boucle, va aussi créer d'autres variables que vous pouvez utiliser pour suivre l'itération. Par exemple, vous pouvez tester la variable forloop.last
pour réaliser une action conditionnelle au dernier passage de la boucle.
Accéder aux variables
Le code à l'intérieur de la boucle crée un élément de liste pour chaque livre, élément qui montre à la fois le titre (comme lien vers la vue détail, encore à créer), et l'auteur.
<a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}})
Nous accédons aux champs de l'enregistrement "livre" associé, en utilisant la notation "à points" (par exemple book.title
et book.author
), où le texte suivant l'item book
est le nom du champ (comme défini dans le modèle).
Nous pouvons aussi appeler des fonctions contenues dans le modèle depuis l'intérieur de notre template — dans ce cas nous appelons Book.get_absolute_url()
pour obtenir une URL que vous pouvez utiliser pour afficher dans la vue détail l'enregistrement associé. Cela fonctionne, pourvu que la fonction ne comporte pas d'arguments (il n'y a aucun moyen de passer des arguments !).
Note : Il nous faut être quelque peu attentifs aux "effets de bord" quand nous appelons des fonctions dans nos templates. Ici nous récupérons simplement une URL à afficher, mais une fonction peut faire à peu près n'importe quoi — nous ne voudrions pas effacer notre base de données (par exemple) juste parce que nous avons affiché notre template !
Mettre à jour le template de base
Ouvrez le template de base (/locallibrary/catalog/templates/base_generic.html) et insérez {% url 'books' %} dans le lien URL pour All books, comme indiqué ci-dessous. Cela va afficher le lien dans toutes les pages (nous pouvons mettre en place ce lien avec succès, maintenant que nous avons créé le mappage d'URL "books").
<li><a href="{% url 'index' %}">Home</a></li>
<li><a href="{% url 'books' %}">All books</a></li>
<li><a href="">All authors</a></li>
À quoi cela ressemble-t-il ?
Vous ne pouvez pas encore construire la liste des livres, car il nous manque toujours une dépendance, à savoir le mappage d'URL pour la page de détail de chaque livre, qui est requise pour créer des hyperliens vers chaque livre. Nous allons montrer les vues liste et détail après la prochaine section.
Page de détail d'un livre
La page de détail d'un livre va afficher les informations sur un livre précis, auquel on accède en utilisant l'URL catalog/book/<id>
(où <id>
est la clé primaire pour le livre). En plus des champs définis dans le modèle Book
(auteur, résumé, ISBN, langue et genre), nous allons aussi lister les détails des copies disponibles (BookInstances
), incluant le statut, la date de retour prévue, la marque d'éditeur et l'id. Cela permettra à nos lecteurs, non seulement de s'informer sur le livre, mais aussi de confirmer si et quand il sera disponible.
Mappage d'URL
Ouvrez /catalog/urls.py et ajoutez-y le mappeur d'URL 'book-detail' indiqué en gras ci-dessous. Cette fonction path()
définit un pattern, la vue générique basée sur classe qui lui est associée, ainsi qu'un nom.
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
]
Pour le chemin book-detail, le pattern d'URL utilise une syntaxe spéciale pour capturer l'id exact du livre que nous voulons voir. La syntaxe est très simple : les chevrons ('<' et '>') définissent la partie de l'URL qui doit être capturée et encadrent le nom de la variable que la vue pourra utiliser pour accéder aux données capturées. Par exemple, <something> va capturer le pattern marqué et passer la valeur à la vue en tant que variable "something". De manière optionnelle, vous pouvez faire précéder le nom de variable d'une spécification de convertisseur, qui définit le type de la donnée (int, str, slug, uuid, path).
Dans ce cas, nous utilisons '<int:pk>'
pour capturer l'id du livre, qui doit être une chaîne formatée d'une certaine manière, et passer cet id à la vue en tant que paramètre nommé pk
(abréviation pour primary key - clé primaire). C'est l'id qui doit être utilisé pour stocker le livre de manière unique dans la base de données, comme défini dans le modèle Book.
Note :
Comme nous l'avons dit précédemment, notre URL correcte est en réalité catalog/book/<digits>
(comme nous sommes dans l'application catalog, /catalog/
est supposé).
Attention : La vue générique basée sur classe "détail" s'attend à recevoir un paramètre appelé pk. Si vous écrivez votre propre fonction, vous pouvez utiliser le nom que vous voulez pour votre paramètre, ou même passer l'information avec un argument non nommé.
Introduction aux chemins et expressions régulières avancés
Note : Vous n'aurez pas besoin de cette section pour achever le tutoriel ! Nous en parlons parce que nous savons que cette option vous sera probablement utile dans votre avenir centré sur Django.
La recherche de pattern fournie par path()
est simple et utile pour les cas (très communs) où vous voulez seulement capturer n'importe quelle chaîne ou entier. Si vous avez besoin d'un filtre plus affiné (par exemple pour filtrer seulement les chaînes qui ont un certain nombre de caractères), alors vous pouvez utiliser la méthode re_path().
Cette méthode est utilisée exactement comme path()
, sauf qu'elle vous permet de spécifier un pattern utilisant une Expression régulière. Par exemple, le chemin précédent pourrait avoir été écrit ainsi :
re_path(r'^book/(?P<pk>\d+)