# Documentation Django 5.2.8

# Utilisation de mixins avec les vues fondées sur les classes

Prudence

This is an advanced topic. A working knowledge of Django’s class-based views is advised before exploring these techniques.

Les vues fondées sur les classes intégrées dans Django fournissent de nombreuses fonctionnalités, mais certaines d’entre elles pourraient être utiles de manière indépendante. Par exemple, si l’on écrit une vue qui utilise un gabarit pour produire une réponse HTTP, mais sans utiliser `TemplateView`; ou encore si l’on veut utiliser un gabarit seulement pour une requête `POST` et faire quelque chose de totalement différent pour les requêtes `GET`. Même si on pourrait utiliser directement `TemplateResponse`, on se retrouverait avec pas mal de code dupliqué.

C’est pour cette raison que Django fournit aussi un certain nombre de mixins englobant des fonctionnalités de manière distincte. Le rendu de gabarits, par exemple, est isolé dans la classe `TemplateResponseMixin`. La documentation de référence de Django contient une documentation complète de toutes les classes mixins.

## Contexte et réponses par gabarit

Deux classes mixins centrales sont à disposition pour aider à présenter une interface cohérente pour travailler avec les gabarits dans les vues fondées sur les classes.

`TemplateResponseMixin`

  Every built in view which returns a `TemplateResponse` will call the `render_to_response()` method that `TemplateResponseMixin` provides. Most of the time this will be called for you (for instance, it is called by the `get()` method implemented by both `TemplateView` and `DetailView`); similarly, it’s unlikely that you’ll need to override it, although if you want your response to return something not rendered via a Django template then you’ll want to do it. For an example of this, see the JSONResponseMixin example.

  `render_to_response()` appelle elle-même `get_template_names()` qui consulte l’attribut `template_name` de la vue, par défaut. Deux autres mixins (`SingleObjectTemplateResponseMixin` et `MultipleObjectTemplateResponseMixin`) surchargent cette méthode pour offrir des valeurs par défaut plus souples lorsqu’il s’agit de manipuler des objets réels.

`ContextMixin`

  Every built in view which needs context data, such as for rendering a template (including `TemplateResponseMixin` above), should call `get_context_data()` passing any data they want to ensure is in there as keyword arguments. `get_context_data()` returns a dictionary; in `ContextMixin` it returns its keyword arguments, but it is common to override this to add more members to the dictionary. You can also use the `extra_context` attribute.

## Construction des vues fondées sur les classes de Django

Voyons comment deux des vues fondées sur les classes de Django sont construites à partir de mixins fournissant des fonctionnalités isolées. Nous examinerons `DetailView` qui produit une vue de détail d’un objet et `ListView` qui produit une liste d’objets, généralement à partir d’un jeu de requête, avec pagination facultative. Ceci nous amènera à étudier quatre mixins qui, combinées entre elles, fournissent des fonctionnalités utiles lors du traitement d’un objet Django unique ou d’un ensemble d’objets.

Il existe également des classes mixins dans les vues génériques d’édition (`FormView` et les vues spécifiques aux modèles `CreateView`, `UpdateView` et `DeleteView`), ainsi que dans les vues génériques centrées sur les dates. Ces classes sont décrites dans la documentation de référence des mixins.

### `DetailView`: traitement d’un seul objet Django

Pour afficher le détail d’un objet, il y a fondamentalement deux choses à faire : récupérer l’objet, puis produire une réponse `TemplateResponse` à l’aide du gabarit adéquat, en transmettant l’objet dans le contexte.

To get the object, `DetailView` relies on `SingleObjectMixin`, which provides a `get_object()` method that figures out the object based on the URL of the request (it looks for `pk` and `slug` keyword arguments as declared in the URLConf, and looks the object up either from the `model` attribute on the view, or the `queryset` attribute if that’s provided). `SingleObjectMixin` also overrides `get_context_data()`, which is used across all Django’s built in class-based views to supply context data for template renders.

To then make a `TemplateResponse`, `DetailView` uses `SingleObjectTemplateResponseMixin`, which extends `TemplateResponseMixin`, overriding `get_template_names()` as discussed above. It actually provides a fairly sophisticated set of options, but the main one that most people are going to use is `<app_label>/<model_name>_detail.html`. The `_detail` part can be changed by setting `template_name_suffix` on a subclass to something else. (For instance, the generic edit views use `_form` for create and update views, and `_confirm_delete` for delete views.)

### `ListView`: traitement de plusieurs objets Django

Les listes d’objets suivent grosso modo le même modèle : récupérer d’abord une liste d’objets (potentiellement paginés), typiquement un objet `QuerySet`, puis produire une réponse `TemplateResponse` avec le gabarit adéquat pour exploiter cette liste d’objets.

Pour obtenir les objets, `ListView` utilise `MultipleObjectMixin` qui offre à la fois `get_queryset()` et `paginate_queryset()`. Au contraire de `SingleObjectMixin`, il n’est pas nécessaire d’identifier des éléments d’URL pour déterminer le jeu de requête à traiter ; le comportement par défaut est d’utiliser l’attribut `queryset` ou `model` de la classe de vue. Une raison fréquente de vouloir surcharger ici `get_queryset()` est d’adapter la liste d’objets de manière dynamique, par exemple en fonction de l’utilisateur actuel ou pour exclure des articles dont la date est dans le futur dans un blog.

`MultipleObjectMixin` also overrides `get_context_data()` to include appropriate context variables for pagination (providing dummies if pagination is disabled). It relies on `object_list` being passed in as a keyword argument, which `ListView` arranges for it.

Pour produire une réponse `TemplateResponse`, `ListView` utilise ensuite `MultipleObjectTemplateResponseMixin`; comme pour `SingleObjectTemplateResponseMixin` ci-dessus, `get_template_names()` est surchargée pour fournir une `série d'options` dont la plus utilisée est le nom de gabarit `<étiquette_application>/<nom_modèle>_list.html` où la partie `_list` provient aussi de l’attribut `template_name_suffix` (les vues génériques centrées sur les dates utilisent des suffixes tels que `_archive`, `_archive_year`, etc. pour utiliser différents gabarits correspondant aux différentes vues de listes spécialisées pour les dates).

## Utilisation des mixins de vues fondées sur les classes de Django

Maintenant que nous avons vu comment les classes génériques fondées sur les classes de Django utilisent les mixins à disposition, examinons d’autres façons de les combiner. Nous allons toujours les combiner avec d’autres vues intégrées ou génériques fondées sur les classes, mais les cas d’utilisation auxquels Django répond ne recouvrent pas toutes les situations plus rares auxquelles on peut être confronté.

Avertissement

On ne peut pas combiner tous les mixins, ni combiner n’importe quel mixin avec toutes les vues génériques fondées sur les classes. Nous présentons ici quelques exemples qui fonctionnent ; si vous souhaitez construire d’autres fonctionnalités, vous devrez étudier les interactions entre les attributs et les méthodes qui se chevauchent entre les différentes classes utilisées, ainsi que la façon dont l’ordre de résolution des méthodes affecte la version des méthodes appelée et l’ordre de ces appels.

La documentation de référence des vues fondées sur les classes et des mixins de vues fondées sur les classes de Django vous aidera à comprendre quels attributs et méthodes peuvent être la source de conflits entre les différentes classes et mixins.

Dans le doute, il est souvent préférable de simplifier les choses en se basant sur `View` ou `TemplateView`, peut-être avec `SingleObjectMixin` ou `MultipleObjectMixin`. Même si vous devez finalement écrire un peu plus de code, il sera probablement plus compréhensible pour quelqu’un devant s’y atteler plus tard, et en réduisant les interactions à surveiller, vous vous économiserez un peu de temps de réflexion (il est bien sûr toujours possible de se plonger dans l’implémentation de Django des vues génériques fondées sur les classes pour y chercher de l’inspiration sur la façon de traiter les problèmes).

### Utilisation de `SingleObjectMixin` avec `View`

Si nous voulons écrire une classe de vue ne répondant qu’aux requêtes `POST`, nous héritons de `View` et écrivons une méthode `post()` dans la sous-classe. Cependant, si le traitement doit porter sur un objet particulier identifié par l’URL, il est souhaitable de profiter de la fonctionnalité offerte par `SingleObjectMixin`.

We’ll demonstrate this with the `Author` model we used in the generic class-based views introduction.

`views.py`

    from django.http import HttpResponseForbidden, HttpResponseRedirect
    from django.urls import reverse
    from django.views import View
    from django.views.generic.detail import SingleObjectMixin
    from books.models import Author

    class RecordInterestView(SingleObjectMixin, View):
        """Records the current user's interest in an author."""

        model = Author

        def post(self, request, *args, **kwargs):
            if not request.user.is_authenticated:
                return HttpResponseForbidden()

            # Look up the author we're interested in.
            self.object = self.get_object()
            # Actually record interest somehow here!

            return HttpResponseRedirect(
                reverse("author-detail", kwargs={"pk": self.object.pk})
            )

En pratique, l’intérêt devrait probablement être enregistré dans un stockage clé-valeur plutôt que dans une base de données relationnelle, nous avons donc laissé cet aspect de côté. La seule partie de la vue qui peut profiter de l’utilisation de `SingleObjectMixin` est l’endroit où il s’agit de récupérer l’auteur concerné par l’intérêt, ce qui est fait en appelant `self.get_object()`. Tout le reste est pris en charge pour nous par le mixin.

Nous pouvons assez facilement brancher cette vue dans nos URL :

`urls.py`

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

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

Remarquez le groupe nommé `pk` qui sera utilisé par `get_object()` pour rechercher l’instance `Author`. Il est aussi possible d’utiliser un « slug » ou toute autre fonctionnalité de `SingleObjectMixin`.

### Utilisation de `SingleObjectMixin` avec `ListView`

`ListView` intègre la pagination, mais il peut être par exemple souhatiable de paginer une liste d’objets qui sont tous liés (par clé étrangère) à un autre objet. Dans notre exemple de publication, on pourrait vouloir paginer tous les livres d’un éditeur particulier.

Une façon de faire cela serait de combiner `ListView` avec `SingleObjectMixin`, afin que le jeu de requête de la liste de livres paginée puisse dépendre de l’éditeur trouvé comme objet unique. Afin de faire cela, il est nécessaire d’avoir deux jeux de requête différents :

Le jeu de requête `Book` utilisé par `ListView`

  Comme nous avons accès à l’éditeur `Publisher` des livres que nous souhaitons afficher, nous surchargeons `get_queryset()` et utilisons le gestionnaire inverse de clé étrangère de l’objet `Publisher`.

`Publisher` queryset for use in `get_object()`

  Nous comptons sur l’implémentation par défaut de `get_object()` pour récupérer le bon objet `Publisher`. Cependant, nous devons explicitement transmettre un paramètre `queryset` car sinon, l’implémentation par défaut de `get_object()` appellerait `get_queryset()` que nous avons surchargée pour renvoyer des objets `Book` au lieu d’objets `Publisher`.

Note

Il faut être prudent avec `get_context_data()`. Comme les deux classes `SingleObjectMixin` et `ListView` vont placer des éléments dans les données de contexte sous la valeur `context_object_name`, si elle est définie, nous allons plutôt nous assurer de manière explicite que l’objet `Publisher` se trouve bien dans les données de contexte. `ListView` se chargera d’ajouter les contenus `page_obj` et `paginator` adéquats pour autant que nous n’oubliions pas d’appeler `super()`.

Nous pouvons maintenant écrire une nouvelle vue `PublisherDetailView`:

    from django.views.generic import ListView
    from django.views.generic.detail import SingleObjectMixin
    from books.models import Publisher

    class PublisherDetailView(SingleObjectMixin, ListView):
        paginate_by = 2
        template_name = "books/publisher_detail.html"

        def get(self, request, *args, **kwargs):
            self.object = self.get_object(queryset=Publisher.objects.all())
            return super().get(request, *args, **kwargs)

        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context["publisher"] = self.object
            return context

        def get_queryset(self):
            return self.object.book_set.all()

Remarquez notre manière de définir `self.object` dans `get()` afin que nous puissions ensuite l’utiliser dans `get_context_data()` et `get_queryset()`. Si vous ne définissez pas `template_name`, le nom du gabarit prendra la valeur par défaut choisie par `ListView`, ce qui donnera dans ce cas `"books/book_list.html"` car il s’agit d’une liste de livres. `ListView` n’a pas conscience de la présence de `SingleObjectMixin`, elle ne sait donc pas du tout que cette vue a quelque chose à voir avec un objet `Publisher`.

Nous avons délibérément défini `paginate_by` à un petit nombre dans l’exemple afin que vous n’ayez pas à créer beaucoup de livres pour voir la pagination à l’œuvre ! Voici le gabarit que vous pourriez utiliser :

    {% extends "base.html" %}

    {% block content %}
        <h2>Publisher {{ publisher.name }}</h2>

        <ol>
          {% for book in page_obj %}
            <li>{{ book.title }}</li>
          {% endfor %}
        </ol>

        <div class="pagination">
            <span class="step-links">
                {% if page_obj.has_previous %}
                    <a href="?page={{ page_obj.previous_page_number }}">previous</a>
                {% endif %}

                <span class="current">
                    Page {{ page_obj.number }} of {{ paginator.num_pages }}.
                </span>

                {% if page_obj.has_next %}
                    <a href="?page={{ page_obj.next_page_number }}">next</a>
                {% endif %}
            </span>
        </div>
    {% endblock %}

## Risque de trop grande complexité

Généralement, il est possible d’utiliser `TemplateResponseMixin` et `SingleObjectMixin` lorsque leurs fonctionnalités sont nécessaires. Comme montré plus haut, en prenant certaines précautions, on peut même combiner `SingleObjectMixin` avec `ListView`. Cependant, les choses deviennent de plus en plus complexes avec ces combinaisons ; voici une bonne règle générale :

Indication

Each of your views should use only mixins or views from one of the groups of generic class-based views: detail, list, editing and date. For example it’s fine to combine `TemplateView` (built in view) with `MultipleObjectMixin` (generic list), but you’re likely to have problems combining `SingleObjectMixin` (generic detail) with `MultipleObjectMixin` (generic list).

Pour montrer ce qui arrive quand on essaie de trop compliquer, nous présentons un exemple qui sacrifie la lisibilité et la maintenabilité alors qu’il existe une solution plus simple. Examinons d’abord une tentative naïve de combiner `DetailView` avec `FormMixin` pour permettre l’envoi `POST` par un formulaire `Form` Django vers la même URL qui est utilisée pour afficher un objet avec `DetailView`.

### Utilisation de `FormMixin` avec `DetailView`

Rappelez-vous notre exemple précédent qui combinait `View` et `SingleObjectMixin`. Nous enregistrions l’intérêt d’un utilisateur pour un auteur particulier. Admettons que nous voulions maintenant donner la possibilité d’écrire un message motivant leur intérêt. Nous partons encore une fois du principe que nous ne stockerons pas cela dans une base de données relationnelle, mais plutôt dans quelque chose de plus exotique dont nous ne nous soucierons pas dans cet exemple.

À ce stade, il est naturel de faire appel à un formulaire `Form` pour englober les informations envoyées par le navigateur de l’utilisateur vers Django. En admettant que nous adhérons aussi aux principes de REST, nous aimerions utiliser la même URL pour l’affichage de l’auteur que pour la capture du message en provenance de l’utilisateur. Réécrivons notre vue `AuthorDetailView` dans cette optique.

Nous conserverons le traitement `GET` de la classe `DetailView`, même s’il faudra rajouter un formulaire `Form` dans les données de contexte pour pouvoir l’afficher dans le gabarit. Nous allons aussi profiter du traitement de formulaire de `FormMixin` et écrire un peu de code afin qu’en cas d’envoi `POST`, le formulaire soit instancié de manière appropriée.

Note

Nous utilisons `FormMixin` et implémentons nous-même la méthode `post()` plutôt que d’essayer de mélanger `DetailView` avec `FormView` (qui fournit déjà une méthode `post()` exploitable), parce que ces deux vues implémentent `get()` et que cela risquerait d’ajouter de la confusion.

Voici à quoi ressemble notre nouvelle vue `AuthorDetailView`:

    # CAUTION: you almost certainly do not want to do this.
    # It is provided as part of a discussion of problems you can
    # run into when combining different generic class-based view
    # functionality that is not designed to be used together.

    from django import forms
    from django.http import HttpResponseForbidden
    from django.urls import reverse
    from django.views.generic import DetailView
    from django.views.generic.edit import FormMixin
    from books.models import Author

    class AuthorInterestForm(forms.Form):
        message = forms.CharField()

    class AuthorDetailView(FormMixin, DetailView):
        model = Author
        form_class = AuthorInterestForm

        def get_success_url(self):
            return reverse("author-detail", kwargs={"pk": self.object.pk})

        def post(self, request, *args, **kwargs):
            if not request.user.is_authenticated:
                return HttpResponseForbidden()
            self.object = self.get_object()
            form = self.get_form()
            if form.is_valid():
                return self.form_valid(form)
            else:
                return self.form_invalid(form)

        def form_valid(self, form):
            # Here, we would record the user's interest using the message
            # passed in form.cleaned_data['message']
            return super().form_valid(form)

`get_success_url()` sert à indiquer une destination de redirection, ce qui est utilisé dans l’implémentation par défaut de `form_valid()`. Nous devons fournir notre propre méthode `post()`, comme expliqué précédemment.

### Une meilleure solution

Le nombre d’interactions subtiles entre `FormMixin` et `DetailView` éprouve déjà nos capacités conceptuelles. Il est improbable que vous ayez envie d’écrire vous-même ce genre de classe.

Dans ce cas, vous pourriez écrire vous-même la méthode `post()`, en conservant `DetailView` comme seule fonctionnalité générique, même si l’écriture du code de gestion des formulaires implique beaucoup de duplication.

Sinon, une approche certainement plus économique que ci-dessus serait d’avoir une vue séparée pour le traitement du formulaire, ce qui permettrait d’utiliser sans problème `FormView` séparément de `DetailView`.

### Une meilleure solution alternative

En réalité, ce que nous essayons de faire ici est d’utiliser deux vues classes différentes pour la même URL. Pourquoi donc ne pas simplement faire cela ? La division est ici très claire : les requêtes `GET` devraient aboutir à la vue `DetailView` (avec en plus le formulaire dans les données de contexte) et les requêtes `POST` à la vue `FormView`. Mettons d’abord en place ces deux vues.

La vue `AuthorDetailView` est presque la même que celle que nous avons présentée en introduction. Nous devons écrire notre propre méthode `get_context_data()` pour mettre à disposition `AuthorInterestForm` dans le gabarit. Nous omettons la surcharge de `get_object()` de la version précédente par souci de clarté :

    from django import forms
    from django.views.generic import DetailView
    from books.models import Author

    class AuthorInterestForm(forms.Form):
        message = forms.CharField()

    class AuthorDetailView(DetailView):
        model = Author

        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context["form"] = AuthorInterestForm()
            return context

Puis pour `AuthorInterestFormView`, il s’agit d’une sous-classe de `FormView`, mais nous devons y adjoindre `SingleObjectMixin` pour pouvoir obtenir l’auteur concerné par l’action en cours et ne pas oublier de définir `template_name` pour s’assurer que les erreurs de formulaires soient affichées dans le même gabarit que `AuthorDetailView` utilise lors de la requête `GET`:

    from django.http import HttpResponseForbidden
    from django.urls import reverse
    from django.views.generic import FormView
    from django.views.generic.detail import SingleObjectMixin

    class AuthorInterestFormView(SingleObjectMixin, FormView):
        template_name = "books/author_detail.html"
        form_class = AuthorInterestForm
        model = Author

        def post(self, request, *args, **kwargs):
            if not request.user.is_authenticated:
                return HttpResponseForbidden()
            self.object = self.get_object()
            return super().post(request, *args, **kwargs)

        def get_success_url(self):
            return reverse("author-detail", kwargs={"pk": self.object.pk})

Finally we bring this together in a new `AuthorView` view. We already know that calling `as_view()` on a class-based view gives us something that behaves exactly like a function based view, so we can do that at the point we choose between the two subviews.

You can pass through keyword arguments to `as_view()` in the same way you would in your URLconf, such as if you wanted the `AuthorInterestFormView` behavior to also appear at another URL but using a different template:

    from django.views import View

    class AuthorView(View):
        def get(self, request, *args, **kwargs):
            view = AuthorDetailView.as_view()
            return view(request, *args, **kwargs)

        def post(self, request, *args, **kwargs):
            view = AuthorInterestFormView.as_view()
            return view(request, *args, **kwargs)

Cette approche peut également être employée avec toute autre vue fondée sur les classes, générique ou créée par vos soins, héritant directement de `View` ou de `TemplateView`, car elle conserve les différentes vues aussi distinctes que possible.

## Plus que du simple code HTML

Les vues fondées sur les classes se révèlent particulièrement utiles au moment où l’on a besoin de répéter plusieurs fois un processus semblable. Imaginez qu’il faille écrire une API et que chaque vue doive renvoyer du JSON au lieu de produire une page HTML.

Il est alors possible de créer une classe mixin afin de l’utiliser dans chaque vue, gérant ainsi à un seul endroit la conversion vers le format JSON.

Par exemple, une classe mixin pour JSON pourrait ressembler à ceci :

    from django.http import JsonResponse

    class JSONResponseMixin:
        """
        A mixin that can be used to render a JSON response.
        """

        def render_to_json_response(self, context, **response_kwargs):
            """
            Returns a JSON response, transforming 'context' to make the payload.
            """
            return JsonResponse(self.get_data(context), **response_kwargs)

        def get_data(self, context):
            """
            Returns an object that will be serialized as JSON by json.dumps().
            """
            # Note: This is *EXTREMELY* naive; in reality, you'll need
            # to do much more complex handling to ensure that arbitrary
            # objects -- such as Django model instances or querysets
            # -- can be serialized as JSON.
            return context

Note

Consultez la documentation Sérialisation d’objets Django pour plus d’informations sur la façon de transformer correctement des modèles Django et des jeux de requête au format JSON.

This mixin provides a `render_to_json_response()` method with the same signature as `render_to_response()`. To use it, we need to mix it into a `TemplateView` for example, and override `render_to_response()` to call `render_to_json_response()` instead:

    from django.views.generic import TemplateView

    class JSONView(JSONResponseMixin, TemplateView):
        def render_to_response(self, context, **response_kwargs):
            return self.render_to_json_response(context, **response_kwargs)

Il serait aussi possible d’utiliser cette classe mixin avec l’une des vues génériques. On peut composer sa propre version de `DetailView` en combinant `JSONResponseMixin` avec `BaseDetailView` (celle-ci contenant le comportement de `DetailView` avant le rendu du gabarit) :

    from django.views.generic.detail import BaseDetailView

    class JSONDetailView(JSONResponseMixin, BaseDetailView):
        def render_to_response(self, context, **response_kwargs):
            return self.render_to_json_response(context, **response_kwargs)

Cette vue peut ensuite être déployée de la même façon que pour n’importe quelle vue `DetailView`, reproduisant le même comportement à l’exception du format de la réponse.

If you want to be really adventurous, you could even mix a `DetailView` subclass that is able to return both HTML and JSON content, depending on some property of the HTTP request, such as a query argument or an HTTP header. Mix in both the `JSONResponseMixin` and a `SingleObjectTemplateResponseMixin`, and override the implementation of `render_to_response()` to defer to the appropriate rendering method depending on the type of response that the user requested:

    from django.views.generic.detail import SingleObjectTemplateResponseMixin

    class HybridDetailView(
        JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView
    ):
        def render_to_response(self, context):
            # Look for a 'format=json' GET argument
            if self.request.GET.get("format") == "json":
                return self.render_to_json_response(context)
            else:
                return super().render_to_response(context)

Because of the way that Python resolves method overloading, the call to `super().render_to_response(context)` ends up calling the `render_to_response()` implementation of `TemplateResponseMixin`.