Django

Tutoriel 2 — Environnement “Geek” pour Django en ligne

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

AZ

Share
Published by
AZ

Recent Posts

Analyse de la demande du marché de la chaussure

La demande dans le secteur de la chaussure ne se résume jamais à un simple…

7 heures ago

Fiche technique d’une entreprise : méthode, structure + Exemple

Il suffit parfois de quelques lignes pour qu’une entreprise prenne forme aux yeux d’un interlocuteur.…

16 heures ago

Marketing Communication Quiz : un moyen simple et efficace de tester ses connaissances

Le Marketing Communication Quiz attire de plus en plus d’intérêt, et cela s’explique assez facilement.…

1 semaine ago

Fiche de traçabilité nettoyage : modèle Excel, utilité et bonnes pratiques

La fiche de traçabilité nettoyage est le doc indispensable dans toute organisation soucieuse d’ordre, d’hygiène…

3 semaines ago

Outil d’entraînement au texte argumentatif 1er Bac Maroc

Face à un sujet de production écrite, beaucoup d’élèves ressentent une hésitation immédiate. Le thème…

3 semaines ago

Réussir le texte argumentatif au 1er Bac

Le texte argumentatif impressionne souvent plus qu’il ne le devrait. Beaucoup d’élèves l’abordent avec une…

3 semaines ago

This website uses cookies.