# Documentation Django 6.0.7

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

Prudence

Ceci est un sujet avancé. Nous suggérons d’avoir une bonne connaissance préalable des vues Django fondées sur les classes avant d’explorer ces 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`

  Chaque vue intégrée renvoyant une réponse `TemplateResponse` appelle la méthode `render_to_response()` fournie par `TemplateResponseMixin`. Dans la plupart des cas, cet appel se fait automatiquement (par exemple, elle est appelée par la méthode `get()` aussi bien de `TemplateView` que de `DetailView`). De même, il est peu probable que vous ayez besoin de la surcharger, bien que cela peut se révéler utile si vous voulez que la réponse renvoie du contenu non rendu par un gabarit Django. Vous pouvez trouver un exemple d’un tel usage dans l’exemple JSONResponseMixin.

  `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`

  Chaque vue intégrée nécessitant des données de contexte, comme pour effectuer le rendu d’un gabarit (y compris `TemplateResponseMixin` ci-dessus), doit appeler `get_context_data()` en transmettant sous forme de paramètres nommés les données qu’elle souhaite y placer. `get_context_data()` renvoie un dictionnaire ; dans `ContextMixin`, elle renvoie ses paramètres nommés, mais il est fréquent de surcharger cela pour ajouter davantage de contenu dans ce dictionnaire. Vous pouvez également utiliser l’attribut `extra_context`.

## 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.

Pour obtenir l’objet, `DetailView` se base sur `SingleObjectMixin` qui fournit une méthode `get_object()` qui va rechercher l’objet en se basant sur l’URL de la requête (elle cherche les paramètres nommés `pk` et `slug` tels que déclarés dans la configuration d’URL et sélectionne l’objet en utilisant soit l’attribut `model` de la vue, soit l’attribut `queryset` si celui-ci est présent). `SingleObjectMixin` surcharge aussi `get_context_data()`, qui est utilisée dans toutes les vues fondées sur les classes de Django pour fournir les données de contexte servant au rendu des gabarits.

Pour produire la réponse `TemplateResponse`, `DetailView` utilise `SingleObjectTemplateResponseMixin`, qui étend `TemplateResponseMixin`, surchargeant `get_template_names()` comme expliqué plus haut. Il fournit en réalité un bon nombre d’options élaborées, mais le nom que la plupart des gens vont utiliser est `<étiquette_application>/<nom_modèle>_detail.html`. La partie `_detail` peut être modifiée en définissant un attribut `template_name_suffix` différent dans une sous-classe (par exemple, les vues génériques d’édition emploient `_form` pour les vues de création et de mise à jour et `_confirm_delete` pour les vues de suppression).

### `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` surcharge aussi `get_context_data()` pour inclure les variables de contexte appropriées à la pagination (indiquant des valeurs vides si la pagination est désactivée). Elle compte sur la présence de `object_list` dans les paramètres nommés transmis, ce dont se charge `ListView`.

To make a `TemplateResponse`, `ListView` then uses `MultipleObjectTemplateResponseMixin`. As with `SingleObjectTemplateResponseMixin` above, this overrides `get_template_names()` to provide a range of options, with the most commonly-used being `<app_label>/<model_name>_list.html`, with the `_list` part again being taken from the `template_name_suffix` attribute. (The date based generic views use suffixes such as `_archive`, `_archive_year` and so on to use different templates for the various specialized date-based list views.)

## 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`.

Nous allons démontrer cela avec le modèle `Author` que nous avons utilisé dans l’introduction aux vues génériques basées sur les classes.

`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`.

Le jeu de requête `Publisher` utilisé par `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

Chacune de vos vues ne devrait utiliser que des mixins ou des vues en provenance de l’un des groupes de vues génériques fondées sur les classes : détail, liste, édition et date. Par exemple, il est convenable de combiner `TemplateView` (vue intégrée) avec `MultipleObjectMixin` (liste générique), mais vous rencontrerez sûrement des problèmes si vous combinez `SingleObjectMixin` (détail générique) avec `MultipleObjectMixin` (liste générique).

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

Pour terminer, nous lions le tout en créant une nouvelle vue `AuthorView`. Nous savons déjà que l’appel à `as_view()` sur une vue fondée sur les classes nous renvoie un objet se comportant exactement comme une vue de type fonction. Nous pouvons donc effectuer cet appel au moment où il faut choisir entre les deux « sous-vues ».

Vous pouvez transmettre des paramètres nommés à `as_view()` comme vous le feriez dans la configuration d’URL, par exemple si vous vouliez que le comportement de `AuthorInterestFormView` puisse aussi être utilisé avec une autre URL mais en affichant un gabarit différent :

    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.

Ce mixin fournit une méthode `render_to_json_response()` ayant la même signature que `render_to_response()`. Pour l’utiliser, nous devons la combiner par exemple à une classe `TemplateView` et surcharger `render_to_response()` afin d’appeler `render_to_json_response()` à la place :

    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.

Si vous avez de réels goûts d’aventure, vous pourriez même tenter de combiner avec une sous-classe de `DetailView` capable de renvoyer à la fois du contenu HTML et JSON en fonction d’une propriété de la requête HTTP, telle qu’un paramètre de requête ou un en-tête HTTP. Combinez `JSONResponseMixin` avec `SingleObjectTemplateResponseMixin` et surchargez l’implémentation de `render_to_response()` pour déléguer le rendu à la méthode appropriée en fonction du type de réponse demandée par l’utilisateur :

    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)

En raison de la façon dont Python résout la surcharge des méthodes, l’appel à `super().render_to_response(context)` fait en réalité appel à l’implémentation `render_to_response()` de `TemplateResponseMixin`.