# 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. - Published: 2026-04-03 - Updated: 2026-04-03 - Author: SharpSkill - Tags: django, python, entretien, orm, middleware, drf, rest-api - Reading time: 9 min --- 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. > **Ce que les recruteurs évaluent réellement** > > 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`. ```python # 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. ```python # 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. ```python # 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. ```python # 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 LTS : clés primaires composites** > > 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. ```python # 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 response ``` Une 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. ```python # 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. ```python # 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. ## Questions d'entretien DRF : performances des serializers Les questions sur les serializers de [Django REST Framework](https://www.django-rest-framework.org/) 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 ? » ```python # 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. ```python # 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. » ```python # 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 ``` ```python # 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 owner ``` La 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. > **Erreur de sécurité DRF courante** > > 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. ```python # 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, } ``` ```python # 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=abc123 ``` La 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_related` pour ForeignKey/OneToOne, `prefetch_related` pour 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_permission` avec un `get_queryset` filtré 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](/technologies/django/interview-questions/django-orm-advanced) et des [questions sur le middleware Django](/technologies/django/interview-questions/django-middleware) sur SharpSkill avant le véritable entretien. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/django/django-interview-orm-middleware-drf