Introduction aux vues fondées sur les classes

Les vues fondées sur les classes sont une autre manière d’implémenter les vues par des objets Python au lieu de fonctions. Elles ne remplacent pas les vues fondées sur les fonction, mais présentent certaines différences et avantages comparées aux vues fondées sur les fonctions :

  • L’organisation du code en lien avec les méthodes HTTP spécifiques (GET, POST, etc.) peut se faire par des méthodes séparées au lieu d’utiliser un branchement conditionnel.
  • Des techniques orientées objet comme les « mixins » (héritage multiple) peuvent être utilisées pour factoriser le code en composants réutilisables.

Liaisons et historique des vues génériques, des vues fondées sur les classes et de celles fondées sur les fonctions

Au départ, il n’y avait que le contrat de la vue fonction à laquelle Django transmet un requête HttpRequest et de laquelle il s’attend à recevoir une réponse HttpResponse. C’était ce que Django lui-même pratiquait.

Assez rapidement, on a reconnu des idiomes et des modèles courants dans le développement des vues. Les vues génériques fondées sur les fonctions ont été introduites pour abstraire ces pratiques et faciliter le développement de vues pour ces cas courants.

Le problème avec les vues génériques fondées sur les fonctions, c’est qu’elles recouvrent bien les cas simples, mais qu’il n’est pas possible de les étendre ou de les personnaliser au-delà de certaines options de configurations, limitant ainsi leur utilité dans bien des applications du monde réel.

Les vues génériques fondées sur les classes ont été crées avec les mêmes objectifs que celles fondées sur les fonctions, pour faciliter le développement de vues. Cependant, la manière d’implémenter cette solution par l’utilisation de mixins fournit un ensemble d’outils aboutissant à des vues génériques fondées sur les classes bien plus souples et extensibles que leur contrepartie fondées sur les fonctions.

Si vous avez essayé d’utiliser les vues génériques fondées sur les fonctions par le passé et que vous les avez trouvées trop limitées, vous ne devriez pas considérer les vues génériques fondées sur les classes comme des équivalents, mais plutôt comme une nouvelle approche pour résoudre les problèmes d’origine que les vues génériques étaient censées résoudre.

L’ensemble des classes de base et des mixins que Django utilise pour construire les vues génériques fondées sur les classes sont conçues pour une souplesse maximale, et présentent ainsi de nombreux points d’accrochage sous les forme d’implémentation de méthodes par défaut et d’attributs qui ne vous concerneront probablement pas dans les cas d’utilisation les plus simples. Par exemple, au lieu de se limiter à un attribut de classe pour form_class, l’implémentation utilise une méthode get_form appelant à son tour la méthode get_form_class qui dans son implémentation par défaut renvoie l’attribut form_class de la classe. Cela vous donne plusieurs options pour indiquer le formulaire à utiliser, que ce soit par un attribut ou par l’appel dynamique à une méthode que l’on peut surcharger. Ces options paraissent ajouter une complexité un peu absurde dans des situations simples, mais sans elles, les scénarios plus compliqués seraient limités.

Utilisation des vues fondées sur les classes

À la base, une vue fondée sur les classes permet de répondre à différentes méthodes de requête HTTP par différentes méthodes d’une classe plutôt que de faire appel à des branchements conditionnels dans une unique vue de type fonction.

Alors que le code pour gérer la méthode HTTP GET dans une fonction de vue ressemble à quelque chose comme cela :

from django.http import HttpResponse


def my_view(request):
    if request.method == "GET":
        # <view logic>
        return HttpResponse("result")

Dans une vue fondée sur les classes, cela devient :

from django.http import HttpResponse
from django.views import View


class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse("result")

Comme le résolveur d’URL de Django est conçu pour envoyer la requête et ses paramètres associés à une fonction exécutable et non pas à une classe, les vues fondées sur les classes possèdent une méthode de classe as_view() qui renvoie une fonction pouvant être appelée lorsqu’une requête arrive avec une URL correspondant au motif associé. La fonction crée une instance de la classe, appelle setup() pour initialiser ses attributs, puis appelle sa méthode dispatch(). dispatch examine la requête pour déterminer s’il s’agit d’un GET, d’un POST, etc. et relaie la requête à une méthode correspondante si elle existe, ou génère l’erreur HttpResponseNotAllowed dans le cas contraire :

# urls.py
from django.urls import path
from myapp.views import MyView

urlpatterns = [
    path("about/", MyView.as_view()),
]

Il vaut la peine de relever que ce qui est renvoyé par la méthode est identique à ce qui est renvoyé par une vue de type fonction, en l’occurrence une instance de HttpResponse. Cela signifie que les raccourcis http ou les objets TemplateResponse peuvent tout à fait être utilisés dans une vue fondée sur les classes.

Même si une vue fondée sur les classes minimale ne nécessite aucun attribut de classe pour faire son travail, les attributs de classe sont utiles dans beaucoup d’architectures basées sur les classes et il existe deux façons de configurer ou de définir des attributs de classe.

La première est la manière standard de Python d’hériter ou de surcharger des attributs et des méthodes dans les sous-classes. Ainsi si la classe parente avait un attribut greeting comme ceci :

from django.http import HttpResponse
from django.views import View


class GreetingView(View):
    greeting = "Good Day"

    def get(self, request):
        return HttpResponse(self.greeting)

Vous pouvez surcharger cela dans une sous-classe :

class MorningGreetingView(GreetingView):
    greeting = "Morning to ya"

Une autre option est de configurer des attributs de classe comme paramètres nommés dans l’appel à as_view() dans la configuration d’URL :

urlpatterns = [
    path("about/", GreetingView.as_view(greeting="G'day")),
]

Note

Alors qu’une instance de classe est créée pour chaque requête qu’elle doit traiter, les attributs de classe définis par le point d’entrée as_view() ne sont configurés qu’une seule fois au moment de l’importation de la configuration d’URL.

Utilisation de mixins

Les mixins sont une forme d’héritage multiple où les comportements et les attributs de plusieurs classes parentes peuvent être combinés.

Par exemple, dans les vues génériques fondées sur les classes, il existe un mixin nommé TemplateResponseMixin dont le rôle principal est de définir la méthode render_to_response(). Lorsqu’il est combiné avec le comportement de la classe de base View, le résultat est une classe TemplateView qui distribue les requêtes vers les méthodes correspondantes (un comportement défini dans la classe de base View), et qui possède une méthode render_to_response() utilisant un attribut template_name pour renvoyer un objet TemplateResponse (comportement défini dans TemplateResponseMixin).

Les mixins sont une très bonne façon de réutiliser du code entre plusieurs classes, mais il y a aussi un revers de médaille. Plus votre code est réparti entre des mixins, plus il sera difficile de lire une classe enfant et de comprendre exactement ce qu’elle fait, et plus il sera difficile de savoir quelle méthode de quel mixin surcharger si vous héritez d’une structure avec une profonde arborescence d’héritage.

Notez également que vous ne pouvez hériter que d’une vue générique, c’est-à-dire qu’une seule classe parente peut hériter de View et toutes les autres classes (le cas échéant) doivent être des mixins. Si vous essayez d’hériter de plus d’une classe héritant de View, par exemple pour utiliser un formulaire au sommet d’une liste et combiner ProcessFormView avec ListView, cela ne marchera pas comme espéré.

Gestion de formulaires avec les vues fondées sur les classes

Une vue de type fonction qui gère des formulaires peut ressembler à ceci :

from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import MyForm


def myview(request):
    if request.method == "POST":
        form = MyForm(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect("/success/")
    else:
        form = MyForm(initial={"key": "value"})

    return render(request, "form_template.html", {"form": form})

Une vue équivalente fondée sur les classes pourrait ressembler à ceci :

from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View

from .forms import MyForm


class MyFormView(View):
    form_class = MyForm
    initial = {"key": "value"}
    template_name = "form_template.html"

    def get(self, request, *args, **kwargs):
        form = self.form_class(initial=self.initial)
        return render(request, self.template_name, {"form": form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect("/success/")

        return render(request, self.template_name, {"form": form})

Il s’agit d’un cas minimal, mais vous pouvez voir que vous aurez ensuite l’option de personnaliser cette vue en surchargeant l’un des ses attributs de classe, par ex. form_class, à travers la configuration d’URL ou par l’héritage et la surcharge de l’une ou plusieurs de ses méthodes (les deux pouvant être combinés).

Décoration des vues fondées sur les classes

L’extension des vues fondées sur les classes n’est pas limitée aux mixins. Il est aussi possible d’utiliser des décorateurs. Comme les vues fondées sur les classes ne sont pas des fonctions, leur décoration fonctionne différemment selon que vous utilisez as_view() ou que vous créez une sous-classe.

Décoration dans la configuration d’URL

Il est possible d’adapter les vues fondées sur les classes en décorant le résultat de la méthode as_view(). L’endroit le plus commode pour le faire est la configuration d’URL dans laquelle la vue est déployée :

from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView

from .views import VoteView

urlpatterns = [
    path("about/", login_required(TemplateView.as_view(template_name="secret.html"))),
    path("vote/", permission_required("polls.can_vote")(VoteView.as_view())),
]

Cette approche applique le décorateur au niveau de l’instance. Si vous souhaitez que chaque instance d’une vue soit systématiquement décorée, vous devez suivre une autre approche.

Décoration de la classe

Pour décorer toutes les instances d’une vue fondée sur les classes, vous devez décorer la définition de classe elle-même. À cet effet, appliquez le décorateur à la méthode dispatch() de la classe.

Une méthode de classe n’est pas totalement identique à une fonction autonome, il n’est donc pas possible de simplement appliquer un décorateur de fonction à la méthode, il faut préalablement le transformer en un décorateur de méthode. Le décorateur method_decorator transforme un décorateur de fonction en un décorateur de méthode afin qu’il puisse être utilisé sur une méthode d’instance. Par exemple :

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView


class ProtectedView(TemplateView):
    template_name = "secret.html"

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

Ou plus succintement, vous pouvez plutôt décorer la classe et passer le nom de la méthode à décorer dans le paramètre nommé name:

@method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
    template_name = "secret.html"

Si un ensemble de décorateurs sont utilisés à plusieurs endroits, vous pouvez définir une liste ou un tuple de décorateurs et l’utiliser au lieu d’appeler plusieurs fois method_decorator(). Ces deux classes sont équivalentes :

decorators = [never_cache, login_required]


@method_decorator(decorators, name="dispatch")
class ProtectedView(TemplateView):
    template_name = "secret.html"


@method_decorator(never_cache, name="dispatch")
@method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
    template_name = "secret.html"

Les décorateurs traitent une requête dans l’ordre où ils sont placés comme décorateurs. Dans l’exemple, never_cache() traitera la requête avant login_required().

Dans cet exemple, chaque instance de ProtectedView recevra une protection de connexion. Ces exemples utilisent login_required, mais le même comportement peut être obtenu en utilisant LoginRequiredMixin.

Note

method_decorator transmet *args et **kwargs comme paramètres à la méthode décorée de la classe. Si votre méthode n’accepte pas un jeu de paramètres compatible, cela générera une exception TypeError.

Back to Top