ajout d’un callback before_filter sur Django

J’ai l’habitude de travailler avec le Framework Ruby On Rails et il y a un filtre before_filter sur les contrôleurs qui permet d’exécuter un ensemble de taches avant de lancer une action. C’est utile pour si on veut vérifier qu’un utilisateur s’est bien connecté et a les droits d’exécuter une action par exemple.

Récemment, j’ai commencé à développer une application Django au bureau et je me suis rendu compte que cette fonctionnalité n’existe pas dans cet univers 🙁

Après l’avoir googlé je suis tombé sur un middleware qui permet de le faire sur Django 🙂 (encore merci M. Google).

Voici le middleware en question :

import sys

class BeforeFilterMiddleware(object):
    def __init__(self):
        self.setting = 'BEFORE_FILTER'
        self.filters = []

    def _call_filters(self, request, view, args, kwargs):
        response = None
        for f in self.filters:
            response = f(request, view, args, kwargs)
            if response and response.has_header('location'):
                self.filters = []
                return response
        self.filters = []
        return response

    def process_view(self, request, view, args, kwargs):
        module = sys.modules[view.__module__]
        if hasattr(module, self.setting):
            for func, views in getattr(module, self.setting).items():
                exclude = '-%s' % view.func_name
                if not views or view.func_name in views: # or views and exclude not in views:
                    if hasattr(module, func):
                        if getattr(module, func) not in self.filters:
                            self.filters.append(getattr(module, func))
        if self.filters:
            return self._call_filters(request, view, args, kwargs)
        return None

Utilisation :

J’ai enregistré ce script dans un fichier outils/my_middlewares/before_filter.py du répertoire principal de mon application.

Ensuite, je l’ai ajouté sur la liste des middlewares de mon application : la constante MIDDLEWARE_CLASSES du fichier de configuration settings.py, maintenant il doit ressembler à ca :

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'outils.my_middlewares.before_filter.BeforeFilterMiddleware',
)

C’est tout pour la configuration.

Maintenant si on veut, par exemple, exécuter une fonction admin_required avant de lancer les actions new et create et owner_required pour l’action edit il suffit d’ajouter ces lignes de code sur ladite view :

BEFORE_FILTER = {'admin_required': ('new', 'create'),
                 'owner_required': ('edit',),
                 }

def admin_required(request, view, args, kwargs):
    if not is_admin():
        messages.warning(request, u"Vous ne pouvez pas accéder à cette page")
        return HttpResponseRedirect(reverse('root'))

def owner_required(request, view, args, kwargs):
    if not is_owner():
        messages.warning(request, u"Vous ne pouvez pas modifier une ressource qui ne vous appartient pas")
        return HttpResponseRedirect(reverse('root'))

C’est tout ! Vous avez des questions ? Commentez 🙂

  • B@elze

    Good.

    • Cheikh Sidya CAMARA

      Thx !

  • b2bsunucoders

    Very good maybe que j’aurai a l’utiliser un jour

  • sow Mansour

    Tes actions new,create et edit coorespondent-elles a des views?
    D’apres ce que j’ai compris vous n’avez pas defini un controleurs sur les views mais vous avez ajouter un middleware qui exectue certaines taches sous certaines conditions.Ce concept existe deja sous django a travers les annotations(a@login_required,@login_url…).La seule differente est que vous avez defini votre propre middleware..Merci

    • – Oui elles correspondent à des views.
      – La fonctionnalité dont tu parles permet seulement de s’assurer que l’utilisateur s’est bien connecté avant d’exécuter une action alors que le before_filter est plus général.

      • Stockman

        – très intéressant. Ce que tu proposes est générique et c’est très bien dans la logique de PEP8

        – dire que Django n’a pas cette fonction par défaut, ce n’est pas du tout “fair”: beaucoup ne connaissent pas bien les possibilités de Django. tu peux écrire tes propres “decorators” qui feront beaucoup de trucs et les invoquer n’importe ou/quand tu veux: http://goo.gl/MvEnPk , http://agiliq.com/blog/2009/06/understanding-decorators/ .

        – Le midleware est certes générique, mais des fois, on a juste besoin d’une ou deux petites fonctions à appliquer sur certains views ( exemple des fonctions lambda : http://goo.gl/MvEnPk ). On se rend compte que ce n’est pas la peine d’écrire tout un midleware.

        – aussi tu peux toujours associer des méthodes ou fonctions spéciales à tes modèles (ou tes “modelsAdmis’, et tes views ) pour être exécutées AVANT ( pre_save()) ou juste APRES (post_save()) la création d’un model : ce sont les Signals. Les signals semblent compliqués mais ils sont très puissants si tu les utilises. Par exemple dans ce blog on parle de pre_save() qui remplace la méthode save() de chaque modèle par défaut : http://holdenweb.blogspot.com/2008/10/django-tip-fieldpresave.html

        • Merci @disqus_QEGWaAlBqZ:disqus pour ces détails. J’utilise bien les signals ‘pre_save’ et ‘post_save’ dans mes applications Django. En ce qui concerne ce ‘before_filter’ que j’ai proposé sur mon article, c’était juste pour avoir le même style que celui du Framework Ruby On Rails mais je pense que les décorateurs sont plus pratiques comme tu viens de le dire 🙂