Préparation d'entretien 100% Gratuit angularforall.com

Python Django/FastAPI Junior — DRF & Injection

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
Junior 🔀 Mixte 20 questions ⏱ 15 min
📝

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.

20 questions ⏱ ~15 min Niveau Junior

Partager

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.

  • Un seul objet Article
  • Un QuerySet de tous les Article actifs (bonne réponse)
  • Une liste Python
  • Une requête SQL brute
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.

  • Article.objects.filter(titre="%Python%")
  • Article.objects.filter(titre__icontains="Python") (bonne réponse)
  • Article.objects.where(titre LIKE "Python")
  • Article.objects.filter(titre__contains_nocase="Python")
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.

  • Un objet qui exécute la requête SQL immédiatement lors de sa création
  • Un objet qui représente une requête BDD non encore exécutée, évaluée seulement quand nécessaire (bonne réponse)
  • Un cache de résultats de requêtes
  • Une liste de dictionnaires représentant les lignes BDD
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().

  • Article.objects.find_or_404(pk=1)
  • get_object_or_404(Article, pk=1) (bonne réponse)
  • Article.objects.get_or_404(pk=1)
  • Article.find_or_raise(pk=1)
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.

  • models.ForeignKey()
  • models.ManyToManyField() (bonne réponse)
  • models.OneToOneField()
  • models.MultipleField()
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').

  • Sélectionne uniquement certains champs
  • Effectue une jointure SQL pour pré-charger les relations ForeignKey en une seule requête (bonne réponse)
  • Charge les relations ManyToMany
  • Filtre les objets selon une relation
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.

  • Un remplacement de Django pour créer des APIs
  • Une extension Django pour construire facilement des APIs REST avec sérialisation, auth et vues (bonne réponse)
  • Un client HTTP pour consommer des APIs dans Django
  • Un générateur de documentation d'API
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.

  • Génère des modèles Django depuis un JSON
  • Sérialise/désérialise automatiquement un modèle Django vers/depuis JSON (bonne réponse)
  • Crée des formulaires HTML depuis un modèle
  • Génère les migrations automatiquement
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).

  • Créer 5 vues séparées (list, create, retrieve, update, destroy)
  • Utiliser un ModelViewSet avec un Router (bonne réponse)
  • Utiliser @api_view avec toutes les méthodes HTTP
  • Hériter de CRUDView
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)

  • body = await request.json()
  • Déclarer un paramètre de type BaseModel dans la signature de la fonction (bonne réponse)
  • Utiliser @Body() devant la fonction
  • body = request.body()
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.

  • age: int = min(0)
  • age: int = Field(ge=0) (bonne réponse)
  • age: PositiveInt
  • @validate(min=0)\nage: int
Explication : Field(ge=0) (greater-or-equal) valide que la valeur est ≥ 0. Autres contraintes : gt (>), le (≤), lt (

  • python manage.py migrate --initial
  • python manage.py makemigrations blog (bonne réponse)
  • python manage.py createdb blog
  • python manage.py schema blog
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).

  • L'importation automatique de paquets pip manquants
  • Un système pour fournir des composants partagés (DB session, auth, settings) aux routes (bonne réponse)
  • Un pattern pour injecter des données dans les templates
  • Le système de cache de FastAPI
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).

  • @authenticated
  • @require_login
  • @login_required (bonne réponse)
  • @auth_required
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].

  • FastAPI intègre directement SQLAlchemy sans configuration
  • Créer un engine, une session factory et une dépendance get_db() avec yield (bonne réponse)
  • Utiliser uniquement Tortoise-ORM avec FastAPI
  • Configurer DATABASE_URL dans settings.py
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.

  • @app.get('/items/', returns=ItemResponse)
  • @app.get('/items/', response_model=ItemResponse) (bonne réponse)
  • @app.get('/items/', output=ItemResponse)
  • return JSONResponse(model=ItemResponse)
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.

  • Article.objects.all().sort("-created_at")
  • Article.objects.all().order_by("-created_at") (bonne réponse)
  • Article.objects.orderby(desc="created_at")
  • Article.objects.all().desc("created_at")
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'].

  • Un bug de numérotation dans les migrations
  • N requêtes supplémentaires déclenchées en boucle lors de l'accès à des relations — résoudre avec select_related/prefetch_related (bonne réponse)
  • Un bug de concurrence dans les QuerySets
  • Le problème lié aux N migrations en attente
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).

  • Article.create(titre="test") .commit()
  • a = Article(titre="test"); a.save()
  • Article.objects.create(titre="test")
  • Les réponses B et C sont correctes (bonne réponse)
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é.

  • @app.get('/', auth=True)
  • Utiliser OAuth2PasswordBearer comme dépendance (bonne réponse)
  • Ajouter @require_auth sur la route
  • Configurer AUTH=True dans les settings FastAPI
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.

  • Supprime la relation mais garde les objets liés
  • Empêche la suppression si des objets liés existent
  • Supprime automatiquement les objets liés quand l'objet référencé est supprimé (bonne réponse)
  • Met la FK à NULL quand l'objet référencé est supprimé
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+.

  • description: str = Optional
  • description: str | None = None (bonne réponse)
  • description: Optional[str] = Field(optional=True)
  • description?: str
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.

  • Un système de fichiers Linux pour les apps Django
  • Un mécanisme pour contrôler l'accès aux vues selon l'utilisateur et ses droits (bonne réponse)
  • Les permissions PostgreSQL pour Django
  • Un outil de gestion des roles CSS
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 ?).

  • Via des variables globales Python
  • Via un dictionnaire context passé à render() (bonne réponse)
  • Via request.template_data
  • Via des paramètres URL uniquement
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.

  • @app.middleware("http")\nasync def middleware(request, call_next) (bonne réponse)
  • app.use(middleware_function)
  • MIDDLEWARE = [MyMiddleware] dans les settings
  • Créer un fichier middleware.py et l'importer
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.

  • Définir le nom de la colonne FK en BDD
  • Définir le nom de l'accesseur inverse (relation parent → enfants) (bonne réponse)
  • Définir un alias pour le modèle lié
  • Activer la relation bidirectionnelle
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.

  • Utiliser Blueprint() comme Flask
  • Utiliser APIRouter() et l'inclure avec app.include_router() (bonne réponse)
  • Créer un fichier router.py et l'importer directement
  • Utiliser app.namespace()
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.

  • async def est obligatoire, def n'est pas supporté
  • async def est pour les opérations I/O non-bloquantes ; def est exécuté dans un thread pool pour éviter de bloquer (bonne réponse)
  • def est plus rapide qu'async def pour les APIs
  • Il n'y a aucune différence de comportement
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.

  • CORS = True dans les settings
  • app.add_middleware(CORSMiddleware, allow_origins=[...]) (bonne réponse)
  • @app.cors(origins=["*"])
  • Installer le paquet cors séparément
Explication : from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=['http://localhost:3000'], allow_credentials=True, allow_methods=['*'], allow_headers=['*'], )

  • form = MyForm(data=request.POST); form.is_valid() (bonne réponse)
  • request.POST.validate()
  • validate_form(request.POST)
  • MyForm.validate(request.POST)
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})

  • Accéder à /test dans le navigateur
  • Utiliser TestClient de fastapi.testclient ou httpx (bonne réponse)
  • Utiliser unittest.mock uniquement
  • FastAPI n'a pas d'outil de test intégré
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.

  • Directement dans settings.py avec os.getenv()
  • Uniquement via docker-compose.yml
  • Via un fichier .env chargé avec python-decouple ou django-environ
  • Les réponses A et C sont correctes (bonne réponse)
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.

  • Un système de notification push vers le navigateur
  • Un mécanisme de communication découplée entre composants lors d'événements (save, delete) (bonne réponse)
  • Une alerte de sécurité Django
  • Un système de cache invalidation
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).

  • Ajouter le champ dans exclude = ["password"] dans Meta
  • Marquer le champ write_only=True
  • Utiliser response_model_exclude dans les settings
  • Les réponses A et B sont correctes (bonne réponse)
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.

  • Il n'y a aucune différence
  • null=True → NULL en BDD ; blank=True → chaîne vide autorisée dans les formulaires/validation (bonne réponse)
  • blank=True → NULL en BDD ; null=True → vide dans les formulaires
  • Les deux permettent des valeurs vides en BDD uniquement
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.

  • Utiliser Page() intégré à FastAPI
  • Paramètres query skip/limit dans la route et slicing du QuerySet (bonne réponse)
  • Configurer PAGINATION=True dans l'app FastAPI
  • Utiliser @paginate décorateur
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.

  • Article.objects.atomic().save()
  • with transaction.atomic(): ... (bonne réponse)
  • @db.transaction\ndef ma_vue(request): ...
  • transaction.begin(); ...; transaction.commit()
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.

  • Manuellement dans chaque fonction de route
  • Via des schemas Pydantic — validation automatique avec erreur 422 si invalide (bonne réponse)
  • Via des décorateurs de validation comme Flask-WTF
  • FastAPI ne valide pas les données, c'est à l'ORM de le faire
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.).

  • Utiliser un seul schéma pour tout
  • Créer ItemCreate (sans id) et ItemResponse (avec id) en héritant d'un ItemBase (bonne réponse)
  • Utiliser Optional pour tous les champs
  • Définir in_fields et out_fields dans le Meta de Pydantic
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.

  • class ArticleListView(PaginatedView):
  • class ArticleListView(ListView): model = Article; paginate_by = 10 (bonne réponse)
  • class ArticleListView(View): pagination = 10
  • Article.objects.paginate(10)
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.

  • Pour ajouter des commentaires aux requêtes SQL
  • Pour ajouter des champs calculés (agrégations) à chaque objet du QuerySet (bonne réponse)
  • Pour annoter les modèles avec des métadonnées
  • Pour créer des index en BDD
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.

  • Avec UploadFile comme paramètre de route (bonne réponse)
  • Avec File() dans le corps JSON
  • Avec request.files["file"]
  • FastAPI ne supporte pas l'upload de fichiers
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.

  • Retourne uniquement les valeurs des champs comme liste de dictionnaires (bonne réponse)
  • Retourne la liste de toutes les valeurs uniques
  • Convertit le QuerySet en liste Python
  • Retourne les métadonnées du modèle
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.

  • Django toujours pour les applications web, FastAPI uniquement pour les ML APIs
  • Django pour des applications full-stack complexes avec admin/auth ; FastAPI pour des microservices ou APIs haute performance (bonne réponse)
  • FastAPI est un superset de Django — toujours préférer FastAPI
  • Ils ne peuvent pas coexister dans le même projet
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.

  • Renommer manuellement tous les champs
  • model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) (bonne réponse)
  • Configurer CAMEL_CASE=True dans FastAPI
  • Utiliser le décorateur @camel_case sur le modèle
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'

  • MEDIA_ROOT et MEDIA_URL dans settings.py + urlpattern pour dev (bonne réponse)
  • STATIC_ROOT suffit pour tous les fichiers
  • Utiliser un CDN uniquement
  • Créer un dossier uploads/ dans l'app
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.

  • Article.objects.order_by("auteur->nom")
  • Article.objects.order_by("auteur__nom") (bonne réponse)
  • Article.objects.sort_by_relation("auteur.nom")
  • Article.objects.join("auteur").order_by("nom")
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'.

  • Ajouter tag="articles" dans chaque @app.get()
  • Utiliser tags=["articles"] dans le décorateur de route ou dans APIRouter (bonne réponse)
  • Configurer SWAGGER_TAGS dans les settings
  • Créer un fichier tags.py
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': '...'}]).

  • Récupère un objet ou retourne None s'il n'existe pas
  • Récupère un objet existant ou le crée s'il n'existe pas, retourne (objet, created) (bonne réponse)
  • Tente de récupérer et lève une exception si manquant
  • Crée toujours un nouvel objet
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().

  • Il n'y a pas de pattern standard, chaque développeur fait à sa manière
  • Définir models SQLAlchemy, schémas Pydantic, dépendance get_db(), et routes CRUD (bonne réponse)
  • Utiliser uniquement les fonctions built-in de FastAPI
  • FastAPI impose un pattern unique via son CLI
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.

  • class TestArticle(TestCase): def test_view(self): response = self.client.get('/articles/') (bonne réponse)
  • def test_articles(): assert requests.get('/articles/').status == 200
  • Django n'a pas de framework de test intégré
  • Utiliser pytest uniquement
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.

  • models.UrlField()
  • models.URLField() (bonne réponse)
  • models.LinkField()
  • models.StringField(is_url=True)
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.

  • pg.connect(DATABASE_URL) dans settings.py
  • DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": ..., "USER": ..., "PASSWORD": ..., "HOST": ..., "PORT": ...}} (bonne réponse)
  • DATABASE_URL = "postgresql://user:pass@host/db"
  • pip install django-postgres suffit
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.

  • email: str = Field(regex=r"[^@]+@[^@]+")
  • email: EmailStr (bonne réponse)
  • @validate_email\nemail: str
  • email: Email
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.

  • Article.objects.all().delete() → supprime tous les articles
  • Article.objects.filter(actif=False).delete()
  • Les deux réponses A et B sont correctes (bonne réponse)
  • delete(Article.objects.all())
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.

  • Un seul response_model est supporté par route
  • Utiliser responses={200: {"model": SuccessModel}, 404: {"model": ErrorModel}} (bonne réponse)
  • Définir plusieurs @app.get() pour la même route
  • Utiliser if/else dans la fonction de route
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.

  • path("articles/", views.liste, label="article-list")
  • path("articles/", views.liste, name="article-list") (bonne réponse)
  • url("articles/", views.liste, id="article-list")
  • path("articles/", views.liste, alias="article-list")
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.

  • Active la compatibilité avec l'ORM Django
  • Permet à Pydantic de lire les données depuis des objets ORM (attributes) au lieu de dict uniquement (bonne réponse)
  • Active le mode strict pour les types
  • Désactive la validation pour les modèles ORM
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.

  • Modifier manuellement settings.py avant chaque déploiement
  • Scinder settings.py en base + dev/staging/prod, utiliser DJANGO_SETTINGS_MODULE et variables d'environnement (bonne réponse)
  • Créer un switch if/else selon le hostname dans settings.py
  • Django ne supporte qu'un seul environnement à la fois
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.

  • models.CharField(max_length=200, indexed=True)
  • models.CharField(max_length=200, db_index=True) (bonne réponse)
  • Ajouter @index au-dessus du champ
  • CREATE INDEX ... dans une migration manuelle uniquement
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.