Vue d'ensemble
Django 5.1, publié le 7 août 2024, apporte des améliorations centrées sur la sécurité et la simplicité. Le nouveau LoginRequiredMiddleware rend l'authentification obligatoire par défaut sur l'ensemble du site, éliminant le risque d'oublier un décorateur @login_required sur une vue sensible.
Les sessions adossées à la base de données sont simplifiées, et l'ORM gagne des optimisations de requêtes qui réduisent le nombre de jointures et de sous-requêtes générées pour les cas courants.
Fonctionnalités principales
LoginRequiredMiddleware
Le LoginRequiredMiddleware impose l'authentification sur toutes les vues par défaut. Les vues publiques (page d'accueil, inscription, mentions légales) sont explicitement exemptées avec le décorateur @login_not_required. Cette approche « sécurisé par défaut » est bien plus robuste que l'approche inverse où chaque vue doit penser à exiger l'authentification.
# settings.py — Ajouter le middleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.LoginRequiredMiddleware', # nouveau
'django.contrib.messages.middleware.MessageMiddleware',
]
# views.py — Toutes les vues exigent l'authentification
# SAUF celles marquées explicitement
from django.contrib.auth.decorators import login_not_required
@login_not_required
def page_accueil(request):
"""Page d'accueil publique."""
return render(request, 'accueil.html')
@login_not_required
def inscription(request):
"""Formulaire d'inscription public."""
# ...
# Cette vue est automatiquement protégée
def tableau_de_bord(request):
"""Tableau de bord privé — authentification requise."""
return render(request, 'tableau_de_bord.html')
# Pour une CBV
from django.utils.decorators import method_decorator
from django.views import View
@method_decorator(login_not_required, name='dispatch')
class MentionsLegalesView(View):
def get(self, request):
return render(request, 'mentions_legales.html')
Sessions simplifiées
Le backend de sessions en base de données est simplifié avec une nouvelle implémentation qui stocke les données directement en JSON au lieu d'utiliser le format pickle sérialisé en base64. Cela rend les sessions plus faciles à inspecter, plus sûres et compatibles avec des outils d'analyse externes.
# settings.py — Utiliser le nouveau backend simplifié
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
# Le sérialiseur JSON est désormais le défaut
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
# Utilisation dans une vue
def ajouter_au_panier(request, produit_id):
panier = request.session.get('panier', {})
cle = str(produit_id)
if cle in panier:
panier[cle]['quantite'] += 1
else:
produit = Produit.objects.get(pk=produit_id)
panier[cle] = {
'nom': produit.nom,
'prix': str(produit.prix),
'quantite': 1,
}
request.session['panier'] = panier
return redirect('panier')
# Les données sont stockées en JSON lisible en base
# {"panier": {"42": {"nom": "Clavier", "prix": "89.99", "quantite": 2}}}
Optimisations de requêtes ORM
Django 5.1 optimise la génération SQL dans plusieurs cas courants. Les sous-requêtes inutiles sont éliminées, les jointures redondantes sont fusionnées, et les expressions Exists() sont mieux optimisées. Ces améliorations sont transparentes : le code existant bénéficie automatiquement de requêtes plus efficaces.
from django.db.models import Exists, OuterRef, Count, Q
# Avant Django 5.1 : pouvait générer des sous-requêtes inutiles
# Après Django 5.1 : SQL optimisé automatiquement
# Exemple 1 : filtrer avec Exists() optimisé
commandes_recentes = Commande.objects.filter(
date__gte='2024-01-01',
client=OuterRef('pk'),
)
clients_actifs = Client.objects.filter(
Exists(commandes_recentes)
)
# Django 5.1 génère un EXISTS plus efficace
# Exemple 2 : annotations avec jointures optimisées
stats_auteurs = (
Auteur.objects
.annotate(
nb_articles=Count('articles'),
nb_articles_publies=Count(
'articles', filter=Q(articles__publie=True)
),
)
.filter(nb_articles_publies__gt=0)
)
# La jointure vers 'articles' n'est effectuée qu'une seule fois
# Exemple 3 : sélection intelligente des colonnes
donnees = (
Commande.objects
.select_related('client')
.only('reference', 'montant', 'client__nom')
)
# Django 5.1 évite de charger les colonnes inutiles de la jointure
