Architecture et configuration d'un projet réel

Première leçon du cours Application complète avec Django, en accès libre : environ 18 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Découper un projet en applications cohérentes et définir un modèle utilisateur personnalisé dès le départ
  • Externaliser la configuration (secrets, DEBUG, hôtes, base de données) dans des variables d'environnement
  • Organiser gabarits, fichiers statiques et fichiers téléversés

Architecture d'un projetConfiguration par environnementModèle utilisateur personnalisé

Le projet fil rouge

Dans ce cours, on construit Atelier, une application de gestion de projets : chaque utilisateur crée des projets, y ajoute des tâches étiquetées, invite des membres, et consulte un tableau de bord. Ce sont les besoins d’un vrai SaaS : comptes, droits, données relationnelles, interface responsive, API JSON, tests et mise en production.

Découper en applications

atelier/
├── manage.py
├── config/                 # réglages, URL racine, wsgi/asgi
│   ├── settings.py
│   └── urls.py
├── comptes/                # modèle utilisateur, inscription, profil
├── projets/                # Projet, Tache, Etiquette, vues et API
├── templates/              # base.html et gabarits partagés
├── static/                 # CSS, JS, images du projet
├── .env.example            # variables attendues, SANS valeurs secrètes
└── requirements.txt

Une application = un domaine fonctionnel cohérent. Si une application devient trop grosse, on la scinde ; si deux applications dépendent sans cesse l’une de l’autre, on les fusionne.

Le modèle utilisateur personnalisé

La documentation de Django recommande de définir dès le début un modèle utilisateur personnalisé, même identique à celui par défaut : le changer après la première migration est très délicat.

# comptes/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models


class Utilisateur(AbstractUser):
    # champs ajoutés plus tard sans douleur : avatar, fuseau horaire...
    bio = models.TextField(blank=True)
# config/settings.py
AUTH_USER_MODEL = "comptes.Utilisateur"   # AVANT le premier migrate

Ailleurs, on ne référence jamais User directement : models.ForeignKey(settings.AUTH_USER_MODEL, ...) dans les modèles, get_user_model() dans le reste du code.

Les réglages viennent de l’environnement

Le même code tourne en développement, en test et en production : seules les variables d’environnement changent. Les secrets ne sont jamais commités.

# config/settings.py
import os
from pathlib import Path
from django.core.exceptions import ImproperlyConfigured

BASE_DIR = Path(__file__).resolve().parent.parent

DEBUG = os.environ.get("DJANGO_DEBUG", "False").lower() in {"1", "true", "yes", "on"}

SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
if not SECRET_KEY:
    if not DEBUG:
        raise ImproperlyConfigured("DJANGO_SECRET_KEY manquante")   # échouer tôt
    SECRET_KEY = "dev-seulement-pas-pour-la-production"

ALLOWED_HOSTS = [h for h in os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",") if h]
if DEBUG and not ALLOWED_HOSTS:
    ALLOWED_HOSTS = ["localhost", "127.0.0.1"]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ.get("POSTGRES_DB", "atelier"),
        "USER": os.environ.get("POSTGRES_USER", "atelier"),
        "PASSWORD": os.environ.get("POSTGRES_PASSWORD", ""),
        "HOST": os.environ.get("POSTGRES_HOST", "localhost"),
        "PORT": os.environ.get("POSTGRES_PORT", "5432"),
    }
}

DEBUG vaut False par défaut : oublier une variable ne doit jamais exposer les pages d’erreur détaillées. Des paquets comme django-environ ou dj-database-url simplifient la lecture (notamment d’une URL DATABASE_URL), mais le principe reste le même. En développement, un fichier .env non versionné peut être chargé par ton outil (par exemple python-dotenv) ; un .env.example versionné documente les variables attendues.

Gabarits, statiques et médias

TEMPLATES = [{
    "BACKEND": "django.template.backends.django.DjangoTemplates",
    "DIRS": [BASE_DIR / "templates"],          # gabarits partagés (base.html)
    "APP_DIRS": True,                          # + app/templates/ de chaque application
    "OPTIONS": {"context_processors": [
        "django.template.context_processors.request",
        "django.contrib.auth.context_processors.auth",
        "django.contrib.messages.context_processors.messages",
    ]},
}]

STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]       # sources
STATIC_ROOT = BASE_DIR / "staticfiles"         # destination de collectstatic (production)
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"                # fichiers téléversés par les utilisateurs

Statiques : fichiers du projet (CSS, JS), versionnés et collectés au déploiement. Médias : fichiers envoyés par les utilisateurs, jamais versionnés, stockés sur un disque persistant ou un service de stockage objet.

À retenir

  • Une application par domaine ; AUTH_USER_MODEL personnalisé avant la première migration.
  • Configuration par variables d’environnement, DEBUG=False par défaut, échec explicite si un secret manque.
  • Statiques ≠ médias.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. CodeDifficulté 2Python dans le navigateur2 indices

    Charger la configuration depuis l'environnement

    Écris charger_reglages(env) (env est un dict) qui renvoie {"DEBUG", "SECRET_KEY", "ALLOWED_HOSTS", "DATABASES"} :

    • DEBUG : vrai si DJANGO_DEBUG vaut 1, true, yes ou on (insensible à la casse), faux par défaut ;
    • SECRET_KEY : DJANGO_SECRET_KEY ; si absente, lève ImproperlyConfigured hors DEBUG, sinon utilise une valeur de développement ;
    • ALLOWED_HOSTS : DJANGO_ALLOWED_HOSTS séparé par des virgules (espaces retirés, éléments vides ignorés) ; si vide et DEBUG, ["localhost", "127.0.0.1"] ;
    • DATABASES["default"] : SQLite (ENGINE django.db.backends.sqlite3, NAME db.sqlite3) si DATABASE_URL est absente, sinon décompose une URL postgres://user:mdp@hote:port/base en ENGINE (django.db.backends.postgresql), NAME, USER, PASSWORD, HOST, PORT (chaîne, 5432 par défaut). Tout autre schéma lève ImproperlyConfigured.
    from urllib.parse import urlsplit
    
    
    class ImproperlyConfigured(Exception):
        pass
    
    
    def charger_reglages(env):
        ...
  2. QCMDifficulté 21 indice

    Modèle utilisateur

    À quel moment faut-il définir AUTH_USER_MODEL vers un modèle personnalisé ?

    • Quand on a besoin d'un nouveau champ
    • Avant la toute première migration du projet
    • Juste avant la mise en production
    • Jamais, il faut garder User
  3. TâcheDifficulté 22 indices

    Squelette du projet Atelier

    Dans ton environnement de travail : crée le projet config, les applications comptes et projets, le modèle Utilisateur(AbstractUser) et AUTH_USER_MODEL, puis lance la première migration. Configure settings.py pour lire DJANGO_DEBUG, DJANGO_SECRET_KEY et DJANGO_ALLOWED_HOSTS depuis l’environnement, ajoute un .env.example et un .gitignore (.env, db.sqlite3, media/, staticfiles/). Crée templates/base.html avec un conteneur responsive.

    • python manage.py migrate crée la table comptes_utilisateur
    • Lancer le serveur sans DJANGO_SECRET_KEY et sans DJANGO_DEBUG échoue avec ImproperlyConfigured
    • Aucun secret n'apparaît dans les fichiers versionnés
    • base.html est utilisé par une page d'accueil de l'application projets

Quiz de fin de leçon

  1. Comment référencer l’utilisateur dans une ForeignKey ?

    • models.ForeignKey(User, ...)
    • models.ForeignKey(settings.AUTH_USER_MODEL, ...)
    • models.ForeignKey("auth.User", ...)
  2. Quelle valeur par défaut donner à DEBUG ?

    • True
    • False
    • Aucune
  3. Où vont les fichiers téléversés par les utilisateurs ?

    • STATIC_ROOT
    • MEDIA_ROOT
    • Dans le dépôt Git

La suite du cours

  1. Authentification et autorisations20 min
  2. Relations et performances de l'ORM20 min
  3. Vues génériques, API JSON et interface responsive20 min
  4. Tests, sécurité et mise en production20 min

Application complète avec Django · Avancé · 5 h 38