LSSD UI
v1.0.0

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
└── .dockerignore

Comment ça s’articule :

  • Le Dockerfile té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 écrit static/css/app.css.
  • Vos pages chargent uniquement app.css. Rien n’est chargé depuis ui.lssd.fr dans 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 dossier vendor n’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 application apps/), ajoutez une ligne @source par 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&amp;f[]=cabinet-grotesk@500,700,800&amp;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, que theme.js ajuste.
  • 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 :

ÉtapeRôle
tailwindTélécharge l’outil Tailwind et vérifie son empreinte.
lssd-uiTé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.
basePython et les paquets de requirements.txt.
devL’image de développement : base plus Tailwind et LSSD UI.
cssCompile app.css à partir des gabarits, pour la production.
prodL’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-web

7.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-ink sur le <h1> de home.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 :

  1. Copiez .env.example en .env et remplissez-le : le domaine, et une clé secrète générée avec docker run --rm python:3.13-slim python -c "import secrets; print(secrets.token_urlsafe(50))".
  2. Construisez et lancez la production seule (sans compose.override.yaml) : docker compose -f compose.yaml up -d --build.
  3. 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

  1. Sur la page Versions, lisez les notes de la nouvelle version et copiez son numéro et son empreinte.
  2. Dans le Dockerfile, remplacez LSSD_UI_VERSION et LSSD_UI_SHA256.
  3. Relancez le développement avec docker compose up --build --renew-anon-volumes (--renew-anon-volumes remplace l’ancienne copie de LSSD UI par la nouvelle).
  4. Vérifiez vos pages dans les deux thèmes, puis déployez.

En cas de problème

SymptômeCause 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 constructionL’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 allocatedUn 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.