Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

📄 Django & PDF – GĂ©nĂ©ration, Manipulation & Tools

Guide opérationnel : WeasyPrint, ReportLab, Templates Django et manipulation de fichiers.

1.1 Facile

Concept : HTML vs Canvas

Deux écoles : Dessiner pixels (ReportLab) ou convertir du HTML/CSS (WeasyPrint).

Théorie Choix Techno
1.2 Facile

WeasyPrint : Le Standard

Le moteur HTML/CSS vers PDF moderne et puissant. Installation et fonctionnement.

WeasyPrint HTML2PDF
1.3 Moyen

Dépendances SystÚme

Installation des libs C requises (Pango, Cairo, GDK-Pixbuf) sur Linux/Mac.

apt install Libraries C
1.4 Moyen

ReportLab : Le "Bas Niveau"

Génération programmatique haute performance. Difficile mais trÚs rapide.

ReportLab Canvas
2.1 Moyen

Django + WeasyPrint

Transformer un Template Django en PDF. render_to_string.

Views Templates
2.2 Moyen

CSS pour l'Impression

RÚgles @page, numéros de page, sauts de page (break-inside).

CSS3 @page
2.3 Facile

Servir le PDF (FileResponse)

Afficher dans le navigateur ou forcer le téléchargement.

FileResponse Content-Type
2.4 Facile

Comparatif Librairies

WeasyPrint vs ReportLab vs xhtml2pdf vs wkhtmltopdf.

Benchmark Features
3.1 Moyen

Manipulation (pypdf)

Fusionner (Merge), Diviser (Split) et tourner des PDFs existants.

pypdf Merger
3.2 Avancé

Génération Asynchrone (Celery)

Ne jamais gĂ©nĂ©rer de gros PDF dans la requĂȘte HTTP. Utiliser des Workers.

Celery Performance
3.3 Moyen

Gestion Fichiers Statiques

ProblĂšmes d'images et CSS (base_url, staticfiles) dans les PDFs.

Static Images
4.1 Facile

Cheat-sheet PDF

Snippets rapides pour Views, CSS et pypdf.

cheat Snippets
1.1 Concept : HTML vs Canvas
1. Approche "Template" (WeasyPrint, xhtml2pdf)

Principe : Vous réutilisez vos connaissances Web. Vous écrivez du HTML/CSS (comme une page web), et un moteur de rendu le convertit en PDF.

  • Avantages : Rapide Ă  dĂ©velopper, facile Ă  maintenir, rĂ©utilisation des templates Django.
  • InconvĂ©nients : Plus lent en CPU/RAM, mise en page "pixel perfect" parfois complexe.
  • Leader : WeasyPrint.
2. Approche "Programmation" (ReportLab)

Principe : Vous écrivez du code Python pour dire "Dessine un rectangle en X=100, Y=200". C'est comme dessiner sur un Canvas.

  • Avantages : ExtrĂȘmement rapide (gĂ©nĂ©ration de masse), contrĂŽle total au millimĂštre prĂšs, pas de moteur de rendu lourd.
  • InconvĂ©nients : TrĂšs verbeux, difficile Ă  maintenir, nĂ©cessite d'apprendre une API complexe.
  • Leader : ReportLab.
1.2 WeasyPrint : Vue d'ensemble
Pourquoi WeasyPrint ?

C'est la solution recommandée pour 90% des projets Django. Elle supporte le CSS moderne (Flexbox, Grid partiel, @page) et produit des PDFs propres.

Installation Python
# Dans votre venv
pip install WeasyPrint
Exemple Minimal (Python)
from weasyprint import HTML

# Depuis une chaĂźne
HTML(string='<h1>Hello</h1>').write_pdf("hello.pdf")

# Depuis une URL
HTML('https://google.com').write_pdf("google.pdf")

WeasyPrint nécessite des librairies systÚme (GTK, Pango) pour fonctionner (voir carte 1.3).

1.3 Dépendances SystÚme (WeasyPrint)
Ubuntu / Debian

Ces librairies sont nécessaires pour le rendu des polices et des graphiques.

sudo apt-get install build-essential python3-dev python3-pip \
    python3-setuptools python3-wheel python3-cffi libcairo2 \
    libpango-1.0-0 libpangoft2-1.0-0 libpangocairo-1.0-0 \
    libgdk-pixbuf2.0-0 libffi-dev shared-mime-info
Fedora / RHEL / CentOS
sudo dnf install redhat-rpm-config python3-devel \
    gcc libffi-devel python3-cffi cairo pango gdk-pixbuf2
MacOS (avec Homebrew)
brew install pango libffi cairo gdk-pixbuf
Dockerfile (Base Python Slim)
FROM python:3.11-slim

# Install WeasyPrint system dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    libpango-1.0-0 \
    libpangoft2-1.0-0 \
    libpangocairo-1.0-0 \
    libgdk-pixbuf2.0-0 \
    libffi-dev \
    libcairo2 \
    && rm -rf /var/lib/apt/lists/*

RUN pip install WeasyPrint django
1.4 ReportLab : Génération Programmatique
Installation
pip install reportlab
Concept : Canvas

L'objet `Canvas` est une feuille blanche. On utilise des coordonnées (X, Y) partant du coin bas-gauche (0,0).

from reportlab.pdfgen import canvas

def create_pdf(filename):
    c = canvas.Canvas(filename)
    # Dessiner un texte (x=100, y=750)
    c.drawString(100, 750, "Hello World from ReportLab")
    # Sauvegarder
    c.save()
Platypus (Haut Niveau)

ReportLab offre une couche "haute" appelée Platypus qui gÚre les paragraphes et les tableaux automatiquement (flowables).

from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet

doc = SimpleDocTemplate("doc.pdf")
styles = getSampleStyleSheet()
story = []

story.append(Paragraph("Ceci est un paragraphe long qui va se wrapper...", styles['Normal']))
doc.build(story)
2.1 Intégration Django + WeasyPrint
views.py

On utilise render_to_string pour générer le HTML, puis on le passe à WeasyPrint.

from django.http import HttpResponse
from django.template.loader import render_to_string
from weasyprint import HTML, CSS

def download_invoice(request, invoice_id):
    # 1. Récupérer les données
    invoice = Invoice.objects.get(pk=invoice_id)
    
    # 2. Rendre le template HTML en string
    html_string = render_to_string('pdf/invoice.html', {
        'invoice': invoice
    })
    
    # 3. Générer le PDF
    # (base_url est important pour les images statiques)
    html = HTML(string=html_string, base_url=request.build_absolute_uri())
    pdf_file = html.write_pdf()
    
    # 4. Retourner la réponse HTTP
    response = HttpResponse(pdf_file, content_type='application/pdf')
    response['Content-Disposition'] = f'filename="facture_{invoice_id}.pdf"'
    return response
templates/pdf/invoice.html
<!DOCTYPE html>
<html>
<head>
    <style>
        @page {
            size: A4;
            margin: 2cm;
            @bottom-right {
                content: "Page " counter(page) " / " counter(pages);
            }
        }
        body { font-family: sans-serif; }
        .header { color: #234A26; border-bottom: 2px solid #333; }
        table { width: 100%; border-collapse: collapse; }
        th, td { border: 1px solid #ccc; padding: 8px; }
    </style>
</head>
<body>
    <div class="header">
        <h1>Facture #</h1>
    </div>
    <p>Client : </p>
    
    <table>
        <thead><tr><th>Item</th><th>Prix</th></tr></thead>
        <tbody>
            
        </tbody>
    </table>
</body>
</html>
2.2 CSS pour l'Impression (Paged Media)
La rĂšgle @page

Définit les marges et la taille du papier.

@page {
    size: A4; /* ou Letter, A3 */
    margin: 2.5cm;
    margin-top: 3cm; /* Espace pour le header */
    
    /* Zones de page (Headers/Footers) */
    @top-center {
        content: "Mon Entreprise SAS";
        font-weight: bold;
    }
    @bottom-right {
        /* Compteurs automatiques */
        content: "Page " counter(page) " sur " counter(pages);
        font-size: 10px;
    }
}
ContrĂŽle des sauts de page

Crucial pour éviter qu'un tableau ou une image soit coupé en deux.

/* EmpĂȘcher de couper un Ă©lĂ©ment */
.no-break {
    page-break-inside: avoid;
    break-inside: avoid;
}

/* Forcer un saut de page avant */
.new-page {
    page-break-before: always;
    break-before: always;
}

/* Tables : répéter le header sur chaque page */
thead {
    display: table-header-group;
}
tr {
    page-break-inside: avoid;
}
2.3 Servir le fichier : FileResponse

Utilisez FileResponse (plus adapté que HttpResponse pour les fichiers binaires).

Option 1 : Afficher dans le navigateur (Preview)
from django.http import FileResponse
import io

# buffer = io.BytesIO(pdf_content)

return FileResponse(buffer, as_attachment=False, filename='rapport.pdf')
Option 2 : Forcer le téléchargement
return FileResponse(buffer, as_attachment=True, filename='rapport.pdf')
Note sur les Buffers

Si vous générez le PDF en RAM (sans le sauvegarder sur disque), utilisez io.BytesIO.

import io
buffer = io.BytesIO()
html.write_pdf(target=buffer) # Ecrit dans la RAM
buffer.seek(0) # Revenir au début du fichier
return FileResponse(buffer, ...)
2.4 Comparatif des Librairies
LibrairieMéthodeCSS SupportPerformanceUtilisation recommandée
WeasyPrintHTML/CSSExcellent (CSS3, Flexbox)MoyenFactures, Rapports designés, Documents standards.
ReportLabCode PythonN/A (Canvas)TrÚs HauteGénération de masse (milliers de docs), Relevés bancaires.
xhtml2pdfHTML/CSSFaible (CSS 2.1)MoyenVieux projets. Déprécié au profit de WeasyPrint.
wkhtmltopdfMoteur WebkitBonBonNécessite binaire externe. Projet abandonné/archivé.
Conclusion

Utilisez WeasyPrint par dĂ©faut. Si vous avez des contraintes de performance extrĂȘmes (1000 PDFs / seconde), passez Ă  ReportLab.

3.1 Manipulation de PDF (pypdf)

Pour fusionner, découper ou tourner des PDFs existants. (Anciennement PyPDF2, maintenant pypdf).

pip install pypdf
Fusionner (Merge)
from pypdf import PdfWriter

merger = PdfWriter()

# Ajouter des fichiers
merger.append("intro.pdf")
merger.append("chapitre1.pdf")
merger.append("fin.pdf")

# Sauvegarder le résultat
merger.write("document_complet.pdf")
merger.close()
Lire et Extraire
from pypdf import PdfReader, PdfWriter

reader = PdfReader("source.pdf")
writer = PdfWriter()

# Extraire la page 1 seulement
page = reader.pages[0]
writer.add_page(page)

# Sauvegarder
with open("page1.pdf", "wb") as fp:
    writer.write(fp)
3.2 Génération Asynchrone (Celery)
Le ProblĂšme

Générer un PDF prend du temps (0.5s à 10s). Si vous le faites dans la vue Django, vous bloquez le serveur (Timeout Gateway 504).

La Solution : Celery

On délÚgue la tùche à un worker.

# tasks.py
from celery import shared_task
from weasyprint import HTML
from django.core.files.base import ContentFile
from .models import Rapport

@shared_task
def generate_rapport_pdf_task(rapport_id):
    rapport = Rapport.objects.get(id=rapport_id)
    html_string = render_to_string('rapport.html', {'r': rapport})
    
    # Génération binaire
    pdf_bytes = HTML(string=html_string).write_pdf()
    
    # Sauvegarde dans un champ FileField
    rapport.pdf_file.save(f'rapport_{rapport_id}.pdf', ContentFile(pdf_bytes))
    rapport.save()

L'utilisateur reçoit une rĂ©ponse immĂ©diate "GĂ©nĂ©ration en cours...", puis tĂ©lĂ©charge le fichier une fois prĂȘt (polling ou email).

3.3 Gestion des Fichiers Statiques
ProblĂšme : Images Introuvables

WeasyPrint ne devine pas oĂč sont vos fichiers CSS/Images locaux si vous utilisez des URLs relatives (ex: /static/logo.png) sans contexte.

Solution 1 : base_url
# Dans la vue
HTML(string=html, base_url=request.build_absolute_uri())
Solution 2 : django-weasyprint

Un wrapper pratique qui gĂšre les staticfiles finder automatiquement.

Solution 3 : Chemins absolus (Fichier systĂšme)

Dans le template, pointer directement vers le disque (utile en dev/prod mixte).

4.1 Cheat-sheet PDF
WeasyPrint Django View
import io
from django.http import FileResponse
from django.template.loader import render_to_string
from weasyprint import HTML

def pdf_view(request):
    # 1. Template -> HTML
    html_txt = render_to_string("doc.html", {})
    
    # 2. HTML -> PDF (Buffer)
    buf = io.BytesIO()
    HTML(string=html_txt, 
         base_url=request.build_absolute_uri()
    ).write_pdf(buf)
    buf.seek(0)
    
    # 3. Response
    return FileResponse(buf, filename="doc.pdf")
CSS Print Snippets
/* Format A4 */
@page { size: A4; margin: 2cm; }

/* Saut de page forcé */
.break { page-break-before: always; }

/* Ne pas couper au milieu */
.nobreak { page-break-inside: avoid; }

/* Numérotation */
@page {
  @bottom-center {
    content: counter(page);
  }
}
Pypdf Merge (One-linerish)
from pypdf import PdfWriter
merger = PdfWriter()
[merger.append(f) for f in ["a.pdf", "b.pdf"]]
merger.write("result.pdf")
merger.close()