Avec Django 1.10, les middlewares ont changé. Allez à la fin de l’article pour en savoir plus.
Vous savez qu’il y a un settings qui ressemble à ça:
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', ) |
Vous savez qu’il est important. Vous ne savez pas vraiment pourquoi.
WaT iZ a MideulWaire ?
Un middleware est un moyen d’exécuter du code à toutes requêtes et/ou à toutes les réponses reçues et envoyées par Django. Si vous ne voyez pas comment fonctionne le cycle de requêtes/réponses, faites un petit saut sur notre schéma de fonctionnement général de Django.
Un middleware, c’est une classe Python ordinaire, avec deux méthodes: une appelée pour les requêtes, une appelée pour les réponses.
L’exemple bidon habituel
class MideulWaireForEverReturnsTheRevenge3: # cette méthode sera appelée automatiquement pour chaque requête # et Django lui passera la requête en cours def process_request(self, request): print("Hey, une requête est arrivée !") # on est pas obligé de retourner quoi que ce soit # cette méthode sera appelée automatiquement pour chaque réponse # et Django lui passera la réponse en cours et la requête # à laquelle on répond def process_response(self, request, response): print("Hey, on a répondu a une requête !") # on DOIT retourner une réponse return response |
On met tout ça dans un fichier mon_projet/mon_app/middlewares.py, et on active le middleware en le rajoutant dans les settings:
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', 'mon_projet.mon_app.middlewares.MideulWaireForEverReturnsTheRevenge3' ) |
Et à chaque reload de page, dans l’affichage du terminal de dev, on a ça:
Et voici ce qui se passe dans le fonctionnement de Django: il trouve la liste des classes de middlewares, et pour chaque requête et réponse, il appelle les méthodes process_response
et process_request
de chaque middleware.
Les points les plus importants
Aucune méthode n’est obligatoire, vous pouvez créer un middleware avec uniquement l’une ou l’autre.
Si process_request
renvoie une réponse, tout s’arrête. Les process_request
des autres middlewares ne sont pas appelés, vos vues ne sont pas appelées, et c’est le cycle de réponse qui commence directement. Très utile si par exemple vous voulez faire une redirection brutale pour toutes les requêtes qui correspondent à un certain critère (par exemple rediriger vers un site mobile).
Les middlewares sont appelés dans leur ordre de déclaration dans MIDDLEWARE_CLASSES
pour chaque requête, et dans l’ordre inverse pour chaque réponse. Mettez donc votre middleware au bon endroit dans la file: inutile de mettre un middleware qui vérifie si un user a des droits avant le middleware d’authentification, puisque l’utilisateur n’est pas encore authentifié.
Un middleware peut posséder 3 autres méthodes également facultatives: process_view
(appelée juste avant l’appel de chaque vue, et permettant de modifier l’instance de la vue elle-même), process_template_response
(appelée sur chaque instance implémentant une méthode render, et surtout utilisée pour injecter des données dans TemplateResponse) et process_exception
(appelée quand une vue lève une exception).
Pour quoi utiliser les middleware ?
Exemples de quelques middlewares qui viennent d’office avec Django:
- UpdateCacheMiddleware, FetchFromCacheMiddleware: ils cachent l’intégralité des pages.
- GZipMiddleware: retourne une réponse compressée si le browser le gère.
- LocaleMiddleware: set la langue que Djando doit utiliser, en fonction du navigateur.
- MessageMiddleware: gestion des “flash-messages”.
- SessionMiddleware: gestion des sessions.
- AuthenticationMiddleware: authentifier un utilisateur.
Il y en a beaucoup d’autres, et il y en a plein qui trainent sur internet. Mais bien entendu le plus chouette, c’est d’en faire un soi-même.
Par exemple, quand je développe (pas en prod, hien !), j’utilise souvent ce petit middleware que j’ai fait avec amour:
class ForceSuperUserMiddleWare(object): def process_request(self, request): request.user = User.objects.filter(is_superuser=True)[0] |
Comme ça je suis toujours connecté en tant que super user, même sur les projets avec des timeout courts pour la session, des doubles authentifications super relous et tout le toutim des clients paranos (traduction: des américains).
Les middlewares en Django 1.10 et plus
À partir de Django 1.10 (publié en 2016), les middlewares ont changé de tête et deviennent des fonctions qui doivent retourner… une fonction.
Cela ressemble à ça (si vous avez suivi le tuto sur les décorateurs, vous ne serez pas trop perdu):
# la première fonction est une factory, c'est-à-dire une fonction # dont le but est de définir et retourner une autre fonction def middleware_factory(get_response): # On met ici le code d'initialisation du middleware # qui ne sera appelée qu'une fois au démarrage de # django def middleware(request): # On met ici le code a exécuter à chaque requête, # avant la vue # récupération de la réponse. Ouai, le paramètre # get_reponse est une fonction. Bienvenu dans la # programmation fonctionnelle. response = get_response(request) # On met ici le code a exécuter pour chaque requête, # après que la réponse soit créée. # n'oubliez pas de retourner la réponse return response # n'oubliez pas de retourner votre middleware return middleware |
Alors je ne sais pas trop ce qu’ils ont fumé chez Django, car d’un côté ils ont soûlé tout le monde avec les class based views, et de l’autre ils passent en FP pour les middlewares. Mais bon, perso j’aime bien ce nouveau look, je trouve ça plus facile, mais je comprends qu’un débutant regarde ça avec des yeux ronds.
Voici notre premier exemple, avec le nouveau style:
def mideulwaire_for_ever_returns_the_revenge_3(get_response): def middleware(request): print("Hey, une requête est arrivée !") response = get_response(request) print("Hey, on a répondu a une requête !") return response return middleware |
Pour éviter de tout mélanger, le settings pour les nouveaux middlewares est appelé MIDDLEWARES
, et non plus MIDDLEWARE_CLASSES
. Vous pouvez déclarer l’un ou l’autre, mais pas les deux.
La bonne nouvelle, c’est qu’il est possible de coder des middlewares qui soient compatibles avec les deux versions. En fait, tous ceux fournis avec Django par défaut le sont, donc si vous utilisez seulement ceux par défaut vous pouvez juste renommer le settings et vous êtes bons.
Si par contre vous devez transformer un de vos anciens middlewares avec un nouveau,vous pouvez hériter django.utils.deprecation.MiddlewareMixin
qui 90% du temps suffit à transformer votre vieux middleware en version compatible avec le nouveau style. Si vous préférez le faire à la main parce que vous avez un comportement très spécial, vous pouvez toujours overrider __call__ puisque ça marche avec n’importe quel callable.
Ordinaire avec un seul “N”
bonjour,
celle là m’a échappée :-) moins écorchée l’oeil aussi :)
Thx
Merci les gars, grace à vous on garde un semblant de crédibilité.
“et dans l’odre inverse “
Fixed.
Merci, super article
Merci pour cet excellent article, clair et très précis.
Merci les gars
C’est super clair comme explication.
:)
Attention cependant cet article date pas mal. Les exemples sont en Python 2.7, et maintenant les middlewares Django ont un peu changé dans la version 1.10: https://docs.djangoproject.com/en/1.10/topics/http/middleware/
Dans le dernier exemple : l’argument est “get_request” au lieu de get_response.
Et : “avec un nouveau,vous pouvez hériter django.utils.deprecation.MiddlewareMixin qui 90% suffit à transformer votre vieux middleware en version compatible avec le …”
virgule après nouveau,
“à 90% du temps” ?
Depuis la mise à jour, les middleware changent d’ordre. Avant on partait du moins vital au plus vital, maintenant c’est le contraire, si j’ai bien suivi https://docs.djangoproject.com/en/1.10/ref/settings/ https://docs.djangoproject.com/en/1.10/topics/http/middleware/