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_MODELpersonnalisé avant la première migration. - Configuration par variables d’environnement,
DEBUG=Falsepar 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.
Charger la configuration depuis l'environnement
Écris
charger_reglages(env)(env est un dict) qui renvoie{"DEBUG", "SECRET_KEY", "ALLOWED_HOSTS", "DATABASES"}:DEBUG: vrai siDJANGO_DEBUGvaut1,true,yesouon(insensible à la casse), faux par défaut ;SECRET_KEY:DJANGO_SECRET_KEY; si absente, lèveImproperlyConfiguredhors DEBUG, sinon utilise une valeur de développement ;ALLOWED_HOSTS:DJANGO_ALLOWED_HOSTSséparé par des virgules (espaces retirés, éléments vides ignorés) ; si vide et DEBUG,["localhost", "127.0.0.1"];DATABASES["default"]: SQLite (ENGINEdjango.db.backends.sqlite3,NAMEdb.sqlite3) siDATABASE_URLest absente, sinon décompose une URLpostgres://user:mdp@hote:port/baseenENGINE(django.db.backends.postgresql),NAME,USER,PASSWORD,HOST,PORT(chaîne, 5432 par défaut). Tout autre schéma lèveImproperlyConfigured.
from urllib.parse import urlsplit class ImproperlyConfigured(Exception): pass def charger_reglages(env): ...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
Squelette du projet Atelier
Dans ton environnement de travail : crée le projet
config, les applicationscomptesetprojets, le modèleUtilisateur(AbstractUser)etAUTH_USER_MODEL, puis lance la première migration. Configuresettings.pypour lireDJANGO_DEBUG,DJANGO_SECRET_KEYetDJANGO_ALLOWED_HOSTSdepuis l’environnement, ajoute un.env.exampleet un.gitignore(.env,db.sqlite3,media/,staticfiles/). Créetemplates/base.htmlavec 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
Comment référencer l’utilisateur dans une ForeignKey ?
- models.ForeignKey(User, ...)
- models.ForeignKey(settings.AUTH_USER_MODEL, ...)
- models.ForeignKey("auth.User", ...)
Quelle valeur par défaut donner à DEBUG ?
- True
- False
- Aucune
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
- Authentification et autorisations20 min
- Relations et performances de l'ORM20 min
- Vues génériques, API JSON et interface responsive20 min
- Tests, sécurité et mise en production20 min