Questions d'entretien Django : ORM, Middleware et DRF en profondeur
Questions d'entretien Django couvrant l'optimisation de l'ORM avec select_related et prefetch_related, 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 5.2 LTS et DRF 3.17.
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), 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 — Approche correcte pour ForeignKey (objet unique)
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 ...
# Approche correcte pour ManyToMany (objets multiples)
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.
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 — Chaînage de QuerySets en 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 5.2 a introduit CompositePrimaryKey, une fonctionnalité attendue de longue date pour les modèles nécessitant des clés primaires multi-colonnes. Les questions d'entretien sur cette fonctionnalité deviennent de plus en plus fréquentes, en particulier pour les candidats travaillant avec des bases de données héritées ou des schémas d'entrepôt de données.
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 avec court-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 — Middleware complet avec tous les 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 ? »
Conclusion
Points clés pour la préparation aux entretiens Django :
- Optimisation de l'ORM : toujours associer la méthode de requête au type de relation.
select_relatedpour ForeignKey/OneToOne,prefetch_relatedpour ManyToMany et FK inversée. Les QuerySets personnalisés permettent une logique métier chaînable et réutilisable. - 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.
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.

Django 6.0 en 2026 : clés primaires composites, tâches en arrière-plan et questions d'entretien
Django 6.0 en 2026 : clés primaires composites, tâches en arrière-plan natives, template partials, middleware CSP et questions d'entretien technique.

Django et Celery : traitement asynchrone des tâches et questions d'entretien 2026
Guide Django Celery avec exemples pratiques, routage de tâches, planification Beat, configuration production et questions d'entretien technique 2026.