# Documentation Django 6.0.7

# Vues génériques fondées sur les classes, fournies par Django

L’écriture d’applications Web peut être une tâche monotone, car certains motifs se répètent encore et encore. Django essaie d’enlever une partie de cette monotonie au niveau des modèles et des gabarits, mais les développeurs Web font également face à ces répétitions au niveau des vues.

Les vues génériques de Django ont été développées pour résoudre ce problème. Elles s’attachent à trouver des motifs ou idiomes courants dans le développement des vues et à les abstraire de façon à ce qu’on puisse rapidement écrire des vues courantes sans devoir écrire trop de code.

Il est possible d’identifier certaines tâches courantes, comme l’affichage d’une liste d’objets, et d’écrire du code qui affiche une liste de n’importe quel objet. Puis, le modèle concerné peut être transmis comme paramètre supplémentaire dans la configuration d’URL.

Django est livré avec des vues génériques pour accomplir les choses suivantes :

- Afficher des pages de liste et de détail pour un seul type d’objet. Si nous devions créer une application pour gérer des conférences, une vue `TalkListView` et une vue `RegisteredUserListView` seraient des exemples de vues de listes. Une page d’une seule conférence pourrait être un exemple de ce que nous appelons une vue de « détail ».

- Présenter des objets datés dans des pages d’archive par année/mois/jour, avec les détails associés et des pages « éléments récents ».

- Permettre à des utilisateurs de créer, mettre à jour et supprimer des objets, avec ou sans gestion des autorisations.

Considérées globalement, ces vues offrent des interfaces pour effectuer les tâches les plus courantes que rencontrent les développeurs.

## Extension des vues génériques

Il est indiscutable que l’utilisation des vues génériques peut considérablement accélérer le développement. Dans la plupart des projets, cependant, on atteint souvent un stade où les vue génériques ne suffisent plus. En fait, la question la plus fréquemment posée par les nouveaux développeurs Django est de savoir comment il est possible de gérer plus de variétés de situations avec les vues génériques.

C’est l’une des raisons pour lesquelles les vues génériques ont été revues pour la version 1.3 ; précédemment, il s’agissait de fonctions de vue avec une très large diversité d’options. Maintenant, au lieu de transmettre une grosse partie de configuration dans la configuration d’URL, la manière recommandée d’étendre les vues génériques est d’en faire des sous-classes et de surcharger leurs attributs et méthodes.

Ceci dit, les vues génériques ont leurs limites. Si vous vous retrouvez à vous battre pour implémenter vos vues comme des sous-classes de vues génériques, il se pourrait qu’il soit plus efficace d’écrire simplement le code dont vous avez besoin dans vos vues, que ce soit par des fonctions ou des classes.

Vous trouverez davantage d’exemples de vues génériques dans certaines applications tierces, mais n’hésitez pas à écrire vos propres vues selon vos besoins.

## Vues génériques d’objets

`TemplateView` est certainement utile, mais les vues génériques de Django exposent tout leur potentiel lorsqu’il s’agit de présenter des vues de contenu de la base de données. Dans la mesure où c’est une tâche très courante, Django contient un ensemble de vues génériques intégrées pour aider à générer des vues de liste et de détail pour les objets.

Commençons par examiner quelques exemples d’affichage d’une liste d’objets ou d’un objet individuel.

Nous allons utiliser ces modèles :

    # models.py
    from django.db import models

    class Publisher(models.Model):
        name = models.CharField(max_length=30)
        address = models.CharField(max_length=50)
        city = models.CharField(max_length=60)
        state_province = models.CharField(max_length=30)
        country = models.CharField(max_length=50)
        website = models.URLField()

        class Meta:
            ordering = ["-name"]

        def __str__(self):
            return self.name

    class Author(models.Model):
        salutation = models.CharField(max_length=10)
        name = models.CharField(max_length=200)
        email = models.EmailField()
        headshot = models.ImageField(upload_to="author_headshots")

        def __str__(self):
            return self.name

    class Book(models.Model):
        title = models.CharField(max_length=100)
        authors = models.ManyToManyField("Author")
        publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
        publication_date = models.DateField()

Nous devons maintenant définir une vue :

    # views.py
    from django.views.generic import ListView
    from books.models import Publisher

    class PublisherListView(ListView):
        model = Publisher

Et finalement connecter cette vue à une URL :

    # urls.py
    from django.urls import path
    from books.views import PublisherListView

    urlpatterns = [
        path("publishers/", PublisherListView.as_view()),
    ]

Voilà tout le code Python qu’il suffit d’écrire. Il faut cependant encore écrire un gabarit. Nous pourrions indiquer explicitement à la vue le gabarit à utiliser en lui ajoutant un attribut `template_name`, mais en l’absence d’un gabarit explicite, Django déduit un nom de gabarit à partir du nom de l’objet. Dans ce cas, le gabarit « automatique » sera `"books/publisher_list.html"`, la partie « books » provient du nom de l’application contenant ce modèle alors que « publisher » provient du nom du modèle en minuscules.

Note

Ainsi, lorsque (par exemple) l’option `APP_DIRS` d’un moteur `DjangoTemplates` est définie à `True` dans `TEMPLATES`, un emplacement de gabarit pourrait être : `/chemin/vers/projet/books/templates/books/publisher_list.html`.

Ce gabarit sera rendu avec un contexte contenant une variable appelée `object_list` contenant elle-même les objets `Publisher`. Un gabarit pourrait ressembler à ceci :

    {% extends "base.html" %}

    {% block content %}
        <h2>Publishers</h2>
        <ul>
            {% for publisher in object_list %}
                <li>{{ publisher.name }}</li>
            {% endfor %}
        </ul>
    {% endblock %}

Et voilà, tout est là. Toutes les fonctionnalités sympathiques des vues génériques proviennent des modifications d’attributs définis dans la vue générique. La référence des vues génériques documente en détails toutes les vues génériques et leurs options. La suite de de document s’attelle à décrire certaines manières fréquentes de personnaliser et étendre les vues génériques.

### Construction de contextes de gabarits « astucieux »

Vous avez peut-être constaté que notre exemple de gabarit de liste d’éditeurs stocke tous les éditeurs dans une variable nommée `object_list`. Même si cela fonctionne très bien, ce n’est pas très « sympa » pour les auteurs de gabarits : ils doivent simplement savoir qu’ils manipulent ici des éditeurs.

Si vous avez affaire à un objet de modèle, c’est déjà fait pour vous. Lorsque vous avez affaire à un objet ou un jeu de requête, Django est capable de renseigner le contexte en utilisant la version en minuscules du nom de la classe de modèle. Ceci s’ajoute à l’élément `object_list` par défaut, mais contient exactement les mêmes données, c’est-à-dire `publisher_list`.

Si ce n’est toujours pas un bon nom, vous pouvez indiquer explicitement le nom de la variable de contexte. L’attribut `context_object_name` d’une vue générique indique le nom de variable de contexte à utiliser :

    # views.py
    from django.views.generic import ListView
    from books.models import Publisher

    class PublisherListView(ListView):
        model = Publisher
        context_object_name = "my_favorite_publishers"

La mise à disposition d’un nom `context_object_name` signifiant est toujours une bonne idée. Vos collaborateurs rédacteurs de gabarits vous remercieront.

### Ajout de contexte supplémentaire

Il est souvent nécessaire de compléter par quelques informations supplémentaires au-delà de ce que la vue générique fournit. Par exemple, on peut penser à afficher une liste de livres sur la page de détail des éditeurs. La vue générique `DetailView` place l’éditeur dans le contexte, mais comment ajouter d’autres informations dans le gabarit ?

La réponse consiste à créer une sous-classe de `DetailView` et d’y placer votre propre implémentation de la méthode `get_context_data`. L’implémentation par défaut ajoute l’objet à afficher dans le gabarit, mais en la surchargeant, vous pouvez l’enrichir :

    from django.views.generic import DetailView
    from books.models import Book, Publisher

    class PublisherDetailView(DetailView):
        model = Publisher

        def get_context_data(self, **kwargs):
            # Call the base implementation first to get a context
            context = super().get_context_data(**kwargs)
            # Add in a QuerySet of all the books
            context["book_list"] = Book.objects.all()
            return context

Note

Généralement, `get_context_data` fusionne les données de contexte de toutes les classes parentes avec celles de la classe actuelle. Pour préserver ce comportement dans vos propres classes où vous souhaitez modifier le contexte, il faut prendre soin d’appeler `get_context_data` de la classe parente (`super`). Pour autant qu’il n’y ait pas deux classes essayant de modifier la même clé, le résultat sera correct. Cependant, si l’une des classes tente de surcharger une clé après qu’une classe parente l’a définie (après l’appel à `super`), toute classe enfant de cette classe devra aussi définir explicitement cette clé après `super` pour être certain de surcharger tous les parents. Si cela vous cause des ennuis, analysez l’ordre de résolution des méthodes de votre vue.

Un autre élément à signaler est que les données de contexte provenant des vues génériques basées sur les classes vont écraser les données fournies par les processeurs de contexte ; voir `get_context_data()` comme exemple.

### Affichage de sous-ensembles d’objets

Examinons maintenant un peu plus attentivement le paramètre `model` que nous avons toujours utilisé. Ce paramètre, qui définit le modèle de base de données sur lequel la vue va porter, est disponible pour toutes les vues génériques qui opèrent sur un seul objet ou une liste d’objets. Cependant, le paramètre `model` n’est pas le seul moyen de définir le type d’objet d’une vue, il est aussi possible de définir la liste des objets par le paramètre `queryset`:

    from django.views.generic import DetailView
    from books.models import Publisher

    class PublisherDetailView(DetailView):
        context_object_name = "publisher"
        queryset = Publisher.objects.all()

L’indication de `model = Publisher` est un raccourci pour dire `queryset = Publisher.objects.all()`. Cependant, en utilisant `queryset` pour définir une liste d’objets filtrée, vous pouvez spécifier plus finement les objets qui seront visibles dans la vue (voir Création de requêtes pour plus d’informations à propos des objets `QuerySet`, et consultez la référence des vues fondées sur les classes pour obtenir tous les détails).

Comme exemple, essayons de trier une liste de livres par date de publication, les plus récents en premier :

    from django.views.generic import ListView
    from books.models import Book

    class BookListView(ListView):
        queryset = Book.objects.order_by("-publication_date")
        context_object_name = "book_list"

Il s’agit d’un exemple très minimal, mais qui montre bien le principe de fonctionnement. Le but est généralement de faire plus que de simplement trier les objets. Si vous souhaitez présenter une liste de livres provenant d’un éditeur particulier, la même technique peut être utilisée :

    from django.views.generic import ListView
    from books.models import Book

    class AcmeBookListView(ListView):
        context_object_name = "book_list"
        queryset = Book.objects.filter(publisher__name="ACME Publishing")
        template_name = "books/acme_list.html"

Remarquez qu’en plus d’un paramètre `queryset` filtré, nous indiquons également un nom de gabarit personnalisé. Sans cela, la vue générique utiliserait le même gabarit que la liste d’objets « standard », ce qui ne correspondrait pas au but recherché.

Remarquez également que ce n’est pas une façon très élégante d’afficher les livres d’un éditeur particulier. Si nous voulions ensuite ajouter une page pour un autre éditeur, nous devrions ajouter des lignes supplémentaires dans la configuration d’URL et nous serions pratiquement limités à quelques éditeurs. Nous aborderons ce problème dans la section suivante.

Note

Si vous obtenez une erreur 404 en accédant à `/books/acme/`, vérifiez que vous possédez bien un objet `Publisher` dont le nom est « ACME Publishing ». Les vues génériques ont un paramètre `allow_empty` pour cette situation. Consultez la référence des vues fondées sur les classes pour plus de détails.

### Filtrage dynamique

Un autre besoin fréquent est de filtrer les objets d’une liste selon un critère de l’URL. Plus haut, nous avons figé le nom de l’éditeur dans la configuration d’URL, mais comment faire si nous voulions écrire une vue affichant tous les livres d’un éditeur librement choisi ?

Heureusement, la classe `ListView` possède une méthode `get_queryset()` que nous pouvons surcharger. Par défaut, elle renvoie la valeur du paramètre `queryset`, mais on peut l’utiliser pour ajouter de la logique supplémentaire.

L’élément clé pour que cela fonctionne est qu’au moment où les vues fondées sur les classes sont appelées, divers attributs utiles sont créés pour `self`. En plus de la requête elle-même (`self.request`), il y a aussi les paramètres positionnels (`self.args`) et nommés (`self.kwargs`) capturés en fonction de la configuration d’URL.

Ici, nous avons une configuration d’URL comportant un seul groupe capturé :

    # urls.py
    from django.urls import path
    from books.views import PublisherBookListView

    urlpatterns = [
        path("books/<publisher>/", PublisherBookListView.as_view()),
    ]

Puis, nous allons écrire la vue `PublisherBookListView`:

    # views.py
    from django.shortcuts import get_object_or_404
    from django.views.generic import ListView
    from books.models import Book, Publisher

    class PublisherBookListView(ListView):
        template_name = "books/books_by_publisher.html"

        def get_queryset(self):
            self.publisher = get_object_or_404(Publisher, name=self.kwargs["publisher"])
            return Book.objects.filter(publisher=self.publisher)

Utiliser `get_queryset` pour ajouter de la logique à la sélection du jeu de requête est aussi pratique que puissant. Nous pourrions par exemple filtrer en se basant sur l’utilisateur connecté (`self.request.user`) ou toute autre logique plus complexe.

Nous pouvons aussi ajouter en même temps l’éditeur dans le contexte, afin de pouvoir l’utiliser dans le gabarit :

    # ...

    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super().get_context_data(**kwargs)
        # Add in the publisher
        context["publisher"] = self.publisher
        return context

### Autres opérations supplémentaires

Le dernier usage que nous examinerons concerne les opérations supplémentaires effectuées avant ou après l’appel à la vue générique.

Imaginez que nous ayons un champ `last_accessed` dans le modèle `Author` qui nous sert à garder la trace du moment le plus récent où quelqu’un a consulté la page de cet auteur :

    # models.py
    from django.db import models

    class Author(models.Model):
        salutation = models.CharField(max_length=10)
        name = models.CharField(max_length=200)
        email = models.EmailField()
        headshot = models.ImageField(upload_to="author_headshots")
        last_accessed = models.DateTimeField()

La classe générique `DetailView` n’a aucune conscience de la présence de ce champ, mais nous pouvons une nouvelle fois écrire une vue personnalisée pour maintenir ce champ à jour.

Tout d’abord, nous devons ajouter une ligne de détail d’auteur dans la configuration d’URL pour faire le lien avec une vue personnalisée :

    from django.urls import path
    from books.views import AuthorDetailView

    urlpatterns = [
        # ...
        path("authors/<int:pk>/", AuthorDetailView.as_view(), name="author-detail"),
    ]

Puis il s’agit d’écrire la nouvelle vue. `get_object` étant la méthode qui récupère l’objet, nous la surchargeons en « enveloppant » l’appel par défaut :

    from django.utils import timezone
    from django.views.generic import DetailView
    from books.models import Author

    class AuthorDetailView(DetailView):
        queryset = Author.objects.all()

        def get_object(self):
            obj = super().get_object()
            # Record the last accessed date
            obj.last_accessed = timezone.now()
            obj.save()
            return obj

Note

Cette configuration d’URL emploie le group nommé `pk`, il s’agit là du nom par défaut utilisé par `DetailView` pour trouver la valeur de clé primaire à utiliser pour filtrer le jeu de requête.

Si vous aimeriez nommer différemment le groupe, vous pouvez définir `pk_url_kwarg` dans la vue.