Guide · Django
LSSD UI dans un projet Django
En suivant ces étapes dans l’ordre, vous obtenez un projet Django qui tourne dans Docker, dont les pages utilisent LSSD UI, avec un CSS recompilé automatiquement pendant que vous travaillez. Comptez une demi-heure. Vous n’installez rien d’autre que Docker.
Ce que vous allez construire
À la fin du guide, votre dossier ressemble à ceci :
mon-outil/
├── config/ ← réglages Django (créé à l’étape 1)
│ ├── settings.py
│ └── urls.py
├── templates/
│ ├── base.html ← squelette commun à toutes les pages
│ └── home.html ← la page d’accueil
├── static/
│ ├── css/
│ │ ├── input.css ← entrée Tailwind (vous l’écrivez)
│ │ └── app.css ← CSS généré par Tailwind (jamais modifié à la main)
│ └── js/
│ └── theme.js ← thème clair ou sombre
├── manage.py
├── requirements.txt
├── Dockerfile
├── compose.yaml ← production
├── compose.override.yaml ← développement
├── .env.example
├── .gitignore
└── .dockerignoreComment ça s’articule :
- Le
Dockerfiletélécharge l’outil Tailwind et une version précise de LSSD UI, dont il vérifie l’empreinte. - Tailwind lit
static/css/input.css, qui importe LSSD UI, parcourt vos gabarits et écritstatic/css/app.css. - Vos pages chargent uniquement
app.css. Rien n’est chargé depuisui.lssd.frdans le navigateur.
Étape 1 — Créer le projet Django
Créez un dossier vide pour votre outil, ouvrez un terminal dedans, puis lancez cette commande. Elle démarre un conteneur Python jetable, y installe Django et crée le projet dans votre dossier :
docker run --rm -v "${PWD}:/app" -w /app python:3.13-slim sh -c "pip install --quiet django && django-admin startproject config ."
Vérification : le dossier contient maintenant manage.py et un dossier
config/. Le point final de la commande est important : il crée le projet dans le dossier
courant au lieu d’un sous-dossier.
Sous Linux, les fichiers créés appartiennent à root. Ajoutez
--user "$(id -u):$(id -g)" juste après docker run pour qu’ils soient à vous.
Étape 2 — Déclarer les dépendances
Créez requirements.txt à la racine. Il liste ce que l’image Docker installe :
Django>=5.2,<5.3
gunicorn>=23,<24
whitenoise>=6,<7- Django : la version 5.2, maintenue à long terme.
- Gunicorn : le serveur web de production.
- WhiteNoise : sert les fichiers statiques (dont
app.css) en production.
Étape 3 — Régler Django
Ouvrez config/settings.py et faites ces changements, dans l’ordre du fichier.
3.1 Lire les secrets dans l’environnement
Tout en haut, ajoutez import os au-dessus de from pathlib import Path. Remplacez
ensuite les lignes SECRET_KEY, DEBUG et ALLOWED_HOSTS par :
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG") == "1"
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",")Les valeurs viendront de Docker Compose (étape 7) : le mode debug n’est activé qu’en développement, et la clé secrète n’est jamais écrite dans le code.
3.2 Ajouter WhiteNoise
Dans MIDDLEWARE, ajoutez la ligne WhiteNoise juste sous SecurityMiddleware :
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# … the other lines stay as they are
]3.3 Indiquer le dossier des gabarits
Dans TEMPLATES, remplacez 'DIRS': [], par :
'DIRS': [BASE_DIR / 'templates'],3.4 Régler les fichiers statiques
Tout en bas, sous STATIC_URL = 'static/', ajoutez :
STATICFILES_DIRS = [BASE_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'staticfiles'
STORAGES = {
'default': {'BACKEND': 'django.core.files.storage.FileSystemStorage'},
'staticfiles': {'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage'},
}
En production, WhiteNoise ajoute une empreinte au nom de chaque fichier (app.4f2c….css) : le
navigateur télécharge le nouveau CSS dès qu’il change.
3.5 Afficher une page d’accueil
Remplacez le contenu de config/urls.py (sous le commentaire du début) par :
from django.contrib import admin
from django.urls import path
from django.views.generic import TemplateView
urlpatterns = [
path('', TemplateView.as_view(template_name='home.html')),
path('admin/', admin.site.urls),
]Étape 4 — Créer l’entrée Tailwind
Créez les dossiers static/css et static/js, puis le fichier static/css/input.css :
/* Tailwind entry: Tailwind first, then LSSD UI, then where the classes are used. */
@import "tailwindcss";
@import "./vendor/lssd-ui/index.css";
@source "../../templates";@import "tailwindcss"charge Tailwind.@import "./vendor/lssd-ui/index.css"charge LSSD UI. Le dossiervendorn’existe pas sur votre disque : c’est l’image Docker qui l’apporte (étape 6). C’est normal.@source "../../templates"dit à Tailwind où chercher les classes. Si vous écrivez des classes Tailwind ailleurs (par exemple dans une applicationapps/), ajoutez une ligne@sourcepar dossier.
Étape 5 — Créer les gabarits
5.1 Le script du thème
Créez static/js/theme.js. Il choisit le thème clair ou sombre avant l’affichage de la page,
pour éviter un flash de la mauvaise couleur :
// Sets data-theme before the first paint: the stored choice, otherwise the system setting.
(function () {
var stored = null;
try { stored = localStorage.getItem("lssd-theme"); } catch (error) {}
var dark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.setAttribute("data-theme", stored || (dark ? "dark" : "light"));
})();5.2 Le squelette commun
Créez le dossier templates, puis templates/base.html :
{% load static %}<!DOCTYPE html>
<html lang="fr" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}LSSD Exemple{% endblock %}</title>
<link rel="preconnect" href="https://api.fontshare.com">
<link rel="preconnect" href="https://cdn.fontshare.com" crossorigin>
<link rel="stylesheet" href="https://api.fontshare.com/v2/css?f[]=general-sans@400,500,600,700&f[]=cabinet-grotesk@500,700,800&display=swap">
<link rel="stylesheet" href="{% static 'css/app.css' %}">
<script src="{% static 'js/theme.js' %}"></script>
</head>
<body class="bg-bg text-ink font-sans antialiased">
<header class="lssd-header">
<a class="lssd-header__brand" href="/">
<span class="lssd-header__name">LSSD</span>
<span class="lssd-header__product">Exemple</span>
</a>
<div class="lssd-header__end">
<a class="lssd-btn lssd-btn--primary lssd-btn--sm" href="/">Ajouter</a>
</div>
</header>
<main class="mx-auto w-full max-w-content px-4 sm:px-6 py-8">
{% block content %}{% endblock %}
</main>
</body>
</html>data-theme="light"sur<html>: le thème par défaut, quetheme.jsajuste.- Les trois lignes Fontshare chargent les polices General Sans et Cabinet Grotesk.
{% static 'css/app.css' %}: le CSS généré par Tailwind.lssd-header: la barre du haut commune à tous les outils LSSD. Remplacez « Exemple » par le nom de votre outil.
5.3 La page d’accueil
Créez templates/home.html :
{% extends "base.html" %}
{% block content %}
<h1 class="title-1">Bonjour</h1>
<article class="lssd-card mt-6">
<p class="lssd-card__overline">LSSD UI</p>
<h2 class="lssd-card__title">Ça marche</h2>
<p class="lssd-card__body">Cette page utilise les classes de LSSD UI.</p>
<div class="lssd-card__actions">
<button type="button" class="lssd-btn lssd-btn--primary">Enregistrer</button>
</div>
</article>
{% endblock %}Étape 6 — Écrire le Dockerfile
Créez Dockerfile (sans extension) à la racine. Vous n’avez rien à y modifier, sauf la version de
LSSD UI quand vous voudrez la mettre à jour.
# syntax=docker/dockerfile:1
# --- tailwind: the standalone Tailwind CSS CLI (no Node), checksum verified ---
FROM python:3.13-slim AS tailwind
ARG TAILWIND_VERSION=v4.3.3
ARG TARGETARCH
RUN set -eu; \
case "${TARGETARCH:-amd64}" in \
amd64) asset=tailwindcss-linux-x64 ;; \
arm64) asset=tailwindcss-linux-arm64 ;; \
*) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \
esac; \
base="https://github.com/tailwindlabs/tailwindcss/releases/download/${TAILWIND_VERSION}"; \
cd /tmp; \
python -c "import sys, urllib.request; [urllib.request.urlretrieve(f'{sys.argv[1]}/{n}', n) for n in sys.argv[2:]]" \
"$base" "$asset" sha256sums.txt; \
grep " \./${asset}\$\| ${asset}\$" sha256sums.txt | sed "s# \./# #" | sha256sum -c -; \
install -m 0755 "$asset" /usr/local/bin/tailwindcss; \
rm -f "$asset" sha256sums.txt
# --- lssd-ui: the pinned LSSD UI library, checksum verified ---
FROM python:3.13-slim AS lssd-ui
ARG LSSD_UI_VERSION=1.0.0
ARG LSSD_UI_SHA256=b34c40a92021933668b2da0fa9d52ab2b738d0a257890a8bc893260c3110c388
ADD --checksum=sha256:${LSSD_UI_SHA256} \
https://ui.lssd.fr/releases/v${LSSD_UI_VERSION}/lssd-ui-${LSSD_UI_VERSION}.tar.gz /tmp/lssd-ui.tar.gz
RUN mkdir /out && tar -xzf /tmp/lssd-ui.tar.gz -C /out
# --- base: Python and the project dependencies ---
FROM python:3.13-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --root-user-action=ignore -r requirements.txt
# --- dev: adds the Tailwind CLI and LSSD UI; the code is mounted by compose.override.yaml ---
FROM base AS dev
COPY --from=tailwind /usr/local/bin/tailwindcss /usr/local/bin/tailwindcss
COPY --from=lssd-ui /out/lssd-ui/ /app/static/css/vendor/lssd-ui/
# --- css: compiles the stylesheet from the templates ---
FROM tailwind AS css
WORKDIR /app
COPY static/css/input.css static/css/input.css
COPY --from=lssd-ui /out/lssd-ui/ static/css/vendor/lssd-ui/
COPY templates/ templates/
RUN tailwindcss -i static/css/input.css -o static/css/app.css --minify
# --- prod: code, compiled CSS and collected static files, run as a non-root user ---
FROM base AS prod
COPY . .
COPY --from=css /app/static/css/app.css static/css/app.css
RUN rm -f static/css/input.css \
&& DJANGO_SECRET_KEY=build-only python manage.py collectstatic --noinput \
&& useradd --system --uid 1000 app
USER app
EXPOSE 8000
CMD ["gunicorn", "config.wsgi", "--bind", "0.0.0.0:8000", "--workers", "3"]Le fichier est découpé en étapes, chacune avec un rôle :
| Étape | Rôle |
|---|---|
tailwind | Télécharge l’outil Tailwind et vérifie son empreinte. |
lssd-ui | Télécharge la version choisie de LSSD UI et vérifie son empreinte SHA-256. Si elle ne correspond pas, la construction s’arrête. |
base | Python et les paquets de requirements.txt. |
dev | L’image de développement : base plus Tailwind et LSSD UI. |
css | Compile app.css à partir des gabarits, pour la production. |
prod | L’image de production : le code, le CSS compilé et les fichiers statiques, sous un utilisateur sans droits d’administration. |
LSSD_UI_VERSION et LSSD_UI_SHA256 sont la version de LSSD UI et son empreinte. Ces
deux valeurs se trouvent sur la page Versions.
Étape 7 — Écrire les fichiers Docker Compose
7.1 La production : compose.yaml
Il décrit le conteneur de production, joint par le Traefik partagé des serveurs LSSD. Remplacez
exemple par le nom de votre outil dans les quatre lignes traefik.http.… : ce nom
doit être unique sur le serveur.
services:
web:
build: { context: ., target: prod }
restart: unless-stopped
env_file: .env
# No published port: only the shared Traefik reaches the site.
networks: [web]
labels:
- traefik.enable=true
- traefik.docker.network=shared-web
- traefik.http.routers.exemple.rule=Host(`${APP_DOMAIN:?Set APP_DOMAIN in .env}`)
- traefik.http.routers.exemple.entrypoints=websecure
- traefik.http.routers.exemple.tls.certresolver=${TRAEFIK_CERTRESOLVER:-myresolver}
- traefik.http.services.exemple.loadbalancer.server.port=8000
networks:
web:
external: true
name: shared-web7.2 Le développement : compose.override.yaml
Docker Compose le lit automatiquement sur votre ordinateur. Il lance le serveur de développement de Django
et un second conteneur, tailwind, qui recompile le CSS à chaque modification d’un gabarit.
# Development only: loaded automatically by `docker compose up`.
services:
web:
build: { context: ., target: dev }
command: python manage.py runserver 0.0.0.0:8000
environment:
DJANGO_DEBUG: "1"
DJANGO_SECRET_KEY: dev-only-not-secret
DJANGO_ALLOWED_HOSTS: localhost,127.0.0.1
ports: ["127.0.0.1:8000:8000"]
volumes: [".:/app"]
# The shared Traefik network does not exist on a development machine.
networks: !override [default]
labels: !reset []
# Recompiles static/css/app.css whenever a template changes.
tailwind:
build: { context: ., target: dev }
command: tailwindcss -i static/css/input.css -o static/css/app.css --watch=always --poll
volumes:
- .:/app
# Keeps the LSSD UI copy of the image visible under the mounted code.
- /app/static/css/vendor
La ligne - /app/static/css/vendor est importante : votre dossier est monté par-dessus celui de
l’image, ce qui cacherait la copie de LSSD UI. Cette ligne la garde visible.
7.3 Les variables : .env.example et .env
Créez .env.example, qui documente les variables :
# Copy this file to .env and fill in the values. Never commit .env.
APP_DOMAIN=exemple.lssd.fr
TRAEFIK_CERTRESOLVER=myresolver
DJANGO_SECRET_KEY=change-me
DJANGO_ALLOWED_HOSTS=exemple.lssd.fr
Copiez-le en .env (Compose en a besoin, même en développement). Sur le serveur, vous y mettrez
les vraies valeurs ; .env n’est jamais commité.
Étape 8 — Ignorer les fichiers générés
.gitignore : ce que Git ne doit pas enregistrer.
.env
__pycache__/
staticfiles/
static/css/app.css
static/css/vendor/.dockerignore : ce que Docker ne doit pas copier dans l’image.
.git
.env
__pycache__
staticfiles
static/css/app.css
static/css/vendorÉtape 9 — Lancer en développement
docker compose up --build
La première fois, Docker télécharge et construit tout : comptez quelques minutes. Les journaux des deux
conteneurs défilent dans le terminal. Quand vous voyez Starting development server at
http://0.0.0.0:8000/ et, côté tailwind, Done in …ms, ouvrez
http://localhost:8000.
Vérification :
- La barre du haut « LSSD Exemple » et une carte « Ça marche » avec un bouton vert en pilule s’affichent.
- Ajoutez
text-accent-inksur le<h1>dehome.html, enregistrez, puis rechargez la page : le titre devient vert. Tailwind a recompilé le CSS tout seul. - Passez votre système en thème sombre et rechargez : la page passe en sombre.
Pour arrêter : Ctrl+C dans le terminal. Pour lancer en arrière-plan :
docker compose up -d, puis docker compose logs -f pour suivre les journaux et
docker compose down pour arrêter. Une commande Django se lance dans le conteneur, par exemple
docker compose exec web python manage.py migrate.
Étape 10 — Utiliser les composants dans un formulaire
Les champs de formulaire Django reçoivent la classe lssd-input par leur widget. Exemple dans un
fichier forms.py :
from django import forms
class ExpenseForm(forms.Form):
amount = forms.DecimalField(
label="Montant",
widget=forms.NumberInput(attrs={"class": "lssd-input"}),
)Et dans le gabarit, chaque champ est entouré de lssd-field, avec son libellé et son erreur :
<form method="post" class="flex flex-col gap-4">
{% csrf_token %}
<div class="lssd-field">
<label class="lssd-label" for="{{ form.amount.id_for_label }}">{{ form.amount.label }}</label>
{{ form.amount }}
{% if form.amount.errors %}
<p class="lssd-error">Erreur : {{ form.amount.errors.0 }}</p>
{% endif %}
</div>
<div>
<button type="submit" class="lssd-btn lssd-btn--primary">Enregistrer</button>
</div>
</form>Tous les composants (boutons, cases à cocher, badges, cartes) et leurs règles sont sur la page Composants.
Étape 11 — Mettre en production
Sur le serveur, dans le dossier du projet :
- Copiez
.env.exampleen.envet remplissez-le : le domaine, et une clé secrète générée avecdocker run --rm python:3.13-slim python -c "import secrets; print(secrets.token_urlsafe(50))". - Construisez et lancez la production seule (sans
compose.override.yaml) :docker compose -f compose.yaml up -d --build. - Le Traefik partagé publie le site en HTTPS sur le domaine de
APP_DOMAIN.
Pour tester l’image de production sur votre ordinateur avant de déployer :
docker compose -f compose.yaml build. Si la construction réussit, le CSS de production a été
compilé.
Mettre à jour LSSD UI
- Sur la page Versions, lisez les notes de la nouvelle version et copiez son numéro et son empreinte.
- Dans le
Dockerfile, remplacezLSSD_UI_VERSIONetLSSD_UI_SHA256. - Relancez le développement avec
docker compose up --build --renew-anon-volumes(--renew-anon-volumesremplace l’ancienne copie de LSSD UI par la nouvelle). - Vérifiez vos pages dans les deux thèmes, puis déployez.
En cas de problème
| Symptôme | Cause et solution |
|---|---|
| La page s’affiche sans aucun style. | Le CSS n’a pas été compilé. Regardez docker compose logs tailwind : une erreur y est affichée. Vérifiez que static/css/app.css existe. |
Can't resolve './vendor/lssd-ui/index.css' | La copie de LSSD UI n’est pas visible dans le conteneur. Vérifiez la ligne - /app/static/css/vendor de compose.override.yaml, puis lancez docker compose up --build --renew-anon-volumes. |
digest mismatch pendant la construction | L’empreinte ne correspond pas à la version. Recopiez les deux valeurs depuis la page Versions. |
| Une classe Tailwind n’a aucun effet. | Soit le fichier qui l’utilise n’est pas dans un dossier @source d’input.css, soit la classe n’existe pas dans LSSD UI : bg-blue-500 ou text-gray-700 sont retirées volontairement. Utilisez les couleurs de la page Fondations. |
port is already allocated | Un autre programme utilise le port 8000. Dans compose.override.yaml, remplacez 127.0.0.1:8000:8000 par 127.0.0.1:8010:8000 et ouvrez http://localhost:8010. |
KeyError: 'DJANGO_SECRET_KEY' | La variable manque : en production, vérifiez le fichier .env. |
DisallowedHost ou « Bad Request (400) » | Le domaine n’est pas dans DJANGO_ALLOWED_HOSTS. Ajoutez-le dans .env, séparé par des virgules. |