Django: mixin ou décorateur pour la gestion des accès aux vues

Avec les vues sous forme de fonction en Django, on gérait les permissions à l’aide de décorateurs.

@login_required
def index(self):
  return render("index.html")

Depuis l’introduction des vues de type classe, la documentation de django précise que pour avoir le même comportement qu’avec les décorateurs, on peut utiliser des classes mixins (ayant peu d’effets de bord).

class IndexView(LoginRequiredMixin, TemplateView):
  template_name = "index.html"

Nous allons voir que le comportement est tout de même différent.

Considérons la vue TextView1 suivante:

class TestView1(LoginRequiredMixin, View):
    def dispatch(self, request, *args, **kwargs):
        if not request.user.has_perm(app.do_something):
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        return HttpResponse("Contenu view1")

Lors de l’appel à TestView1.as_view(), la méthode dispatch() est appelée. On vérifie que l’utilisateur a bien la permission app.do_something, mais rien ne garantit que l’utilisateur est bien connecté ! Cela se fera seulement après l’appel à super().dispatch(), qui va d’abord appeler la méthode dispatch() de la mixin LoginRequiredMixin.

On pourrait donc appeler d’abord la méthode dispatch() du parent, puis ensuite vérifier que l’utilisateur a bien les permissions nécessaires.

On considère la vue View2 suivante:

class View2(LoginRequiredMixin, View):
    def dispatch(self, request, *args, **kwargs):
        response =  super().dispatch(request, *args, **kwargs)
        if not request.user.has_perm('stage.gerer_stage'):
            raise PermissionDenied
        return response

    def get(self, request, *args, **kwargs):
        return HttpResponse("Contenu view2")

Ici il y a deux possibilité:

  • Soit on met l’attribut raise_exception à True, dans ce cas, lors de l’appel à super().dispatch(), une exception sera renvoyée.
  • Soit on met l’attribut à False, et la réponse renvoyée par super().dispatch() sera une url de redirection vers la page de login. Or, on devrait renvoyer directement vers cette url, mais on vérifie que l’utilisateur a bien la permission app.do_something, même s’il n’est pas connecté!

De plus, en imaginant que l’utilisateur est connecté, l’appel à la méthode super().dispatch() va exécuter un tas d’instructions qui, s’il s’avère que l’utilisateur n’a pas les permissions nécessaires, seront de toute façon futiles car un raise PermissionDenied sera de renvoyé.

La dernière méthode, pour se rapprocher des décorateurs utilisés avec les vues de types fonction (fbv), on peut utiliser @method_decorator sur la classe (qui va en réalité s’appliquer sur la méthode dispatch()).

@method_decorator(login_required, name='dispatch')
class Test3(View):
    def dispatch(self, request, *args, **kwargs):
        if not request.user.has_perm('stage.gerer_stage'):
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        return HttpResponse("Contenu view2")

Dans ce cas, la méthode dispatch() est changée, et son contenu tel qu’implémenté dans la vue Vue3 ne sera appelé que si l’utilisateur est connecté.

Un projet avec test est disponible sur framagit/sekun/cbv-mixin-test.