Python
Django
Fastapi
Junior
Drf
Django-Rest-Framework
Fastapi-Junior
Jwt
Django-Orm-Avance
Entretien-Python-Junior
Qcm-Django-Drf
Modelviewset
Pydantic-Avance
Dependency-Injection
📝
Python Django/FastAPI Junior — DRF & Injection
20 questions niveau junior : ORM avancé Django, DRF serializers et ViewSets, injection de dépendances FastAPI, auth JWT et middlewares. Pour préparer un entretien Python intermédiaire.
Banque de révision : les 60 questions corrigées
Voici l'intégralité des 60 questions de « Python Django/FastAPI Junior — DRF & Injection », chacune avec sa bonne réponse et son explication. Le quiz interactif ci-dessus en tire une sélection aléatoire à chaque tentative — utilisez cette liste complète pour réviser.
Explication :
filter() retourne un QuerySet (lazy — la requête SQL n'est pas exécutée immédiatement). Différence avec get() qui retourne un objet unique et lève DoesNotExist si introuvable ou MultipleObjectsReturned si plusieurs résultats.
Explication :
Django ORM utilise des lookups avec double underscore.
__icontains = contient (case-insensitive). Autres lookups : __exact, __contains, __startswith, __gt (greater than), __in=[1,2,3], __isnull=True.
Explication :
Un QuerySet est lazy : la requête SQL n'est exécutée qu'au moment de l'évaluation (itération,
list(), slicing, first(), count()). Cela permet de chaîner des filtres sans requêtes multiples : qs.filter(...).exclude(...).order_by(...) → une seule requête SQL.
Mentionner que les QuerySets sont mis en cache après la première évaluation. Attention au problème N+1 : accéder à des relations en boucle déclenche une requête par objet — utiliser
select_related() ou prefetch_related().
Explication :
get_object_or_404(Article, pk=1) (importé depuis django.shortcuts) appelle Article.objects.get(pk=1) et retourne une réponse 404 si l'objet n'existe pas. C'est le raccourci standard dans les vues Django pour éviter un try/except.
Explication :
ManyToManyField(Tag) sur le modèle Article crée une table de jointure automatiquement. ForeignKey = Many-to-One (un article, un auteur). OneToOneField = extension de modèle. Pour la table de jointure personnalisée : ManyToManyField(through='ArticleTag').
Explication :
select_related('auteur') fait un SQL JOIN pour charger l'auteur (ForeignKey) en même temps que l'article → 1 requête au lieu de N+1. Pour les ManyToMany et reverse FK : utiliser prefetch_related('tags') qui fait 2 requêtes avec Python-side joining.
Explication :
DRF (Django REST Framework) est une bibliothèque qui ajoute à Django : Serializers (validation + sérialisation), ViewSets (CRUD automatique), Routers (URLs automatiques), Authentication (Token, Session, JWT avec djangorestframework-simplejwt), Permissions.
En entretien, distinguer DRF de FastAPI : DRF reste dans l'écosystème Django (réutilise models, admin, auth), FastAPI est un framework indépendant plus performant pour les APIs pures.
Explication :
ModelSerializer génère automatiquement les champs depuis le modèle Django. Exemple : class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'titre', 'contenu']Il valide aussi les données entrantes (POST/PUT).
Explication :
ModelViewSet + DefaultRouter génèrent toutes les routes CRUD automatiquement. class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
router = DefaultRouter()
router.register('articles', ArticleViewSet)
Explication :
FastAPI détecte automatiquement qu'un paramètre héritant de
BaseModel est le corps JSON. La validation et la conversion de types se font automatiquement. Si invalide, FastAPI retourne une erreur 422 avec les détails de validation.
Explication :
Field(ge=0) (greater-or-equal) valide que la valeur est ≥ 0. Autres contraintes : gt (>), le (≤), lt (
Explication :
python manage.py makemigrations blog crée les migrations pour l'app blog (analyse models.py). Sans préciser l'app : génère pour toutes les apps. python manage.py migrate applique ensuite. Commandes utiles : showmigrations, sqlmigrate blog 0001 (voir le SQL).
Explication :
FastAPI utilise
Depends() pour l'injection de dépendances. Exemple : def get_db(): ... (générateur de session BDD) puis dans la route : def read_items(db: Session = Depends(get_db)). FastAPI injecte automatiquement la session, la ferme après la requête. Très utile pour auth, settings, pagination.
Mentionner que les dépendances peuvent être imbriquées (une dépendance peut appeler une autre) et avoir leur propre cycle de vie (par requête, par application).
Explication :
@login_required (importé de django.contrib.auth.decorators) redirige les utilisateurs non connectés vers la page de login. Pour les CBV : utiliser LoginRequiredMixin. Pour DRF : permission_classes = [IsAuthenticated].
Explication :
Pattern standard : engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()Puis
db: Session = Depends(get_db) dans les routes. Le yield garantit la fermeture.
Explication :
response_model=ItemResponse dans le décorateur : FastAPI filtre la réponse selon ce schéma Pydantic (évite d'exposer des champs sensibles comme les mots de passe), génère la doc correcte et valide la sortie. Très important pour la sécurité des APIs.
Explication :
order_by('-created_at') trie par ordre décroissant (le - devant le nom de champ). Croissant : order_by('created_at'). Tri multiple : order_by('-date', 'titre'). On peut aussi définir un tri par défaut dans class Meta: ordering = ['-created_at'].
Explication :
Problème N+1 : pour 10 articles, si on accède à
article.auteur en boucle → 1 requête (all articles) + 10 requêtes (auteur de chaque) = 11 requêtes. Solution : Article.objects.select_related('auteur') → 1 seule requête JOIN. Pour ManyToMany : prefetch_related('tags').
En entretien : mentionner django-debug-toolbar pour détecter les N+1 en développement (affiche toutes les requêtes SQL exécutées).
Explication :
Deux méthodes : (1)
a = Article(titre='test'); a.save() — crée l'objet en mémoire puis sauvegarde. (2) Article.objects.create(titre='test') — crée et sauvegarde en une ligne. create() retourne l'objet créé avec son id généré.
Explication :
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='token')
@app.get('/me')
def get_me(token: str = Depends(oauth2_scheme)):
# valider le token JWT
...FastAPI extrait automatiquement le token du header
Authorization: Bearer xxx.
Explication :
CASCADE supprime les enfants quand le parent est supprimé (commentaires supprimés si l'article est supprimé). Autres options : PROTECT (empêche la suppression), SET_NULL (met la FK à NULL), SET_DEFAULT, DO_NOTHING. on_delete est obligatoire en Django 2+.
Explication :
En Python 3.10+ :
description: str | None = None. En Python 3.8+ : from typing import Optional; description: Optional[str] = None. La valeur par défaut None rend le champ optionnel. Sans valeur par défaut, le champ serait requis même si le type est Optional.
Explication :
DRF propose des classes de permission :
IsAuthenticated, IsAdminUser, AllowAny, IsAuthenticatedOrReadOnly. On les applique globalement (DEFAULT_PERMISSION_CLASSES dans settings) ou par vue (permission_classes = [IsAuthenticated]). On peut créer des permissions personnalisées en héritant de BasePermission.
Distinguer permissions (qui peut accéder ?) et authentication (qui êtes-vous ?).
Explication :
return render(request, 'template.html', {'articles': articles, 'titre': 'Blog'}). Le dictionnaire context est injecté dans le template : {{ articles }}, {% for a in articles %}. On peut aussi utiliser RequestContext ou les processors de contexte.
Explication :
@app.middleware('http')
async def add_header(request: Request, call_next):
response = await call_next(request)
response.headers['X-Custom'] = 'value'
return responseOu via
app.add_middleware(CORSMiddleware, ...) pour les middlewares Starlette.
Explication :
auteur = models.ForeignKey(User, on_delete=CASCADE, related_name='articles'). Sans related_name, l'accès inverse est user.article_set.all(). Avec : user.articles.all(). Plus lisible, surtout si plusieurs FK vers le même modèle.
Explication :
router = APIRouter(prefix='/articles', tags=['articles'])
@router.get('/')
def list_articles(): ...
# Dans main.py:
app.include_router(router)Permet d'organiser le code en modules indépendants, chacun avec son propre préfixe, tags et dépendances.
Explication :
FastAPI supporte les deux.
async def : exécuté dans la boucle asyncio, idéal pour les await (appels DB async, HTTP async). def ordinaire : FastAPI l'exécute dans un thread pool (via run_in_threadpool) pour ne pas bloquer la boucle. Ne pas utiliser def avec des opérations I/O bloquantes (psycopg2 sync, requests).
Mentionner asyncpg (PostgreSQL async), httpx (HTTP client async), aiomysql comme alternatives async pour éviter les opérations bloquantes.
Explication :
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=['http://localhost:3000'],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)
Explication :
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save()
return redirect('article-detail', pk=article.pk)
else:
# form.errors contient les erreurs
return render(request, 'form.html', {'form': form})
Explication :
from fastapi.testclient import TestClient
client = TestClient(app)
def test_read_root():
response = client.get('/')
assert response.status_code == 200
assert response.json() == {'message': 'Hello'}Basé sur requests (synchrone). Pour async tests : utiliser httpx.AsyncClient.
Explication :
Deux approches : (1)
import os; SECRET_KEY = os.getenv('SECRET_KEY', 'default'). (2) Avec django-environ ou python-decouple : lit automatiquement le fichier .env. Ne jamais commiter le .env (dans .gitignore). En prod : injecter via le système d'exploitation/container.
Explication :
Les signaux Django permettent d'exécuter du code quand certains événements se produisent. Signaux intégrés :
post_save, pre_delete, m2m_changed, etc. Exemple : créer un profil automatiquement après la création d'un User. On connecte via @receiver(post_save, sender=User).
En entretien : mentionner les limites — les signaux sont synchrones, difficiles à tester, et peuvent créer du couplage implicite. Pour les tâches lourdes, préférer Celery. Éviter d'abuser des signaux (antipattern possible).
Explication :
Deux approches : (1)
class Meta: exclude = ['password'] — exclut le champ complètement. (2) password = serializers.CharField(write_only=True) — accepté en entrée (POST) mais jamais renvoyé en réponse. La méthode write_only est préférable pour les champs nécessaires à la validation.
Explication :
null=True : Django peut stocker NULL en BDD pour ce champ. blank=True : la validation des formulaires/serializers accepte la valeur vide. Pour les CharField/TextField : utiliser blank=True (pas null=True, les strings vides sont préférées à NULL). Pour les relations (FK) : null=True.
Explication :
@app.get('/items/')
def list_items(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
return db.query(Item).offset(skip).limit(limit).all()Pattern standard. Pour une pagination plus avancée : bibliothèque
fastapi-pagination.
Explication :
from django.db import transaction
with transaction.atomic():
a = Article.objects.create(...)
b = Commentaire.objects.create(article=a, ...). Si une exception est levée dans le bloc, toutes les opérations sont rollbackées. Aussi utilisable comme décorateur @transaction.atomic.
Explication :
FastAPI délègue la validation à Pydantic. Si la requête ne respecte pas le schéma, FastAPI retourne automatiquement une réponse
422 Unprocessable Entity avec le détail des erreurs de validation en JSON. Aucun code de validation manuelle nécessaire.
Mentionner Pydantic v2 : validators avec @field_validator, @model_validator, model_config pour personnaliser le comportement (forbid extra fields, strict mode, etc.).
Explication :
Pattern recommandé :class ItemBase(BaseModel): name: str; price: float
class ItemCreate(ItemBase): pass # pas d'id
class ItemResponse(ItemBase): id: int # retourné après créationÉvite de réexposer des champs internes et sépare clairement les structures d'entrée/sortie.
Explication :
ListView avec paginate_by = 10 gère automatiquement la pagination. Dans le template : {% for a in page_obj %} et l'objet page_obj fournit has_next, has_previous, next_page_number, etc. URL avec ?page=2.
Explication :
Article.objects.annotate(nb_commentaires=Count('commentaire')) ajoute un attribut nb_commentaires à chaque article. Fonctions d'agrégation : Count, Sum, Avg, Max, Min. Différence avec aggregate() qui retourne un dictionnaire global.
Explication :
from fastapi import UploadFile, File
@app.post('/upload/')
async def upload(file: UploadFile = File(...)):
content = await file.read()
return {'filename': file.filename, 'size': len(content)}FastAPI supporte
UploadFile (multipart/form-data) nativement.
Explication :
Article.objects.values('id', 'titre') retourne une liste de dict [{'id': 1, 'titre': 'Python'}] au lieu d'objets Article. Plus performant si on n'a pas besoin des méthodes du modèle. values_list('titre', flat=True) retourne une liste plate de valeurs.
Explication :
Choisir Django : application monolithique, admin nécessaire, ORM riche, auth intégrée, équipe familière. Choisir FastAPI : microservice dédié API, besoin d'async (ML inference, WebSockets), haute performance, équipe TypeScript/Pydantic. Les deux peuvent coexister (Django back-office + FastAPI API publique).
Mentionner aussi que Django 4.1+ supporte nativement l'async dans les vues/ORM (en beta) — l'écart avec FastAPI se réduit.
Explication :
Pydantic v2 : from pydantic import ConfigDict
from pydantic.alias_generators import to_camel
class MyModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
first_name: str # sérialisé en 'firstName'
Explication :
Dans
settings.py : MEDIA_ROOT = BASE_DIR / 'media' (dossier physique) et MEDIA_URL = '/media/' (URL publique). En dev, dans urls.py : urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT). En prod : servir via Nginx.
Explication :
Django utilise le double underscore
__ pour traverser les relations. order_by('auteur__nom') fait un JOIN avec la table User et trie par nom. Fonctionne aussi dans filter(auteur__email__icontains='@gmail'). Peut traverser plusieurs niveaux : 'auteur__profil__ville'.
Explication :
@app.get('/articles/', tags=['articles']) ou APIRouter(tags=['articles']). Les routes du même tag sont regroupées dans Swagger UI. On peut enrichir les tags avec des métadonnées : app = FastAPI(openapi_tags=[{'name': 'articles', 'description': '...'}]).
Explication :
obj, created = Tag.objects.get_or_create(nom='Python'). Retourne un tuple : l'objet et un booléen created (True si créé, False si existait déjà). Évite les race conditions. Pour mise à jour conditionnelle : update_or_create().
Explication :
Pattern CRUD FastAPI + SQLAlchemy : (1) Models : classe SQLAlchemy héritant de Base. (2) Schemas : ItemBase, ItemCreate, ItemResponse (Pydantic). (3) CRUD functions : get_item(), create_item() dans
crud.py. (4) Routes : utilisant les CRUD functions + dépendance DB. (5) Database : engine, session, models.Base.metadata.create_all().
Mentionner l'alternative moderne : SQLModel (créé par le même auteur que FastAPI) qui combine SQLAlchemy et Pydantic en une seule classe.
Explication :
from django.test import TestCase
class ArticleViewTest(TestCase):
def test_list_view(self):
response = self.client.get('/articles/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Python')
self.client est un client HTTP de test Django.
Explication :
models.URLField() valide automatiquement que la valeur est une URL bien formée. En BDD : stocké comme VARCHAR(200) par défaut (max_length=200). Autres champs spéciaux : EmailField, IPAddressField, SlugField.
Explication :
Dans
settings.py : DATABASES = {'default': {'ENGINE': 'django.db.backends.postgresql', 'NAME': 'mydb', 'USER': 'postgres', 'PASSWORD': 'secret', 'HOST': 'localhost', 'PORT': '5432'}}Installer psycopg2-binary (driver Python PostgreSQL). Pour une URL : utiliser dj-database-url.
Explication :
from pydantic import EmailStr; email: EmailStr. Pydantic valide automatiquement le format email. Nécessite pip install pydantic[email] (qui installe email-validator). Si invalide : erreur de validation 422 automatique.
Explication :
delete() peut être appelé sur n'importe quel QuerySet. Article.objects.filter(actif=False).delete() supprime en une requête SQL DELETE. Retourne un tuple (n, {'app.Article': n}). Attention : les signaux pre_delete/post_delete sont envoyés pour chaque objet.
Explication :
@app.get('/items/{id}',
response_model=Item,
responses={404: {'model': HTTPError}, 422: {'model': ValidationError}})
def get_item(id: int): ...Ces modèles additionnels enrichissent la documentation OpenAPI sans changer le comportement.
Explication :
name='article-list' dans path(). Dans un template : {% url 'article-list' %}. Dans Python : from django.urls import reverse; reverse('article-list'). Avec paramètres : {% url 'article-detail' pk=article.pk %}. Les noms d'URL permettent de changer les URLs sans casser les liens.
Explication :
Sans ce mode, Pydantic ne peut lire que des dicts. Avec
from_orm(obj) (v1) ou model_validate(obj) (v2), Pydantic lit les attributs d'un objet SQLAlchemy. Config v2 : model_config = ConfigDict(from_attributes=True). Indispensable pour convertir les résultats SQLAlchemy en réponses Pydantic.
Explication :
Stratégie recommandée :
settings/base.py (commun), settings/dev.py (from .base import *; DEBUG=True), settings/prod.py. Variables secrètes dans l'environnement. Lancer avec DJANGO_SETTINGS_MODULE=mysite.settings.prod python manage.py ....
Mentionner
django-environ ou python-decouple pour charger les variables sensibles depuis .env sans les mettre dans les fichiers Python versionnés.
Explication :
db_index=True crée un index BDD sur ce champ. Alternatives : dans class Meta: indexes = [models.Index(fields=['titre', 'date'])] pour des index composites. unique=True crée aussi un index unique. Les FK ont automatiquement un index.