Django

Tutoriel 2 — Environnement “Geek” pour Django en ligne

×

Recommandés

Le setup qui sent le terminal, les dotfiles bien rangés et la vélocité clavier.

Pitch

Objectif simple : ouvrir un IDE cloud et obtenir un poste de dev Django qui claque — shell nerveux, prompts qui parlent, linters qui mordent, hot-reload propre, logs lisibles, raccourcis partout. Zéro bricolage manuel. Tout se déclare via conteneur + dotfiles + scripts idempotents.

Un environnement Django en ligne peut être austère ou plaisant. Version “geek”, il devient rapide, verbeux quand il faut, silencieux quand il faut, et surtout prédictible. Copiez-collez ces fichiers, poussez dans votre repo, ouvrez le workspace : votre équipe a désormais un cockpit clavier-first pour shipper sans yak-shaving.


1) Base image & devcontainer : que tout le monde voie la même machine

Dockerfile (dev)

FROM mcr.microsoft.com/devcontainers/python:3.12

# Outils CLI "geek" (rapides & lisibles)
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
    git ripgrep fd-find fzf bat eza direnv tmux \
    && ln -s /usr/bin/fdfind /usr/local/bin/fd \
    && ln -s /usr/bin/batcat /usr/local/bin/bat \
    && rm -rf /var/lib/apt/lists/*

# Python tooling (ultra-rapide) : uv + ruff + mypy
RUN pipx install uv && pipx ensurepath
ENV PATH="/root/.local/bin:${PATH}"

# Node minimal (pour Pyright ou front léger)
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs

# Starship prompt
RUN curl -fsSL https://starship.rs/install.sh | sh -s -- -y

.devcontainer/devcontainer.json

{
  "name": "django-geek-dev",
  "build": { "dockerfile": "Dockerfile" },
  "features": {},
  "settings": {
    "terminal.integrated.defaultProfile.linux": "bash",
    "editor.rulers": [100],
    "python.analysis.typeCheckingMode": "basic",
    "files.trimTrailingWhitespace": true
  },
  "postCreateCommand": "just bootstrap",
  "forwardPorts": [8000],
  "remoteEnv": {
    "DJANGO_SETTINGS_MODULE": "config.settings.dev"
  }
}

2) Dotfiles & prompt qui parle

~/.bashrc (extrait)

eval "$(direnv hook bash)"
export EDITOR="nvim"
export PIP_DISABLE_PIP_VERSION_CHECK=1
export PYTHONDONTWRITEBYTECODE=1
eval "$(starship init bash)"
alias ll='eza -lh --git --icons --group-directories-first'
alias cat='bat --paging=never'
alias g='git'

~/.config/starship.toml (prompt minimal lisible)

add_newline = false
[directory] truncation_length = 3
[python] format = "via [ $virtualenv]($style) "
[git_branch] symbol = " "
[cmd_duration] min_time = 500

~/.tmux.conf (multiplexage sobre)

setw -g mode-keys vi
set -g mouse on
bind r source-file ~/.tmux.conf \; display "reloaded"

3) Arborescence Django “opinionated”

project/
├─ .devcontainer/
├─ .env.example
├─ justfile
├─ pyproject.toml
├─ manage.py
├─ src/
│  ├─ config/
│  │  ├─ asgi.py
│  │  ├─ wsgi.py
│  │  └─ settings/{base.py,dev.py,prod.py}
│  ├─ apps/core/
│  ├─ templates/
│  └─ static/
└─ tests/

4) Dépendances & qualité : uv + ruff + pytest

pyproject.toml (extrait)

[project]
name = "django-geek"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
  "django>=5.0",
  "dj-database-url>=2.2",
  "python-dotenv>=1.0",
  "django-extensions>=3.2",
]

[project.optional-dependencies]

dev = [« pytest », « pytest-django », « ruff », « black », « mypy », « ipython »]

[tool.pytest.ini_options]

DJANGO_SETTINGS_MODULE = « config.settings.dev » pythonpath = [« src »]

Installation express

uv pip install -e ".[dev]"

5) Settings 12-Factor : sobres mais affûtés

src/config/settings/base.py

from pathlib import Path
import os, dj_database_url

BASE_DIR = Path(__file__).resolve().parents[2]
ENV = os.getenv("ENV", "dev")

def env(key, default=None):
    v = os.getenv(key, default)
    return None if v is None else v

SECRET_KEY = env("DJANGO_SECRET_KEY", "change-me")
DEBUG = env("DJANGO_DEBUG", "0") == "1"
ALLOWED_HOSTS = env("DJANGO_ALLOWED_HOSTS", "").split(",") if env("DJANGO_ALLOWED_HOSTS") else []
CSRF_TRUSTED_ORIGINS = env("DJANGO_CSRF_TRUSTED_ORIGINS","").split(",") if env("DJANGO_CSRF_TRUSTED_ORIGINS") else []

LANGUAGE_CODE = env("DJANGO_LANGUAGE_CODE", "fr-fr")
TIME_ZONE = env("DJANGO_TIME_ZONE", "Europe/Paris")
USE_I18N = True; USE_TZ = True

INSTALLED_APPS = [
    "django.contrib.admin","django.contrib.auth","django.contrib.contenttypes",
    "django.contrib.sessions","django.contrib.messages","django.contrib.staticfiles",
    "django_extensions","apps.core",
]
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"

DATABASES = {"default": dj_database_url.parse(env("DATABASE_URL","sqlite:///db.sqlite3"))}

STATIC_URL = "/static/"; STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "/media/";   MEDIA_ROOT = BASE_DIR / "media"

LOGGING = {
  "version": 1, "disable_existing_loggers": False,
  "handlers": {"console": {"class": "logging.StreamHandler"}},
  "root": {"handlers": ["console"], "level": env("DJANGO_LOG_LEVEL","INFO")},
}

dev.py et prod.py

# dev.py
from .base import *
DEBUG = True
INTERNAL_IPS = ["127.0.0.1"]

# prod.py
from .base import *
SECURE_SSL_REDIRECT = os.getenv("DJANGO_SSL_REDIRECT","1") == "1"
SESSION_COOKIE_SECURE = True; CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = int(os.getenv("DJANGO_HSTS_SECONDS","0"))

6) Variables d’environnement : contrat d’infra

.env.example

DJANGO_SECRET_KEY=changeme
DJANGO_DEBUG=1
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost:8000
DJANGO_LOG_LEVEL=INFO
DJANGO_LANGUAGE_CODE=fr-fr
DJANGO_TIME_ZONE=Europe/Paris
DATABASE_URL=sqlite:///db.sqlite3
DJANGO_SSL_REDIRECT=0
DJANGO_HSTS_SECONDS=0

direnv pour l’auto-load
.envrc

dotenv
export DJANGO_SETTINGS_MODULE=config.settings.dev

Puis :

direnv allow

7) Task runner sans friction : just au lieu de retenir 12 commandes

justfile

set shell := ["bash", "-cu"]

bootstrap:
    uv pip install -e ".[dev]"
    pre-commit install || true

run:
    python manage.py migrate
    python manage.py runserver 0.0.0.0:8000

shell:
    python manage.py shell_plus

lint:
    ruff check .
    black --check .
    mypy src

fix:
    ruff check . --fix
    black .

test:
    pytest -q

collect:
    python manage.py collectstatic --noinput

Exécution :

just bootstrap
just run

8) Expérience dev “clavier only”

Recherche ultra-rapide : rg "pattern" -n src/
Fuzzy-open un fichier : fzf puis enter
Git lisible :

git config --global core.pager "delta"
git log --graph --oneline --decorate --all

Django shell++ : just shell (via django-extensions)
Hot-reload fiable : runserver 0.0.0.0:8000 + volumes montés (devcontainer)
Multiplexage : tmux new -s dj && tmux split-window -h && tmux split-window -v
Panneau gauche : serveur. Haut droit : tests. Bas droit : logs.


9) Observabilité locale propre

Astuce logging de dev : niveaux lisibles, timestamps courts.

# dans base.py, remplacez "handlers" par:
"handlers": {
  "console": {
    "class": "logging.StreamHandler",
    "formatter": "simple"
}},
"formatters": {
  "simple": { "format": "[%(levelname).1s %(asctime)s] %(name)s: %(message)s",
              "datefmt": "%H:%M:%S" }
}

Résultat : [I 14:22:10] django.server: "GET /admin/..." 200


10) DB & services “à la carte” en dev

docker-compose.yml (optionnel)

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: django
      POSTGRES_PASSWORD: django
      POSTGRES_DB: django
    ports: ["5432:5432"]
  redis:
    image: redis:7
    ports: ["6379:6379"]

Env : DATABASE_URL=postgres://django:django@localhost:5432/django
Bonus Celery/Beat en split-process via Procfile si besoin.


11) Qualité automatique

pre-commit (extrait .pre-commit-config.yaml)

repos:
- repo: https://github.com/psf/black
  rev: 24.8.0
  hooks: [{id: black}]
- repo: https://github.com/astral-sh/ruff-pre-commit
  rev: v0.6.9
  hooks: [{id: ruff}]
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v4.6.0
  hooks:
    - id: end-of-file-fixer
    - id: detect-private-key

Une fois installé : chaque commit est “peigné” automatiquement.


12) Routine quotidienne (ultra-courte)

Ouvrir le workspace → just run → coder → just test → commit/push.
Aucun copier-coller de commandes ésotériques, aucune divergence locale. L’IDE cloud, le conteneur et les dotfiles font le gros du travail.


13) Débogage “geek”

python manage.py runserver_plus (Werkzeug debugger — via django-extensions).
ipdb/breakpoint() pour pause granulaire.
pytest -k nom -q exécute un seul test, plus vite que tout.
• Profilage express :

from django_extensions.management.commands.runserver_plus import Command
# ou utilisez --print-sql / django-silk en local

De l’IDE cloud à la préprod/prod (Geek edition)

But. Transformer votre setup Django “en ligne” en un pipeline CI/CD prêt pour la préprod et la prod : build Docker multi-stage, statiques via WhiteNoise/CDN, médias sur stockage objet, migrations automatiques, workers Celery, healthchecks et monitoring.

1) Architecture d’exécution (vue rapide)

  • web : gunicorn config.wsgi:application
  • worker : celery -A config worker -l info
  • scheduler : celery -A config beat -l info
  • services : Postgres, Redis, stockage objet (S3-compatible)
  • statiques : WhiteNoise (et/ou CDN)
  • médias : bucket S3, jamais sur le conteneur
  • logs : stdout/stderr (niveau piloté par env)

2) Dockerfile prod (multi-stage, slim & rapide)

# syntax=docker/dockerfile:1
FROM python:3.12-slim AS build
WORKDIR /app
RUN apt-get update && apt-get install -y build-essential libpq-dev && rm -rf /var/lib/apt/lists/*
COPY pyproject.toml ./
RUN pip install --upgrade pip && pip install --no-cache-dir "pip-tools"
# Si vous utilisez requirements.txt: COPY requirements.txt . && pip install -r requirements.txt

FROM python:3.12-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
RUN useradd -m django && apt-get update && apt-get install -y libpq5 && rm -rf /var/lib/apt/lists/*
COPY --from=build /usr/local /usr/local
COPY ./src ./src
COPY manage.py gunicorn.conf.py ./
ENV DJANGO_SETTINGS_MODULE=config.settings.prod
USER django
CMD ["gunicorn","-c","gunicorn.conf.py","config.wsgi:application"]

gunicorn.conf.py (prod)

bind = "0.0.0.0:8000"
workers = 3
threads = 2
timeout = 60
graceful_timeout = 30
accesslog = "-"   # stdout
errorlog  = "-"   # stderr

3) Statiques et WhiteNoise

settings/prod.py (extraits)

from .base import *
INSTALLED_APPS = ["whitenoise.runserver_nostatic", *INSTALLED_APPS]
MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware")
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
SECURE_SSL_REDIRECT = os.getenv("DJANGO_SSL_REDIRECT","1") == "1"
SESSION_COOKIE_SECURE = True; CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = int(os.getenv("DJANGO_HSTS_SECONDS","31536000"))

Build/collectstatic (CI avant image finale)

python manage.py collectstatic --noinput

4) Médias sur S3 (django-storages)

# settings/prod.py (suite)
INSTALLED_APPS += ["storages"]
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME")
AWS_S3_REGION_NAME = os.getenv("AWS_S3_REGION_NAME", "eu-west-1")
AWS_S3_OBJECT_PARAMETERS = {"CacheControl": "max-age=31536000, public"}

Env requis

AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_STORAGE_BUCKET_NAME, AWS_S3_REGION_NAME

5) Healthchecks & readiness

Une vue “prête” pour l’orchestrateur :

# src/apps/core/views.py
from django.http import JsonResponse
def health(request): return JsonResponse({"status":"ok"})
def ready(request):  return JsonResponse({"db":"ok","cache":"ok"})

# src/config/urls.py
from django.urls import path
from apps.core.views import health, ready
urlpatterns = [path("health/", health), path("ready/", ready)]

6) Celery & Redis

src/config/celery.py

import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE","config.settings.prod")
app = Celery("config")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

Env (ex.)

CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/1

7) GitHub Actions — pipeline CI/CD minimal

.github/workflows/ci.yml

name: ci
on:
  push: { branches: [main] }
  pull_request: {}
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: '3.12' }
      - name: Install deps
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Lint & Tests
        run: |
          ruff check .
          black --check .
          pytest -q

.github/workflows/cd.yml (build & push image + déploiement via SSH)

name: cd
on:
  push: { branches: [main] }
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    permissions: { contents: read, packages: write }
    steps:
      - uses: actions/checkout@v4
      - name: Login GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build & Push
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
      - name: Deploy (SSH)
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            docker pull ghcr.io/${{ github.repository }}:latest
            docker compose -f /srv/app/compose.prod.yml up -d --force-recreate
            docker compose -f /srv/app/compose.prod.yml exec -T web python manage.py migrate

8) compose.prod.yml (web + worker + beat)

services:
  web:
    image: ghcr.io/owner/repo:latest
    env_file: /srv/app/.env
    ports: ["80:8000"]
    depends_on: [db, redis]
    restart: always
  worker:
    image: ghcr.io/owner/repo:latest
    command: celery -A config worker -l info
    env_file: /srv/app/.env
    depends_on: [redis]
    restart: always
  beat:
    image: ghcr.io/owner/repo:latest
    command: celery -A config beat -l info
    env_file: /srv/app/.env
    depends_on: [redis]
    restart: always
  db:
    image: postgres:16
    volumes: ["pgdata:/var/lib/postgresql/data"]
    environment:
      POSTGRES_DB: django
      POSTGRES_USER: django
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
  redis:
    image: redis:7
volumes:
  pgdata:

9) Secrets & variables (checklist)

  • Django : DJANGO_SECRET_KEY, DJANGO_ALLOWED_HOSTS, DJANGO_CSRF_TRUSTED_ORIGINS, DJANGO_LOG_LEVEL
  • DB/Cache : DATABASE_URL, CELERY_BROKER_URL, CELERY_RESULT_BACKEND
  • Sécurité : DJANGO_SSL_REDIRECT=1, DJANGO_HSTS_SECONDS=31536000
  • I18N/TZ : DJANGO_LANGUAGE_CODE, DJANGO_TIME_ZONE
  • Stockage : clés S3 si médias externes

10) Monitoring minimal (Sentry)

# settings/prod.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
SENTRY_DSN = os.getenv("SENTRY_DSN")
if SENTRY_DSN:
    sentry_sdk.init(dsn=SENTRY_DSN, integrations=[DjangoIntegration()], traces_sample_rate=0.2)

11) Runbook de déploiement (récap)

  1. CI : lint + tests → OK
  2. Build image → push registry
  3. CD : pull sur serveur, compose up -d
  4. Migrations : manage.py migrate (étape dédiée)
  5. Health : /health/ et /ready/ → 200
  6. Roll-back : repasser au tag précédent, relancer compose

12) Panneaux rouges (troubleshooting)

  • 403 CSRF : URL prod manquante dans CSRF_TRUSTED_ORIGINS
  • DisallowedHost : host non listé dans ALLOWED_HOSTS
  • Statiques 404 : collectstatic oublié ou mauvais STATICFILES_STORAGE
  • Uploads perdus : pas de bucket S3 → activer DEFAULT_FILE_STORAGE
  • Celery muet : CELERY_BROKER_URL incorrect / Redis non joignable
  • Timeout : ajuster workers/threads/timeout de Gunicorn ou index DB

Recommandés

Django en ligne : poser un environnement...
Chapo. Fini les “ça marche chez...
En savoir plus
Courrier de relance pour facture impayée :...
Une facture impayée se gère rarement...
En savoir plus
Fiche technique d’un studio d’enregistrement : ...
Un studio d’enregistrement fonctionne comme une...
En savoir plus
Procédure interne Word et PDF : un cadre clair pour travailler juste, partout, tout le temps
Procédure interne Word et PDF : un...
Une procédure interne ne se résume...
En savoir plus
Formulaire de prêt de matériel en entreprise dans Excel + Inventaire - Mouvements - Dossiers
Formulaire de prêt de matériel en entreprise...
Télécharger un modèle de formulaire...
En savoir plus
Échéanciers clients – Création & export Excel...
Une partie de la vie d’une...
En savoir plus

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

error: Content is protected !!