# Questions d'entretien Django et Python : Top 25 en 2026 > Les 25 questions d'entretien Django et Python les plus posées. ORM, vues, middleware, DRF, signaux et optimisation avec réponses détaillées et exemples de code. - Published: 2026-01-28 - Updated: 2026-03-28 - Author: SharpSkill - Tags: django, python, interview, django rest framework, entretien technique - Reading time: 20 min --- Les entretiens Django évaluent la maîtrise du framework web Python le plus populaire, la compréhension de l'ORM, l'architecture MVT, et la capacité à construire des API REST robustes. Ce guide couvre les 25 questions les plus posées, des fondamentaux Django jusqu'aux patterns avancés de Django REST Framework. > **Conseil pour l'entretien** > > Les recruteurs apprécient les candidats qui expliquent les choix architecturaux de Django. Comprendre pourquoi le framework adopte certaines conventions (convention over configuration) fait la différence en entretien. ## Fondamentaux Django ### Question 1 : Décrivez l'architecture MVT de Django L'architecture Model-View-Template (MVT) est la variante Django du pattern MVC. Le framework gère automatiquement la partie contrôleur, simplifiant le développement. ```python # models.py # Le Model représente la structure des données et la logique métier from django.db import models class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() published_at = models.DateTimeField(auto_now_add=True) author = models.ForeignKey( "auth.User", on_delete=models.CASCADE, # Supprime les articles si l'auteur est supprimé related_name="articles" # Accès inverse : user.articles.all() ) class Meta: ordering = ["-published_at"] # Tri par défaut def __str__(self): return self.title ``` ```python # views.py # La View contient la logique de traitement des requêtes from django.shortcuts import render, get_object_or_404 def article_detail(request, pk): # get_object_or_404 lève Http404 si l'objet n'existe pas article = get_object_or_404(Article, pk=pk) return render(request, "blog/article_detail.html", {"article": article}) ``` Dans MVT, Django joue le rôle de contrôleur en routant les URLs vers les vues appropriées via `urls.py`. Le Template gère la présentation HTML. ### Question 2 : Quelle est la différence entre un projet et une application Django ? Un **projet** est la configuration globale (settings, URLs racine, WSGI/ASGI). Une **application** est un module réutilisable avec une responsabilité unique. Un projet contient plusieurs applications. ```python # Créer un projet et une application # django-admin startproject myproject # python manage.py startapp blog # settings.py # Enregistrement des applications dans le projet INSTALLED_APPS = [ "django.contrib.admin", # Interface d'administration "django.contrib.auth", # Authentification "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", # Applications personnalisées "blog.apps.BlogConfig", # Application blog "users.apps.UsersConfig", # Application utilisateurs ] ``` Chaque application suit le principe de responsabilité unique et peut être réutilisée dans d'autres projets. ### Question 3 : Expliquez le cycle de vie d'une requête Django La requête traverse plusieurs couches avant d'atteindre la vue. Comprendre ce cycle est essentiel pour le debugging et l'optimisation. ```python # middleware.py # Les middlewares interceptent chaque requête/réponse class RequestTimingMiddleware: """Middleware qui mesure le temps de traitement.""" def __init__(self, get_response): self.get_response = get_response # Référence au middleware suivant def __call__(self, request): import time start = time.time() # Phase requête : avant la vue response = self.get_response(request) # Phase réponse : après la vue duration = time.time() - start response["X-Request-Duration"] = f"{duration:.3f}s" return response ``` Le cycle complet : requête HTTP → WSGI/ASGI → middlewares (process_request) → URL resolver → vue → middlewares (process_response) → réponse HTTP. ## ORM et Base de Données ### Question 4 : Comment fonctionne le QuerySet de Django et quel est son lazy loading ? Les QuerySets sont évalués paresseusement : aucune requête SQL n'est exécutée tant que les données ne sont pas effectivement utilisées. ```python # queryset_lazy.py # Démonstration du lazy loading des QuerySets # Aucune requête SQL n'est exécutée ici qs = Article.objects.filter(published=True) # Pas de requête qs = qs.exclude(title="Draft") # Toujours pas qs = qs.order_by("-created_at") # Toujours pas # La requête SQL est exécutée UNIQUEMENT ici for article in qs: # UNE seule requête SQL combinée print(article.title) # Autres déclencheurs d'évaluation list(qs) # Conversion en liste qs[0] # Accès par index len(qs) # Comptage (préférer qs.count()) bool(qs) # Test d'existence (préférer qs.exists()) ``` Cette évaluation paresseuse permet de chaîner les filtres sans surcoût, et de n'exécuter qu'une seule requête optimisée. ### Question 5 : Qu'est-ce que le problème N+1 et comment le résoudre ? Le problème N+1 survient quand une requête principale génère N requêtes supplémentaires pour charger les relations. C'est la cause la plus fréquente de lenteur dans les applications Django. ```python # n_plus_one.py # Problème N+1 et solutions # ❌ PROBLÈME : N+1 requêtes # 1 requête pour les articles + 1 requête PAR article pour l'auteur articles = Article.objects.all() for article in articles: print(article.author.username) # Requête SQL à chaque itération ! # ✅ SOLUTION 1 : select_related (ForeignKey, OneToOne) # Joint les tables en UNE seule requête SQL (JOIN) articles = Article.objects.select_related("author").all() for article in articles: print(article.author.username) # Pas de requête supplémentaire # ✅ SOLUTION 2 : prefetch_related (ManyToMany, reverse FK) # Exécute 2 requêtes séparées + assemblage Python articles = Article.objects.prefetch_related("tags").all() for article in articles: print(article.tags.all()) # Données déjà en cache # ✅ SOLUTION 3 : Prefetch personnalisé avec filtre from django.db.models import Prefetch articles = Article.objects.prefetch_related( Prefetch( "comments", queryset=Comment.objects.filter(approved=True).select_related("user"), to_attr="approved_comments" # Attribut personnalisé ) ) ``` Utiliser `select_related` pour les relations ForeignKey/OneToOne (JOIN SQL) et `prefetch_related` pour ManyToMany ou les relations inverses (requêtes séparées). ### Question 6 : Comment créer un Manager personnalisé et quand l'utiliser ? Les Managers personnalisés encapsulent les requêtes fréquentes au niveau du modèle, rendant le code plus lisible et réutilisable. ```python # managers.py # Managers et QuerySets personnalisés class PublishedQuerySet(models.QuerySet): """QuerySet réutilisable pour les articles publiés.""" def published(self): return self.filter(status="published", published_at__lte=timezone.now()) def by_author(self, user): return self.filter(author=user) def popular(self): return self.annotate( comment_count=models.Count("comments") ).order_by("-comment_count") class PublishedManager(models.Manager): """Manager qui expose uniquement les articles publiés.""" def get_queryset(self): return PublishedQuerySet(self.model, using=self._db).published() class Article(models.Model): # ... objects = models.Manager() # Manager par défaut (tous les articles) published = PublishedManager() # Manager personnalisé (publiés uniquement) # Utilisation : # Article.objects.all() → Tous les articles # Article.published.all() → Articles publiés uniquement # Article.published.popular() → Articles publiés triés par popularité ``` Les Managers personnalisés respectent le principe DRY et centralisent la logique de requête. ## Vues et URLs ### Question 7 : Quand utiliser les Class-Based Views vs Function-Based Views ? Les Function-Based Views (FBV) offrent simplicité et contrôle explicite. Les Class-Based Views (CBV) apportent réutilisabilité et structure via l'héritage. ```python # views_comparison.py # FBV : Explicite, simple, facile à comprendre from django.http import JsonResponse from django.views.decorators.http import require_http_methods @require_http_methods(["GET", "POST"]) def article_list(request): if request.method == "GET": articles = Article.published.all() return render(request, "articles/list.html", {"articles": articles}) # POST : création d'article form = ArticleForm(request.POST) if form.is_valid(): form.save() return redirect("article-list") return render(request, "articles/list.html", {"form": form}) ``` ```python # views_cbv.py # CBV : Réutilisable, extensible via mixins from django.views.generic import ListView, CreateView from django.contrib.auth.mixins import LoginRequiredMixin class ArticleListView(LoginRequiredMixin, ListView): model = Article template_name = "articles/list.html" context_object_name = "articles" # Nom de la variable dans le template paginate_by = 20 # Pagination automatique def get_queryset(self): # Surcharge pour filtrer les articles publiés return Article.published.all() class ArticleCreateView(LoginRequiredMixin, CreateView): model = Article form_class = ArticleForm success_url = reverse_lazy("article-list") def form_valid(self, form): form.instance.author = self.request.user # Assigne l'auteur return super().form_valid(form) ``` Règle pratique : utiliser les FBV pour la logique simple ou non standard, les CBV pour les opérations CRUD classiques. ### Question 8 : Comment fonctionnent les middlewares dans Django ? Les middlewares sont des hooks qui traitent chaque requête/réponse. Chaque middleware peut intervenir à différentes phases du cycle de traitement. ```python # auth_middleware.py # Middleware d'authentification personnalisé import jwt from django.conf import settings from django.http import JsonResponse class JWTAuthenticationMiddleware: """Vérifie le token JWT sur les endpoints protégés.""" EXEMPT_PATHS = ["/api/auth/login", "/api/auth/register", "/health"] def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # Ignorer les chemins exemptés if any(request.path.startswith(p) for p in self.EXEMPT_PATHS): return self.get_response(request) # Extraire et vérifier le token auth_header = request.headers.get("Authorization", "") if not auth_header.startswith("Bearer "): return JsonResponse({"error": "Token manquant"}, status=401) try: token = auth_header.split(" ")[1] payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) request.user_id = payload["user_id"] # Attache au request except jwt.ExpiredSignatureError: return JsonResponse({"error": "Token expiré"}, status=401) except jwt.InvalidTokenError: return JsonResponse({"error": "Token invalide"}, status=401) return self.get_response(request) ``` L'ordre des middlewares dans `MIDDLEWARE` est crucial : ils s'exécutent de haut en bas pour les requêtes et de bas en haut pour les réponses. ## Django REST Framework ### Question 9 : Quelle est la différence entre Serializer et ModelSerializer ? Le `Serializer` définit manuellement chaque champ, tandis que `ModelSerializer` génère automatiquement les champs depuis le modèle. ```python # serializers.py from rest_framework import serializers # Serializer manuel : contrôle total sur chaque champ class ArticleSerializer(serializers.Serializer): id = serializers.IntegerField(read_only=True) title = serializers.CharField(max_length=200) content = serializers.CharField() author_name = serializers.SerializerMethodField() def get_author_name(self, obj): return obj.author.get_full_name() def create(self, validated_data): return Article.objects.create(**validated_data) def update(self, instance, validated_data): instance.title = validated_data.get("title", instance.title) instance.content = validated_data.get("content", instance.content) instance.save() return instance # ModelSerializer : génération automatique des champs class ArticleModelSerializer(serializers.ModelSerializer): author_name = serializers.SerializerMethodField() comment_count = serializers.IntegerField(read_only=True) class Meta: model = Article fields = ["id", "title", "content", "author", "author_name", "comment_count", "published_at"] read_only_fields = ["published_at"] def get_author_name(self, obj): return obj.author.get_full_name() ``` Préférer `ModelSerializer` pour les cas standard et `Serializer` quand la représentation diffère significativement du modèle. ### Question 10 : Comment implémenter la pagination dans DRF ? DRF propose plusieurs stratégies de pagination configurables globalement ou par vue. ```python # settings.py # Configuration globale de la pagination REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": 20, } # pagination.py # Pagination personnalisée par vue from rest_framework.pagination import CursorPagination, LimitOffsetPagination class ArticleCursorPagination(CursorPagination): """Pagination par curseur : performante pour les grands datasets.""" page_size = 20 ordering = "-published_at" # Champ indexé requis cursor_query_param = "cursor" class ArticleLimitOffsetPagination(LimitOffsetPagination): """Pagination offset/limit : flexible mais moins performante.""" default_limit = 20 max_limit = 100 # views.py class ArticleViewSet(viewsets.ModelViewSet): queryset = Article.published.all() serializer_class = ArticleModelSerializer pagination_class = ArticleCursorPagination # Pagination spécifique ``` La pagination par curseur est recommandée pour les datasets volumineux car elle reste performante quelle que soit la page, contrairement à OFFSET/LIMIT. ### Question 11 : Comment sécuriser une API avec les permissions DRF ? DRF propose un système de permissions modulaire combinant authentification et autorisations granulaires. ```python # permissions.py from rest_framework.permissions import BasePermission, IsAuthenticated class IsAuthorOrReadOnly(BasePermission): """Seul l'auteur peut modifier, tout le monde peut lire.""" def has_object_permission(self, request, view, obj): # GET, HEAD, OPTIONS sont toujours autorisés if request.method in ("GET", "HEAD", "OPTIONS"): return True # Seul l'auteur peut modifier ou supprimer return obj.author == request.user class IsAdminOrManager(BasePermission): """Accès réservé aux admins et managers.""" def has_permission(self, request, view): return ( request.user.is_authenticated and request.user.role in ("admin", "manager") ) # views.py from rest_framework.viewsets import ModelViewSet from rest_framework.throttling import UserRateThrottle class ArticleViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsAuthorOrReadOnly] throttle_classes = [UserRateThrottle] # Rate limiting def get_permissions(self): # Permissions dynamiques selon l'action if self.action == "destroy": return [IsAdminOrManager()] return super().get_permissions() ``` Combiner `permission_classes` au niveau de la vue et `has_object_permission` pour un contrôle fin objet par objet. ## Signaux et Tâches Asynchrones ### Question 12 : Comment fonctionnent les signaux Django et quand les utiliser ? Les signaux permettent de découpler les composants en réagissant à des événements du framework ou de l'application. ```python # signals.py from django.db.models.signals import post_save, pre_delete from django.dispatch import receiver from django.core.mail import send_mail @receiver(post_save, sender=Article) def notify_on_publish(sender, instance, created, **kwargs): """Envoie une notification quand un article est publié.""" if not created and instance.status == "published": # Déclenché uniquement lors de la publication (pas la création) subscribers = instance.author.subscribers.values_list("email", flat=True) send_mail( subject=f"Nouvel article : {instance.title}", message=f"Découvrez le dernier article de {instance.author.username}", from_email="noreply@example.com", recipient_list=list(subscribers), ) @receiver(pre_delete, sender=Article) def cleanup_article_files(sender, instance, **kwargs): """Supprime les fichiers associés avant la suppression de l'article.""" if instance.cover_image: instance.cover_image.delete(save=False) # Supprime le fichier physique ``` Les signaux conviennent pour les effets de bord légers (logging, cache invalidation). Pour les tâches lourdes, préférer Celery. ### Question 13 : Comment intégrer Celery avec Django pour les tâches asynchrones ? Celery permet d'exécuter des tâches en arrière-plan, essentiel pour les opérations longues comme l'envoi d'emails ou le traitement de fichiers. ```python # celery_config.py # Configuration Celery dans le projet Django import os from celery import Celery os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings") app = Celery("myproject") app.config_from_object("django.conf:settings", namespace="CELERY") app.autodiscover_tasks() # Découvre les tasks.py dans chaque app # tasks.py from celery import shared_task from django.core.mail import send_mass_mail @shared_task(bind=True, max_retries=3, default_retry_delay=60) def send_newsletter(self, article_id): """Envoie la newsletter de manière asynchrone.""" try: article = Article.objects.get(id=article_id) subscribers = User.objects.filter(newsletter=True) messages = [ (f"Nouveau : {article.title}", article.content[:200], "noreply@example.com", [sub.email]) for sub in subscribers ] send_mass_mail(messages, fail_silently=False) except Article.DoesNotExist: pass # L'article a été supprimé entre-temps except Exception as exc: self.retry(exc=exc) # Retry automatique en cas d'erreur # Appel depuis une vue # send_newsletter.delay(article.id) # Exécution asynchrone # send_newsletter.apply_async(args=[article.id], countdown=300) # Délai de 5 min ``` Celery est indispensable en production pour toute opération qui ne doit pas bloquer la réponse HTTP. ## Sécurité et Authentification ### Question 14 : Comment Django protège-t-il contre les attaques CSRF ? Django intègre une protection CSRF par défaut via un middleware qui vérifie un token unique à chaque requête POST. ```python # Protection CSRF dans les formulaires # Le template tag {% csrf_token %} génère un champ hidden # Pour les API (DRF), CSRF est souvent désactivé en faveur de tokens # settings.py REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.SessionAuthentication", # Inclut CSRF "rest_framework.authentication.TokenAuthentication", # Pas de CSRF ], } # Pour les vues AJAX avec session auth # Le cookie csrftoken doit être envoyé dans le header X-CSRFToken ``` ```python # csrf_exemption.py # Exempter une vue spécifique (avec précaution) from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie @ensure_csrf_cookie def get_csrf_token(request): """Endpoint qui force l'envoi du cookie CSRF au client.""" return JsonResponse({"detail": "CSRF cookie set"}) @csrf_exempt # ⚠️ À utiliser uniquement pour les webhooks externes def stripe_webhook(request): """Webhook Stripe - authentifié par signature, pas par CSRF.""" payload = request.body sig_header = request.headers.get("Stripe-Signature") # Vérification par signature Stripe à la place ``` Ne jamais désactiver CSRF globalement. Utiliser `csrf_exempt` uniquement sur les endpoints authentifiés autrement (webhooks, API tokens). ### Question 15 : Comment implémenter l'authentification personnalisée dans Django ? Django permet de remplacer le modèle User par défaut et de personnaliser le backend d'authentification. ```python # models.py from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin class CustomUserManager(BaseUserManager): """Manager pour le modèle User personnalisé.""" def create_user(self, email, password=None, **extra_fields): if not email: raise ValueError("L'email est obligatoire") email = self.normalize_email(email) # Normalise le domaine user = self.model(email=email, **extra_fields) user.set_password(password) # Hash le mot de passe user.save(using=self._db) return user def create_superuser(self, email, password=None, **extra_fields): extra_fields.setdefault("is_staff", True) extra_fields.setdefault("is_superuser", True) return self.create_user(email, password, **extra_fields) class CustomUser(AbstractBaseUser, PermissionsMixin): email = models.EmailField(unique=True) # Login par email username = models.CharField(max_length=30, blank=True) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) date_joined = models.DateTimeField(auto_now_add=True) objects = CustomUserManager() USERNAME_FIELD = "email" # Champ utilisé pour le login REQUIRED_FIELDS = [] # Champs requis en plus de USERNAME_FIELD # settings.py AUTH_USER_MODEL = "users.CustomUser" # Avant la première migration ! ``` Définir `AUTH_USER_MODEL` dès le début du projet. Le changer après les premières migrations est complexe et risqué. ## Optimisation et Performance ### Question 16 : Comment optimiser les requêtes ORM Django ? L'optimisation des requêtes est critique pour la performance. Plusieurs techniques réduisent le nombre et le coût des requêtes SQL. ```python # query_optimization.py from django.db.models import F, Q, Count, Avg, Prefetch # 1. Only/Defer : charger uniquement les champs nécessaires articles = Article.objects.only("title", "published_at") # SELECT title, published_at heavy_articles = Article.objects.defer("content") # Tout SAUF content # 2. Agrégations au niveau SQL (pas Python) stats = Article.objects.aggregate( total=Count("id"), avg_views=Avg("view_count"), ) # 3. F() expressions : opérations au niveau SQL Article.objects.filter(published=True).update( view_count=F("view_count") + 1 # Incrémentation atomique en SQL ) # 4. Q() objects : requêtes complexes results = Article.objects.filter( Q(title__icontains="django") | Q(tags__name="python"), ~Q(status="draft"), # NOT draft published_at__year=2026 ) # 5. Bulk operations : réduire les requêtes INSERT/UPDATE articles = [Article(title=f"Article {i}") for i in range(100)] Article.objects.bulk_create(articles, batch_size=50) # 2 requêtes au lieu de 100 Article.objects.filter(status="draft").update(status="archived") # 1 requête ``` Utiliser `django-debug-toolbar` en développement pour identifier les requêtes lentes et le problème N+1. ### Question 17 : Comment implémenter le caching dans Django ? Django propose un framework de cache multi-niveaux : par vue, par template fragment, ou par données arbitraires. ```python # settings.py CACHES = { "default": { "BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", } } # cache_strategies.py from django.core.cache import cache from django.views.decorators.cache import cache_page from django.utils.decorators import method_decorator # Cache par vue : met en cache la réponse HTTP entière @cache_page(60 * 15) # 15 minutes def article_list(request): return render(request, "articles/list.html", {"articles": Article.published.all()}) # Cache de données : contrôle granulaire def get_popular_articles(): cache_key = "popular_articles_v1" articles = cache.get(cache_key) if articles is None: articles = list( Article.published.popular()[:10].values("id", "title", "view_count") ) cache.set(cache_key, articles, timeout=60 * 30) # 30 min return articles # Invalidation du cache def invalidate_article_cache(article_id): cache.delete(f"article_{article_id}") cache.delete("popular_articles_v1") cache.delete_pattern("article_list_*") # Avec django-redis ``` Privilégier Redis comme backend de cache en production pour la persistance et les fonctionnalités avancées (patterns, TTL). ## Migrations et Gestion de la Base ### Question 18 : Comment gérer les migrations complexes dans Django ? Les migrations Django gèrent l'évolution du schéma de base de données de manière versionnée et reproductible. ```python # 0005_migrate_data.py # Migration de données personnalisée from django.db import migrations def migrate_user_roles(apps, schema_editor): """Convertit les booléens is_admin en rôles textuels.""" User = apps.get_model("users", "CustomUser") # Utiliser apps.get_model() pour accéder au modèle historique User.objects.filter(is_admin=True).update(role="admin") User.objects.filter(is_admin=False, is_staff=True).update(role="manager") User.objects.filter(is_admin=False, is_staff=False).update(role="user") def reverse_migrate(apps, schema_editor): """Migration inverse pour le rollback.""" User = apps.get_model("users", "CustomUser") User.objects.filter(role="admin").update(is_admin=True) class Migration(migrations.Migration): dependencies = [ ("users", "0004_add_role_field"), ] operations = [ migrations.RunPython(migrate_user_roles, reverse_migrate), ] ``` Toujours fournir une fonction `reverse` pour permettre le rollback. Tester les migrations sur une copie de la base de production avant le déploiement. ### Question 19 : Comment créer des indexes personnalisés pour l'optimisation ? Les indexes accélèrent les requêtes fréquentes mais augmentent le coût des écritures. Un choix judicieux est essentiel. ```python # models.py class Article(models.Model): title = models.CharField(max_length=200) slug = models.SlugField(unique=True) status = models.CharField(max_length=20, db_index=True) # Index simple published_at = models.DateTimeField(null=True) author = models.ForeignKey("auth.User", on_delete=models.CASCADE) class Meta: indexes = [ # Index composé pour les requêtes fréquentes models.Index(fields=["status", "-published_at"], name="idx_status_date"), # Index partiel : uniquement les articles publiés models.Index( fields=["published_at"], condition=models.Q(status="published"), name="idx_published_articles" ), # Index GIN pour la recherche full-text (PostgreSQL) GinIndex(fields=["search_vector"], name="idx_search"), ] ``` Les indexes composés suivent l'ordre des colonnes : le champ le plus sélectif doit être en premier. ## Tests et Qualité ### Question 20 : Comment structurer les tests dans un projet Django ? Django fournit un framework de test robuste basé sur unittest, enrichi par pytest-django pour plus de flexibilité. ```python # tests/test_views.py from django.test import TestCase, Client from django.urls import reverse from rest_framework.test import APITestCase, APIClient class ArticleViewTests(TestCase): """Tests des vues avec le client Django.""" def setUp(self): self.client = Client() self.user = CustomUser.objects.create_user( email="test@example.com", password="testpass123" ) self.article = Article.objects.create( title="Test Article", content="Content here", author=self.user, status="published" ) def test_article_list_returns_200(self): response = self.client.get(reverse("article-list")) self.assertEqual(response.status_code, 200) self.assertContains(response, "Test Article") def test_create_article_requires_auth(self): response = self.client.post(reverse("article-create"), {"title": "New"}) self.assertEqual(response.status_code, 302) # Redirige vers login class ArticleAPITests(APITestCase): """Tests de l'API REST avec DRF.""" def setUp(self): self.user = CustomUser.objects.create_user( email="api@example.com", password="testpass123" ) self.client = APIClient() self.client.force_authenticate(user=self.user) def test_create_article_via_api(self): data = {"title": "API Article", "content": "Created via API"} response = self.client.post("/api/articles/", data, format="json") self.assertEqual(response.status_code, 201) self.assertEqual(Article.objects.count(), 1) ``` Séparer les tests en fichiers par domaine : `test_models.py`, `test_views.py`, `test_serializers.py`, `test_services.py`. ### Question 21 : Comment utiliser les fixtures et factories pour les tests ? Les factories (avec factory_boy) sont préférées aux fixtures JSON pour la flexibilité et la maintenabilité des données de test. ```python # factories.py import factory from factory.django import DjangoModelFactory class UserFactory(DjangoModelFactory): class Meta: model = CustomUser email = factory.Sequence(lambda n: f"user{n}@example.com") username = factory.Faker("user_name") is_active = True class ArticleFactory(DjangoModelFactory): class Meta: model = Article title = factory.Faker("sentence", nb_words=5) content = factory.Faker("paragraphs", nb=3) author = factory.SubFactory(UserFactory) # Crée un user automatiquement status = "published" class Params: draft = factory.Trait(status="draft", published_at=None) # tests.py def test_published_articles_count(self): ArticleFactory.create_batch(5) # 5 articles publiés ArticleFactory.create_batch(3, draft=True) # 3 brouillons self.assertEqual(Article.published.count(), 5) ``` Les factories garantissent des données de test cohérentes et évitent les dépendances entre tests. ## Patterns Avancés ### Question 22 : Comment implémenter les WebSockets avec Django Channels ? Django Channels étend Django au-delà de HTTP pour supporter les WebSockets, les protocoles temps réel et les tâches en arrière-plan. ```python # consumers.py import json from channels.generic.websocket import AsyncWebsocketConsumer class ChatConsumer(AsyncWebsocketConsumer): """Consumer WebSocket pour un chat en temps réel.""" async def connect(self): self.room_name = self.scope["url_route"]["kwargs"]["room_name"] self.room_group = f"chat_{self.room_name}" # Rejoindre le groupe de la room await self.channel_layer.group_add(self.room_group, self.channel_name) await self.accept() async def disconnect(self, close_code): # Quitter le groupe await self.channel_layer.group_discard(self.room_group, self.channel_name) async def receive(self, text_data): data = json.loads(text_data) # Diffuser le message à tout le groupe await self.channel_layer.group_send( self.room_group, {"type": "chat.message", "message": data["message"], "username": self.scope["user"].username} ) async def chat_message(self, event): # Envoie le message au client WebSocket await self.send(text_data=json.dumps({ "message": event["message"], "username": event["username"], })) ``` Django Channels utilise ASGI (au lieu de WSGI) et nécessite un serveur compatible comme Daphne ou Uvicorn. ### Question 23 : Expliquez le pattern Repository et Service Layer en Django Le pattern Service Layer sépare la logique métier des vues et de l'ORM, facilitant les tests et la maintenance. ```python # services/article_service.py from django.db import transaction class ArticleService: """Service encapsulant la logique métier des articles.""" @staticmethod def publish_article(article_id: int, user) -> Article: """Publie un article avec toutes les vérifications.""" article = Article.objects.select_for_update().get(id=article_id) if article.author != user: raise PermissionError("Seul l'auteur peut publier cet article") if article.status == "published": raise ValueError("L'article est déjà publié") article.status = "published" article.published_at = timezone.now() article.save(update_fields=["status", "published_at"]) # Effets de bord : notification, cache, analytics send_newsletter.delay(article.id) cache.delete("popular_articles_v1") return article @staticmethod @transaction.atomic def bulk_archive(article_ids: list[int], user) -> int: """Archive plusieurs articles dans une transaction.""" updated = Article.objects.filter( id__in=article_ids, author=user, status="published" ).update(status="archived", archived_at=timezone.now()) return updated ``` Le Service Layer est le point d'entrée pour toute logique métier. Les vues et serializers délèguent au service, jamais directement à l'ORM. ### Question 24 : Comment gérer les variables d'environnement et la configuration multi-environnement ? La gestion de la configuration suit le principe des 12-Factor Apps : séparation stricte entre configuration et code. ```python # settings/base.py # Configuration partagée entre tous les environnements import os from pathlib import Path from dotenv import load_dotenv load_dotenv() # Charge le fichier .env BASE_DIR = Path(__file__).resolve().parent.parent.parent SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] # Obligatoire, pas de valeur par défaut DEBUG = os.environ.get("DEBUG", "False").lower() == "true" ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",") DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": os.environ.get("DB_NAME", "myapp"), "USER": os.environ.get("DB_USER", "postgres"), "HOST": os.environ.get("DB_HOST", "localhost"), "PORT": os.environ.get("DB_PORT", "5432"), } } # settings/production.py from .base import * DEBUG = False SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 # 1 an ``` Ne jamais commiter les secrets dans le code. Utiliser des variables d'environnement ou un gestionnaire de secrets (Vault, AWS Secrets Manager). ### Question 25 : Comment déployer une application Django en production ? Le déploiement production nécessite une checklist complète couvrant la sécurité, la performance et la fiabilité. ```python # Checklist de déploiement Django # 1. Vérification avec la commande built-in # python manage.py check --deploy # 2. Configuration WSGI/ASGI pour la production # gunicorn.conf.py import multiprocessing bind = "0.0.0.0:8000" workers = multiprocessing.cpu_count() * 2 + 1 # Formule recommandée worker_class = "gthread" # Workers avec threads threads = 4 max_requests = 1000 # Recycler les workers pour éviter les fuites mémoire max_requests_jitter = 50 timeout = 30 accesslog = "-" # Logs sur stdout errorlog = "-" ``` ```python # docker-compose.yml (configuration typique) # Services : web (gunicorn), db (postgres), redis (cache/celery), worker (celery) # 3. Fichiers statiques # python manage.py collectstatic --noinput # Servir via nginx ou un CDN (WhiteNoise pour les cas simples) # 4. Configuration nginx # - Proxy vers gunicorn sur le port 8000 # - Servir /static/ et /media/ directement # - Activer gzip, HTTP/2, et les en-têtes de sécurité ``` Exécuter `python manage.py check --deploy` avant chaque mise en production. Cette commande vérifie les paramètres de sécurité essentiels. ## Conclusion Ces 25 questions couvrent l'essentiel des entretiens Django et Python, des fondamentaux de l'architecture MVT aux patterns de déploiement production. Checklist de préparation : - ✅ Architecture MVT et cycle de vie des requêtes - ✅ ORM : QuerySets, N+1, select_related, prefetch_related - ✅ Django REST Framework : serializers, pagination, permissions - ✅ Sécurité : CSRF, authentification, permissions - ✅ Performance : optimisation ORM, caching, indexes - ✅ Tests : TestCase, APITestCase, factories - ✅ Patterns avancés : Channels, Service Layer, déploiement > **Aller plus loin** > > Chaque question mérite un approfondissement avec la documentation officielle de Django. Les recruteurs valorisent les candidats qui connaissent les subtilités du framework et savent justifier leurs choix techniques. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/django/questions-entretien-django-python