Questions d'entretien Django : ORM, Middleware et DRF en profondeur
Questions d'entretien Django couvrant l'optimisation de l'ORM avec select_related, prefetch_related et le mode FETCH_PEERS de Django 6.1, l'architecture middleware et les performances des serializers Django REST Framework, permissions et pagination.

Les questions d'entretien Django se concentrent sur trois piliers qui distinguent les candidats expérimentés des autres : la maîtrise de l'ORM, l'architecture middleware et les design patterns de Django REST Framework (DRF). Ce guide détaille les questions exactes posées par les recruteurs en 2026, avec des exemples de code prêts pour la production utilisant Django 6.1 et DRF 3.18.
Les entretiens Django posent rarement des questions sur le CRUD basique. L'accent s'est déplacé vers l'optimisation des QuerySets (select_related vs prefetch_related, le nouveau mode FETCH_PEERS), la conception de middleware personnalisés et les performances des serializers DRF. Les candidats capables d'expliquer les requêtes N+1 et d'écrire des viewsets efficaces surpassent systématiquement ceux qui ne connaissent que les vues génériques.
Questions d'entretien sur l'ORM Django : optimisation des QuerySets
La question la plus courante sur l'ORM Django cible le problème N+1. Face à un modèle avec des relations de clé étrangère, le candidat doit démontrer quand utiliser select_related par rapport à prefetch_related.
# models.py
from django.db import models
class Company(models.Model):
name = models.CharField(max_length=200)
founded_year = models.IntegerField()
class Developer(models.Model):
name = models.CharField(max_length=200)
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="developers")
skills = models.ManyToManyField("Skill", related_name="developers")
class Skill(models.Model):
name = models.CharField(max_length=100)
category = models.CharField(max_length=50)La différence entre les deux méthodes tient au type de relation traversée.
# queries.py — Correct approach for ForeignKey (single object)
developers = Developer.objects.select_related("company").all()
# Génère UNE seule requête SQL avec JOIN
# SELECT developer.*, company.* FROM developer INNER JOIN company ...
# Correct approach for ManyToMany (multiple objects)
developers = Developer.objects.prefetch_related("skills").all()
# Génère DEUX requêtes SQL :
# 1. SELECT * FROM developer
# 2. SELECT * FROM skill INNER JOIN developer_skills WHERE developer_id IN (...)select_related effectue un JOIN SQL et fonctionne avec les champs ForeignKey et OneToOneField. prefetch_related exécute une requête séparée et fonctionne avec les ManyToManyField et les relations ForeignKey inverses. Confondre les deux provoque soit des JOINs inutiles sur de grands jeux de données, soit le redouté problème N+1.
Django 6.1 : mode FETCH_PEERS pour la prévention automatique du N+1
Django 6.1 introduit un nouveau système de modes de récupération qui traite les requêtes N+1 au niveau de l'ORM. Lors de l'itération sur un queryset et de l'accès à un champ lié, FETCH_PEERS récupère ce champ pour toutes les instances du queryset avec une seule requête supplémentaire.
# views.py — Django 6.1 FETCH_PEERS mode
from django.db import models
from .models import Book
# Traditional approach: requires explicit prefetch_related
books = Book.objects.prefetch_related("author").all()
# Django 6.1 approach: automatic peer fetching
books = Book.objects.fetch_mode(models.FETCH_PEERS)
for book in books:
print(book.author.name) # Only 2 queries total: books + all authorsTrois modes de récupération sont disponibles :
FETCH_ONE(par défaut) : récupère le champ manquant uniquement pour l'instance couranteFETCH_PEERS: récupère le champ pour toutes les instances du même QuerySetFETCH_RAISE: lèveFieldFetchBlockedpour le code critique en performance où le chargement paresseux doit être empêché
Pour les entretiens, les candidats doivent connaître les deux approches : select_related/prefetch_related explicite pour un contrôle fin, et FETCH_PEERS pour l'optimisation automatique lors de l'itération sur les querysets.
ORM avancé : Managers personnalisés et chaînage de QuerySets
Les recruteurs demandent souvent aux candidats d'écrire un manager personnalisé qui encapsule la logique métier. L'objectif est de vérifier que le candidat comprend le pattern manager de Django et sait écrire des interfaces de requêtes réutilisables.
# managers.py
from django.db import models
from django.utils import timezone
class ActiveDeveloperQuerySet(models.QuerySet):
def active(self):
"""Filter developers who logged in within the last 30 days."""
cutoff = timezone.now() - timezone.timedelta(days=30)
return self.filter(last_login__gte=cutoff)
def senior(self):
"""Filter developers with 5+ years of experience."""
return self.filter(years_experience__gte=5)
def by_skill(self, skill_name):
"""Filter developers by a specific skill."""
return self.filter(skills__name=skill_name)
class ActiveDeveloperManager(models.Manager):
def get_queryset(self):
return ActiveDeveloperQuerySet(self.model, using=self._db)
def active(self):
return self.get_queryset().active()La question de suivi clé : « Pourquoi utiliser un QuerySet personnalisé plutôt qu'un simple Manager ? » La réponse tient au chaînage. Les méthodes d'un QuerySet personnalisé peuvent être enchaînées, alors que les méthodes d'un Manager ne peuvent pas l'être après le premier appel.
# usage.py — QuerySet chaining in action
# Cela fonctionne car chaque méthode retourne un QuerySet
senior_python_devs = (
Developer.active_objects # manager personnalisé
.active() # méthode ActiveDeveloperQuerySet
.senior() # chaîne une autre méthode QuerySet
.by_skill("Python") # chaîne une troisième méthode
.select_related("company") # les méthodes standard QuerySet fonctionnent toujours
)Django 6.0 a introduit CompositePrimaryKey pour les modèles nécessitant des clés primaires multi-colonnes, ainsi qu'un framework de tâches en arrière-plan intégré. Les questions d'entretien sur ces fonctionnalités deviennent de plus en plus fréquentes, en particulier pour les candidats travaillant avec des bases de données héritées ou des besoins de traitement asynchrone.
Questions d'entretien sur le middleware Django : pipeline requête-réponse
Les questions sur le middleware testent la compréhension du cycle de vie requête-réponse de Django. La question standard : « Expliquez l'ordre dans lequel le middleware traite une requête et une réponse. »
La réponse suit un schéma strict. Pendant une requête, les classes middleware s'exécutent de haut en bas tel que défini dans MIDDLEWARE. Pendant une réponse, elles s'exécutent de bas en haut. Cette architecture en couches d'oignon signifie que le premier middleware de la liste enveloppe tout le reste.
# middleware.py
import time
import logging
from django.http import JsonResponse
logger = logging.getLogger(__name__)
class RequestTimingMiddleware:
"""Logs the time taken to process each request."""
def __init__(self, get_response):
self.get_response = get_response # next middleware or view
def __call__(self, request):
start_time = time.monotonic()
response = self.get_response(request) # passes to next layer
duration_ms = (time.monotonic() - start_time) * 1000
logger.info(
"method=%s path=%s status=%d duration=%.2fms",
request.method,
request.path,
response.status_code,
duration_ms,
)
return responseUne question de suivi courante : « Comment court-circuiter la chaîne middleware ? » Retourner une HttpResponse depuis __call__ avant d'appeler self.get_response(request) arrête entièrement la chaîne. Les middleware restants et la vue ne s'exécutent jamais.
# middleware.py — Rate limiting with short-circuit
from django.core.cache import cache
class RateLimitMiddleware:
"""Blocks requests exceeding 100 per minute per IP."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
ip = request.META.get("REMOTE_ADDR")
cache_key = f"rate_limit:{ip}"
request_count = cache.get(cache_key, 0)
if request_count >= 100:
return JsonResponse( # short-circuits — view never executes
{"error": "Rate limit exceeded. Try again in 60 seconds."},
status=429,
)
cache.set(cache_key, request_count + 1, timeout=60)
return self.get_response(request)Hooks middleware : process_view, process_exception et process_template_response
Au-delà de __call__, le middleware Django prend en charge trois méthodes hook optionnelles. Les recruteurs les utilisent pour évaluer la profondeur des connaissances.
# middleware.py — Full middleware with all hooks
class AuditMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
"""Called after URL resolution, before the view executes."""
logger.info("Calling view: %s", view_func.__name__)
return None # returning None continues normal processing
def process_exception(self, request, exception):
"""Called only if the view raises an exception."""
logger.error("View exception: %s", exception, exc_info=True)
return None # returning None lets Django's default handling proceed
def process_template_response(self, request, response):
"""Called if the response has a render() method (TemplateResponse)."""
response.context_data["audit_timestamp"] = time.time()
return response # must return a response with render()process_view se déclenche après la résolution d'URL mais avant la vue. Retourner None continue l'exécution ; retourner une HttpResponse court-circuite. process_exception se déclenche uniquement sur les exceptions non gérées. process_template_response se déclenche uniquement pour les objets TemplateResponse, pas pour les HttpResponse classiques.
Prêt à réussir tes entretiens Django ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Questions d'entretien DRF : performances des serializers
Les questions sur les serializers de Django REST Framework se concentrent sur la sérialisation imbriquée et les implications de performance des différentes approches. La question la plus posée : « Comment gérer les relations imbriquées sans provoquer de requêtes N+1 ? »
# serializers.py
from rest_framework import serializers
from .models import Developer, Company, Skill
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ["name", "category"]
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ["name", "founded_year"]
class DeveloperSerializer(serializers.ModelSerializer):
company = CompanySerializer(read_only=True) # nested FK
skills = SkillSerializer(many=True, read_only=True) # nested M2M
class Meta:
model = Developer
fields = ["id", "name", "company", "skills"]Le serializer seul ne résout pas les problèmes de performance. Le ViewSet doit optimiser le queryset.
# views.py
from rest_framework import viewsets
from .models import Developer
from .serializers import DeveloperSerializer
class DeveloperViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = DeveloperSerializer
def get_queryset(self):
return (
Developer.objects
.select_related("company") # JOIN for FK
.prefetch_related("skills") # separate query for M2M
.order_by("-id")
)Sans select_related et prefetch_related dans le ViewSet, chaque développeur sérialisé déclenche des requêtes individuelles pour son entreprise et ses compétences. Sur une liste de 50 développeurs, cela signifie 1 + 50 + 50 = 101 requêtes au lieu de 3.
Permissions et authentification personnalisées DRF
Une question fréquente en entretien DRF : « Écrire une permission personnalisée qui restreint l'accès en fonction du rôle de l'utilisateur et du propriétaire de l'objet. »
# permissions.py
from rest_framework.permissions import BasePermission
class IsOwnerOrAdmin(BasePermission):
"""
Object-level permission:
- Admin users can access any object
- Regular users can only access objects they own
"""
message = "Access restricted to the object owner or admin users."
def has_object_permission(self, request, view, obj):
if request.user.is_staff:
return True
# Assumes the model has an 'owner' field pointing to User
return obj.owner == request.user# views.py — Applying custom permissions
from rest_framework import viewsets, permissions
from .permissions import IsOwnerOrAdmin
class ProjectViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, IsOwnerOrAdmin]
def get_queryset(self):
# Non-admin users only see their own projects
if self.request.user.is_staff:
return Project.objects.all()
return Project.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user) # auto-assign ownerLa nuance recherchée par les recruteurs : has_permission s'exécute à chaque requête (niveau liste), tandis que has_object_permission ne s'exécute que lorsque get_object() est appelé (niveau détail). Oublier de surcharger get_queryset pour les vues liste crée une faille de sécurité où les utilisateurs peuvent voir des objets qui ne leur appartiennent pas.
Se fier uniquement à has_object_permission sans filtrer le queryset laisse l'endpoint de liste sans protection. Il faut toujours combiner les permissions au niveau objet avec un get_queryset filtré pour appliquer le contrôle d'accès sur les vues liste et détail.
Throttling et patterns de pagination DRF
Le throttling et la pagination sont des questions de suivi standard. Les recruteurs veulent voir que le candidat comprend la configuration d'API prête pour la production.
# settings.py — Production DRF configuration
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "20/minute", # unauthenticated users
"user": "200/minute", # authenticated users
},
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.CursorPagination",
"PAGE_SIZE": 25,
}# pagination.py — Custom cursor pagination for consistent ordering
from rest_framework.pagination import CursorPagination
class CreatedAtCursorPagination(CursorPagination):
page_size = 25
ordering = "-created_at" # must be a unique, sequential field
cursor_query_param = "cursor" # ?cursor=abc123La pagination par curseur surpasse la pagination par offset sur les grands jeux de données car elle ne nécessite pas de compter le nombre total de lignes. Le compromis : les clients ne peuvent pas sauter à une page arbitraire. C'est la réponse attendue lorsque les recruteurs demandent « Pourquoi choisiriez-vous la pagination par curseur plutôt que la pagination par numéro de page ? »
Sources
- Django 6.1 Release Notes : mode de récupération FETCH_PEERS pour la prévention automatique du N+1
- Django 6.0 Release Notes : CompositePrimaryKey et framework Background Tasks
- Documentation Django REST Framework : patterns de Serializer et ViewSet
Points clés pour la préparation aux entretiens Django
- Optimisation de l'ORM : associer la méthode de requête au type de relation.
select_relatedpour ForeignKey/OneToOne,prefetch_relatedpour ManyToMany et FK inversée. Le modeFETCH_PEERSde Django 6.1 fournit une prévention automatique du N+1 lors de l'itération sur les querysets. - QuerySets personnalisés : permettent une logique métier chaînable et réutilisable. Les méthodes de Manager personnalisé ne peuvent pas être enchaînées après le premier appel, mais les méthodes QuerySet le peuvent.
- Architecture middleware : la requête circule de haut en bas, la réponse de bas en haut. Court-circuiter en retournant une réponse avant
get_response()est un pattern fondamental pour le rate limiting, les vérifications d'authentification et la validation des requêtes. - Performances des serializers DRF : les serializers imbriqués nécessitent une optimisation explicite du queryset dans le ViewSet. Sans
select_related/prefetch_related, la sérialisation provoque des requêtes N+1 à grande échelle. - Permissions DRF : combiner
has_object_permissionavec unget_querysetfiltré pour appliquer le contrôle d'accès sur les endpoints liste et détail. Les permissions au niveau objet seules laissent les vues liste sans protection. - Throttling et pagination : la pagination par curseur évolue mieux que l'offset sur les grandes tables. Configurer des taux de throttling distincts pour les utilisateurs anonymes et authentifiés.
Pour consolider ces connaissances, il est recommandé de pratiquer avec des questions d'entretien sur l'ORM Django et des questions sur le middleware Django sur SharpSkill avant le véritable entretien.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tu saurais repérer le bug en Django ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 13 septembre 2026
Tags
Partager
Articles similaires

Django 5.2 : Middleware Personnalisé et Gestion des Signaux pour les Entretiens Techniques
Guide complet sur les middleware personnalisés et les signaux dans Django 5.2. Exemples pratiques de middleware de logging, middleware asynchrone, signaux pre_save/post_save et questions fréquentes en entretien.

Vues asynchrones Django et ASGI en 2026 : performance et questions d’entretien
Une plongée en profondeur dans les vues asynchrones Django et ASGI en 2026 : leur fonctionnement interne, quel serveur déployer, l’ORM asynchrone et le piège SynchronousOnlyOperation, plus des questions d’entretien.

Django et PostgreSQL en 2026 : indexation, recherche plein texte et questions d'entretien
Guide pratique de l'optimisation Django PostgreSQL : index B-tree, partiels et couvrants, recherche plein texte avec SearchVector et GIN, plus les questions d'entretien 2026.