# Fichier: python_cheats/cheatsheets/jinja2.txt
# Cheatsheet Jinja2 - Guide Complet du Débutant à l'Expert


[OK] INTRODUCTION JINJA2

# Qu'est-ce que Jinja2?
# Moteur de templates moderne et puissant pour Python
# Créé par Armin Ronacher (créateur de Flask)
# Utilisé dans Flask, Ansible, Salt, et bien d'autres

# Installation
pip install jinja2

# Version
pip show jinja2

# Import de base
from jinja2 import Template
from jinja2 import Environment, FileSystemLoader


[OK] BASES - NIVEAU DÉBUTANT


# === PREMIER TEMPLATE ===

from jinja2 import Template

# Template simple
template = Template("Hello {{ name }}!")
result = template.render(name="World")
# Output: "Hello World!"

# Template avec plusieurs variables
template = Template("{{ greeting }}, {{ name }}!")
result = template.render(greeting="Bonjour", name="Alice")
# Output: "Bonjour, Alice!"


# === SYNTAXE DE BASE ===

# {{ ... }}    - Expressions (affichage de variables)
# {% ... %}    - Statements (logique: if, for, etc.)
# {# ... #}    - Commentaires (non rendus)
# #  ...       - Commentaires ligne (avec extension)

template = Template("""
{{ variable }}           {# Affiche variable #}
{% if condition %}...{% endif %}
{# Ceci est un commentaire #}
""")


# === VARIABLES ===

# Variables simples
template = Template("{{ name }}")
template.render(name="Bob")                    # "Bob"

# Accès attributs
template = Template("{{ user.name }}")
template.render(user={'name': 'Alice'})        # "Alice"

# Accès items (dict)
template = Template("{{ user['email'] }}")
template.render(user={'email': 'a@b.com'})     # "a@b.com"

# Accès index (list)
template = Template("{{ items[0] }}")
template.render(items=['a', 'b', 'c'])         # "a"

# Appel de méthode
template = Template("{{ name.upper() }}")
template.render(name="hello")                  # "HELLO"

# Variables avec défaut
template = Template("{{ name | default('Guest') }}")
template.render()                              # "Guest"


# === FILTRES DE BASE ===

# upper / lower
{{ name | upper }}          # ALICE
{{ name | lower }}          # alice

# capitalize / title
{{ name | capitalize }}     # Alice
{{ text | title }}          # Hello World

# length
{{ items | length }}        # Nombre d'éléments

# default
{{ var | default('N/A') }}  # Valeur par défaut si None/undefined

# trim
{{ "  hello  " | trim }}    # "hello"


# === STRUCTURES DE CONTRÔLE ===

# if / elif / else
{% if user %}
    Bonjour {{ user.name }}
{% elif guest %}
    Bonjour invité
{% else %}
    Qui êtes-vous?
{% endif %}

# for loop
{% for item in items %}
    {{ item }}
{% endfor %}

# for avec index
{% for item in items %}
    {{ loop.index }}: {{ item }}
{% endfor %}

# for avec else (si liste vide)
{% for user in users %}
    - {{ user.name }}
{% else %}
    Aucun utilisateur
{% endfor %}


[OK] NIVEAU INTERMÉDIAIRE


# === FILTRES AVANCÉS ===

# join - Concaténer liste
{{ items | join(', ') }}                    # "a, b, c"
{{ items | join(' | ') }}                   # "a | b | c"

# replace
{{ text | replace('old', 'new') }}

# truncate
{{ text | truncate(20) }}                   # Tronque à 20 chars
{{ text | truncate(20, True) }}             # Avec "..." à la fin

# wordcount
{{ text | wordcount }}                      # Nombre de mots

# first / last
{{ items | first }}                         # Premier élément
{{ items | last }}                          # Dernier élément

# random
{{ items | random }}                        # Élément aléatoire

# sort
{{ items | sort }}                          # Tri ascendant
{{ items | sort(reverse=True) }}            # Tri descendant

# unique
{{ items | unique }}                        # Éléments uniques

# reverse
{{ items | reverse }}                       # Inverse l'ordre

# slice
{{ items | slice(3) }}                      # Divise en 3 groupes

# map
{{ items | map('upper') }}                  # Applique upper à chaque item
{{ users | map(attribute='name') }}         # Extrait attribut name

# select / reject
{{ items | select('odd') }}                 # Filtre nombres impairs
{{ items | reject('even') }}                # Rejette nombres pairs

# sum
{{ numbers | sum }}                         # Somme
{{ items | sum(attribute='price') }}        # Somme attribut

# abs / round
{{ -5 | abs }}                              # 5
{{ 3.14159 | round(2) }}                    # 3.14

# int / float / string
{{ "42" | int }}                            # Conversion en int
{{ 42 | float }}                            # Conversion en float
{{ 42 | string }}                           # Conversion en string

# safe - Marque HTML comme sûr (pas d'échappement)
{{ html_content | safe }}

# escape - Échappe HTML
{{ user_input | escape }}                   # Alias: e

# urlencode
{{ url | urlencode }}

# filesizeformat
{{ size_bytes | filesizeformat }}           # "1.5 MB"

# format
{{ "Hello %s" | format(name) }}

# indent
{{ text | indent(4) }}                      # Indente chaque ligne

# center
{{ text | center(80) }}                     # Centre sur 80 chars

# wordwrap
{{ text | wordwrap(40) }}                   # Coupe à 40 chars


# === TESTS ===

# Utilisation avec if
{% if variable is defined %}...{% endif %}
{% if variable is undefined %}...{% endif %}
{% if variable is none %}...{% endif %}

# Tests de type
{% if var is number %}...{% endif %}
{% if var is string %}...{% endif %}
{% if var is sequence %}...{% endif %}
{% if var is mapping %}...{% endif %}

# Tests de contenu
{% if list is iterable %}...{% endif %}
{% if var is callable %}...{% endif %}

# Tests numériques
{% if number is even %}...{% endif %}
{% if number is odd %}...{% endif %}
{% if number is divisibleby(3) %}...{% endif %}

# Tests de chaîne
{% if text is lower %}...{% endif %}
{% if text is upper %}...{% endif %}

# Tests booléens
{% if var is sameas(True) %}...{% endif %}
{% if var is sameas(False) %}...{% endif %}

# Tests de comparaison
{% if x is equalto(y) %}...{% endif %}
{% if list is sameas(other_list) %}...{% endif %}


# === BOUCLES AVANCÉES ===

# Variable loop (dans for)
{% for item in items %}
    {{ loop.index }}        # Index (commence à 1)
    {{ loop.index0 }}       # Index (commence à 0)
    {{ loop.revindex }}     # Index inversé
    {{ loop.revindex0 }}    # Index inversé (commence à 0)
    {{ loop.first }}        # True si premier
    {{ loop.last }}         # True si dernier
    {{ loop.length }}       # Nombre total d'items
    {{ loop.cycle('odd', 'even') }}  # Alterne entre valeurs
    {{ loop.depth }}        # Profondeur loop imbriqué
    {{ loop.depth0 }}       # Profondeur (commence à 0)
{% endfor %}

# Filtrer dans for
{% for user in users if user.active %}
    {{ user.name }}
{% endfor %}

# Boucles imbriquées
{% for category in categories %}
    <h2>{{ category.name }}</h2>
    {% for product in category.products %}
        {{ loop.index }}.{{ loop.parent.loop.index }}: {{ product.name }}
    {% endfor %}
{% endfor %}

# break / continue
{% for item in items %}
    {% if item == 'stop' %}
        {% break %}
    {% endif %}
    {% if item == 'skip' %}
        {% continue %}
    {% endif %}
    {{ item }}
{% endfor %}

# Boucle récursive
{% for item in items recursive %}
    {{ item.name }}
    {% if item.children %}
        <ul>{{ loop(item.children) }}</ul>
    {% endif %}
{% endfor %}


# === ASSIGNATIONS ===

# set - Créer/modifier variable
{% set name = 'Alice' %}
{% set x, y, z = range(3) %}

# set avec block
{% set navigation %}
    <nav>...</nav>
{% endset %}

# Variables namespace (mutables)
{% set ns = namespace(counter=0) %}
{% for item in items %}
    {% set ns.counter = ns.counter + 1 %}
{% endfor %}
Total: {{ ns.counter }}


# === MACROS (FONCTIONS) ===

# Définir macro
{% macro input(name, value='', type='text') %}
    <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}

# Utiliser macro
{{ input('username') }}
{{ input('password', type='password') }}
{{ input('email', 'user@example.com', 'email') }}

# Macro avec varargs
{% macro list_users(title, *users) %}
    <h3>{{ title }}</h3>
    {% for user in users %}
        <li>{{ user }}</li>
    {% endfor %}
{% endmacro %}

{{ list_users('Admins', 'Alice', 'Bob', 'Charlie') }}

# Macro avec kwargs
{% macro link(text, href, **attrs) %}
    <a href="{{ href }}" {% for key, value in attrs.items() %}{{ key }}="{{ value }}" {% endfor %}>
        {{ text }}
    </a>
{% endmacro %}

{{ link('Google', 'https://google.com', target='_blank', class='external') }}

# Appeler macro depuis autre template
{% from 'forms.html' import input, textarea %}


# === HÉRITAGE DE TEMPLATES ===

# base.html (template parent)
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Mon Site{% endblock %}</title>
    {% block head %}{% endblock %}
</head>
<body>
    <header>
        {% block header %}
            <h1>En-tête par défaut</h1>
        {% endblock %}
    </header>
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        {% block footer %}
            <p>&copy; 2025</p>
        {% endblock %}
    </footer>
</body>
</html>

# page.html (template enfant)
{% extends "base.html" %}

{% block title %}Ma Page - {{ super() }}{% endblock %}

{% block head %}
    <link rel="stylesheet" href="style.css">
{% endblock %}

{% block content %}
    <h2>Contenu de ma page</h2>
    <p>...</p>
{% endblock %}

# super() - Appelle le contenu du block parent
{% block content %}
    {{ super() }}
    <p>Contenu additionnel</p>
{% endblock %}


# === INCLUSION DE TEMPLATES ===

# include - Inclure autre template
{% include 'header.html' %}
{% include 'sidebar.html' %}

# include avec variables
{% include 'item.html' with context %}
{% include 'item.html' without context %}

# include avec variables spécifiques
{% include 'user.html' with {'user': admin_user} %}

# include conditionnel
{% include 'debug.html' ignore missing %}


# === IMPORT ===

# Import macros
{% import 'forms.html' as forms %}
{{ forms.input('username') }}
{{ forms.textarea('bio') }}

# Import spécifique
{% from 'forms.html' import input, textarea %}
{{ input('email') }}

# Import avec alias
{% from 'forms.html' import input as text_input %}


[OK] NIVEAU AVANCÉ


# === ENVIRONMENT ET CONFIGURATION ===

from jinja2 import Environment, FileSystemLoader, select_autoescape

# Créer environnement personnalisé
env = Environment(
    loader=FileSystemLoader('templates'),
    autoescape=select_autoescape(['html', 'xml']),
    trim_blocks=True,
    lstrip_blocks=True,
    line_statement_prefix='#',
    line_comment_prefix='##',
    keep_trailing_newline=True,
    optimized=True,
    undefined=StrictUndefined,
    extensions=['jinja2.ext.debug']
)

# Options importantes:
# - autoescape: Auto-échappe HTML (sécurité)
# - trim_blocks: Retire premier newline après block tag
# - lstrip_blocks: Retire espaces avant block tag
# - line_statement_prefix: Permet statements sur ligne (ex: # for)
# - line_comment_prefix: Permet commentaires ligne (ex: ## comment)
# - keep_trailing_newline: Garde newline final
# - undefined: Comportement variables non définies


# === LOADERS (CHARGEURS) ===

from jinja2 import (
    FileSystemLoader,
    PackageLoader,
    DictLoader,
    FunctionLoader,
    PrefixLoader,
    ChoiceLoader,
    ModuleLoader
)

# FileSystemLoader - Charge depuis système de fichiers
loader = FileSystemLoader('templates')
loader = FileSystemLoader(['templates', 'other_templates'])
loader = FileSystemLoader('templates', encoding='utf-8')

# PackageLoader - Charge depuis package Python
loader = PackageLoader('myapp', 'templates')

# DictLoader - Charge depuis dictionnaire
loader = DictLoader({
    'index.html': '<h1>{{ title }}</h1>',
    'about.html': '<p>{{ content }}</p>'
})

# FunctionLoader - Charge avec fonction personnalisée
def load_template(name):
    # Logique personnalisée
    return template_source

loader = FunctionLoader(load_template)

# PrefixLoader - Charge avec préfixes
loader = PrefixLoader({
    'app1': FileSystemLoader('app1/templates'),
    'app2': FileSystemLoader('app2/templates')
})
# Utilisation: env.get_template('app1/index.html')

# ChoiceLoader - Essaie plusieurs loaders
loader = ChoiceLoader([
    FileSystemLoader('custom_templates'),
    FileSystemLoader('default_templates')
])

# ModuleLoader - Charge templates compilés
from jinja2.loaders import split_template_path
loader = ModuleLoader('/path/to/compiled/templates')


# === UNDEFINED BEHAVIORS ===

from jinja2 import (
    Undefined,           # Défaut: silencieux, retourne ''
    DebugUndefined,      # Debug: retourne nom variable
    StrictUndefined,     # Strict: lève exception
    ChainableUndefined   # Chainable: permet chaining
)

# Undefined (défaut)
env = Environment(undefined=Undefined)
# {{ missing_var }} -> '' (chaîne vide)

# DebugUndefined
env = Environment(undefined=DebugUndefined)
# {{ missing_var }} -> '{{ missing_var }}'

# StrictUndefined
env = Environment(undefined=StrictUndefined)
# {{ missing_var }} -> UndefinedError exception

# ChainableUndefined
env = Environment(undefined=ChainableUndefined)
# {{ missing.var.attr }} -> '' (pas d'erreur sur chaining)


# === FILTRES PERSONNALISÉS ===

from jinja2 import Environment

env = Environment()

# Filtre simple
def reverse_string(s):
    return s[::-1]

env.filters['reverse'] = reverse_string
# Usage: {{ "hello" | reverse }}

# Filtre avec arguments
def repeat(s, times=2):
    return s * times

env.filters['repeat'] = repeat
# Usage: {{ "ab" | repeat(3) }}

# Filtre avec décorateur
@env.filter
def shout(text):
    return text.upper() + '!!!'

# Usage: {{ "hello" | shout }}

# Filtre avec contexte
def current_user_filter(context):
    return context.get('user', 'Anonymous')

env.filters['current_user'] = current_user_filter

# Filtre avec environnement
def datetimeformat(value, format='%Y-%m-%d'):
    return value.strftime(format)

env.filters['datetimeformat'] = datetimeformat
# Usage: {{ date | datetimeformat('%d/%m/%Y') }}

# Exemples de filtres utiles
def currency(value, symbol='€'):
    return f"{symbol}{value:,.2f}"

def slugify(text):
    import re
    text = text.lower()
    return re.sub(r'[\s]+', '-', text)

def excerpt(text, length=100):
    if len(text) <= length:
        return text
    return text[:length].rsplit(' ', 1)[0] + '...'

env.filters['currency'] = currency
env.filters['slugify'] = slugify
env.filters['excerpt'] = excerpt


# === TESTS PERSONNALISÉS ===

# Test simple
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

env.tests['prime'] = is_prime
# Usage: {% if number is prime %}

# Test avec arguments
def divisible_by(n, divisor):
    return n % divisor == 0

env.tests['divisibleby'] = divisible_by
# Usage: {% if number is divisibleby(3) %}

# Tests utiles
def is_email(value):
    import re
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return re.match(pattern, value) is not None

def contains(sequence, item):
    return item in sequence

env.tests['email'] = is_email
env.tests['contains'] = contains


# === GLOBALS (VARIABLES GLOBALES) ===

# Ajouter variable globale
env.globals['site_name'] = 'Mon Site'
env.globals['version'] = '1.0.0'

# Ajouter fonction globale
def url_for(endpoint, **kwargs):
    # Logique de génération d'URL
    return f"/{endpoint}"

env.globals['url_for'] = url_for
# Usage: {{ url_for('home') }}

# Fonctions utiles
import datetime

env.globals['now'] = datetime.datetime.now
env.globals['date'] = datetime.date
# Usage: {{ now() }}


# === EXTENSIONS ===

from jinja2 import Environment

# Extensions built-in
env = Environment(extensions=[
    'jinja2.ext.do',              # {% do ... %}
    'jinja2.ext.loopcontrols',    # {% break %}, {% continue %}
    'jinja2.ext.debug',           # {% debug %}
    'jinja2.ext.i18n'             # Internationalisation
])

# Extension do - Exécuter code sans output
{% do navigation.append('Home') %}
{% do users.pop() %}

# Extension debug - Afficher contexte
{% debug %}

# Extension i18n - Traduction
{% trans %}Hello{% endtrans %}
{% trans name=user.name %}Hello {{ name }}{% endtrans %}

# Pluralisation
{% trans count=items|length %}
    One item
{% pluralize %}
    {{ count }} items
{% endtrans %}


# === EXTENSION PERSONNALISÉE ===

from jinja2 import nodes
from jinja2.ext import Extension

class UpperExtension(Extension):
    tags = {'upper'}
    
    def parse(self, parser):
        lineno = next(parser.stream).lineno
        body = parser.parse_statements(['name:endupper'], drop_needle=True)
        return nodes.CallBlock(
            self.call_method('_upper', []),
            [], [], body
        ).set_lineno(lineno)
    
    def _upper(self, caller):
        return caller().upper()

# Utilisation
env = Environment(extensions=[UpperExtension])

# Dans template:
{% upper %}
    hello world
{% endupper %}
# Output: HELLO WORLD


# === AUTO-ESCAPE PERSONNALISÉ ===

from jinja2 import select_autoescape

# Auto-escape intelligent
env = Environment(
    autoescape=select_autoescape(
        enabled_extensions=['html', 'xml'],
        default_for_string=True,
        default=False
    )
)

# Fonction personnalisée
def custom_autoescape(template_name):
    if template_name is None:
        return False
    return template_name.endswith(('.html', '.xml', '.jinja2'))

env = Environment(autoescape=custom_autoescape)


# === CONTEXTE ET PROCESSEURS ===

# Context processor - Ajoute variables à tous templates
def inject_defaults():
    return {
        'site_name': 'Mon Site',
        'year': datetime.date.today().year
    }

# Dans Flask
@app.context_processor
def utility_processor():
    return dict(format_price=lambda x: f"${x:.2f}")

# Avec environnement Jinja2 pur
template = env.get_template('index.html')
context = {**inject_defaults(), **custom_vars}
output = template.render(context)


# === POLITIQUE D'ÉCHAPPEMENT ===

from markupsafe import Markup, escape

# Marquer chaîne comme sûre
safe_html = Markup('<strong>Bold</strong>')

# Dans template
{% autoescape true %}
    {{ user_input }}           # Échappé
    {{ safe_html }}            # Pas échappé (Markup)
    {{ user_input | safe }}    # Pas échappé
{% endautoescape %}

{% autoescape false %}
    {{ user_input }}           # Pas échappé
    {{ user_input | escape }}  # Échappé explicitement
{% endautoescape %}


# === MISE EN CACHE ===

from jinja2 import Environment, FileSystemLoader

# Cache bytecode compilé
env = Environment(
    loader=FileSystemLoader('templates'),
    bytecode_cache=FileSystemBytecodeCache('/tmp/jinja2_cache')
)

# Cache en mémoire
from jinja2 import MemcachedBytecodeCache
cache = MemcachedBytecodeCache(client)
env = Environment(bytecode_cache=cache)

# Désactiver cache
env = Environment(cache_size=0)


# === STREAMING ===

# Générer template par morceaux (grandes données)
template = env.get_template('large.html')

# Stream au lieu de render
for chunk in template.generate(data):
    print(chunk, end='')

# Ou avec stream
stream = template.stream(data)
stream.dump('output.html')


# === ASYNC SUPPORT (Jinja2 3.0+) ===

from jinja2 import Environment, select_autoescape

env = Environment(
    enable_async=True,
    autoescape=select_autoescape()
)

# Template async
async def render_async():
    template = env.get_template('async_template.html')
    result = await template.render_async(data)
    return result

# Avec async for
{% for item in async_items %}
    {{ item }}
{% endfor %}


[OK] NIVEAU EXPERT


# === OPTIMISATIONS AVANCÉES ===

# Précompilation templates
from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader('templates'))

# Compiler template
code = env.compile_expression('items | length > 5')
result = code(items=[1, 2, 3, 4, 5, 6])  # True

# Compiler template complet
source = env.loader.get_source(env, 'template.html')[0]
code = env.compile(source)

# Sauvegarder bytecode
import marshal
with open('compiled.pyc', 'wb') as f:
    marshal.dump(code, f)


# === SANDBOX (SÉCURITÉ) ===

from jinja2.sandbox import SandboxedEnvironment

# Environnement sécurisé
env = SandboxedEnvironment()

# Restreindre accès
class MySandbox(SandboxedEnvironment):
    def is_safe_attribute(self, obj, attr, value):
        # Bloquer attributs privés
        if attr.startswith('_'):
            return False
        # Bloquer méthodes dangereuses
        if attr in ('system', 'exec', 'eval'):
            return False
        return True
    
    def is_safe_callable(self, obj):
        # Autoriser seulement certaines fonctions
        return obj in (len, str, int, float)

env = MySandbox()

# Protection contre:
# - Accès fichiers système
# - Exécution code arbitraire
# - Accès attributs dangereux


# === META-PROGRAMMING ===

from jinja2 import meta

# Analyser template sans le rendre
env = Environment()
source = "{{ user.name }} {% for item in items %}{{ item }}{% endfor %}"
ast = env.parse(source)

# Trouver variables non définies
variables = meta.find_undeclared_variables(ast)
# {'user', 'items'}

# Extraire tous les includes/extends
referenced = meta.find_referenced_templates(ast)


# === AST MANIPULATION ===

from jinja2 import nodes

# Créer nœuds AST
name_node = nodes.Name('variable', 'load')
const_node = nodes.Const('value')
output_node = nodes.Output([name_node])

# Modifier AST avant compilation
def preprocess(source):
    ast = env.parse(source)
    # Modifier ast...
    return ast


# === NATIVE TEMPLATES ===

from jinja2.nativetypes import NativeEnvironment

# Retourne types Python natifs au lieu de strings
env = NativeEnvironment()

template = env.from_string("{{ items }}")
result = template.render(items=[1, 2, 3])
# result est [1, 2, 3], pas "[1, 2, 3]"

template = env.from_string("{{ {'key': 'value'} }}")
result = template.render()
# result est {'key': 'value'}, pas "{'key': 'value'}"


# === POLITIQUES PERSONNALISÉES ===

from jinja2 import select_autoescape, pass_context

class CustomPolicy:
    def __init__(self, env):
        self.env = env
    
    def select_autoescape(self, template_name):
        # Logique personnalisée
        return template_name.endswith('.html')
    
    def compile_expression(self, source):
        # Prétraiter expressions
        return self.env.compile_expression(source)


# === INTROSPECTION RUNTIME ===

# Accéder au contexte template
{% set ctx = context %}
{{ ctx.keys() }}

# Accéder à l'environnement
{% set env = environment %}
{{ env.filters.keys() }}

# Informations loop
{% for item in items %}
    {% if loop.first %}
        Début boucle
    {% endif %}
    
    {% if loop.changed(item.category) %}
        Nouvelle catégorie: {{ item.category }}
    {% endif %}
{% endfor %}


# === TEMPLATE INLINE ===

from jinja2 import Template, Environment

# Méthode 1: Template direct
template_str = """
<div>
    {% for item in items %}
        <span>{{ item }}</span>
    {% endfor %}
</div>
"""
template = Template(template_str)

# Méthode 2: From string
env = Environment()
template = env.from_string(template_str)


# === FINALIZE CALLBACK ===

# Fonction appelée sur toutes les variables avant output
def finalize(value):
    if value is None:
        return ''
    if isinstance(value, str):
        return value.strip()
    return value

env = Environment(finalize=finalize)

# Maintenant toutes les valeurs sont processed
# {{ None }} -> ''
# {{ "  text  " }} -> 'text'


# === WHITESPACE CONTROL ===

# Contrôle précis espaces blancs

# Retirer whitespace avant tag
{%- if true %}
    content
{% endif %}

# Retirer whitespace après tag
{% if true -%}
    content
{% endif %}

# Retirer des deux côtés
{%- if true -%}
    content
{%- endif -%}

# Pareil pour variables
{{- variable -}}

# Exemple pratique
<ul>
    {%- for item in items %}
    <li>{{ item }}</li>
    {%- endfor %}
</ul>


[OK] PATTERNS ET BEST PRACTICES


# === STRUCTURE PROJET ===

myproject/
├── app.py
├── templates/
│   ├── base.html              # Template parent
│   ├── layouts/
│   │   ├── main.html
│   │   └── admin.html
│   ├── partials/
│   │   ├── header.html
│   │   ├── footer.html
│   │   └── sidebar.html
│   ├── components/
│   │   ├── button.html
│   │   ├── card.html
│   │   └── form_field.html
│   ├── pages/
│   │   ├── home.html
│   │   ├── about.html
│   │   └── contact.html
│   └── macros/
│       ├── forms.html
│       └── utils.html
└── static/


# === COMPOSANTS RÉUTILISABLES ===

# components/card.html
{% macro card(title, content, footer='') %}
<div class="card">
    <div class="card-header">
        <h3>{{ title }}</h3>
    </div>
    <div class="card-body">
        {{ content }}
    </div>
    {% if footer %}
    <div class="card-footer">
        {{ footer }}
    </div>
    {% endif %}
</div>
{% endmacro %}

# Utilisation
{% from 'components/card.html' import card %}
{{ card('Titre', 'Contenu ici', footer='Bas de page') }}


# === SYSTÈME DE LAYOUT ===

# layouts/base.html
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}{% endblock %} - {{ site_name }}</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    {% block meta %}{% endblock %}
    {% block styles %}
        <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
    {% endblock %}
</head>
<body>
    {% block body %}
        {% include 'partials/header.html' %}
        
        <main>
            {% block content %}{% endblock %}
        </main>
        
        {% include 'partials/footer.html' %}
    {% endblock %}
    
    {% block scripts %}
        <script src="{{ url_for('static', filename='js/main.js') }}"></script>
    {% endblock %}
</body>
</html>

# layouts/dashboard.html
{% extends "layouts/base.html" %}

{% block body %}
    {% include 'partials/navbar.html' %}
    
    <div class="dashboard">
        {% include 'partials/sidebar.html' %}
        
        <main class="dashboard-content">
            {% block dashboard_content %}{% endblock %}
        </main>
    </div>
{% endblock %}


# === MACROS AVANCÉES ===

# macros/forms.html
{% macro input(name, label='', type='text', value='', required=false, **kwargs) %}
<div class="form-group">
    {% if label %}
        <label for="{{ name }}">
            {{ label }}
            {% if required %}<span class="required">*</span>{% endif %}
        </label>
    {% endif %}
    <input 
        type="{{ type }}"
        id="{{ name }}"
        name="{{ name }}"
        value="{{ value }}"
        {% if required %}required{% endif %}
        {% for key, val in kwargs.items() %}
            {{ key }}="{{ val }}"
        {% endfor %}
    >
</div>
{% endmacro %}

{% macro select(name, options, label='', selected='', **kwargs) %}
<div class="form-group">
    {% if label %}
        <label for="{{ name }}">{{ label }}</label>
    {% endif %}
    <select id="{{ name }}" name="{{ name }}" {% for k, v in kwargs.items() %}{{ k }}="{{ v }}"{% endfor %}>
        {% for value, text in options %}
            <option value="{{ value }}" {% if value == selected %}selected{% endif %}>
                {{ text }}
            </option>
        {% endfor %}
    </select>
</div>
{% endmacro %}

{% macro textarea(name, label='', value='', rows=4, **kwargs) %}
<div class="form-group">
    {% if label %}
        <label for="{{ name }}">{{ label }}</label>
    {% endif %}
    <textarea 
        id="{{ name }}"
        name="{{ name }}"
        rows="{{ rows }}"
        {% for k, v in kwargs.items() %}{{ k }}="{{ v }}"{% endfor %}
    >{{ value }}</textarea>
</div>
{% endmacro %}

{% macro checkbox(name, label, checked=false, value='1') %}
<div class="form-check">
    <input 
        type="checkbox"
        id="{{ name }}"
        name="{{ name }}"
        value="{{ value }}"
        {% if checked %}checked{% endif %}
    >
    <label for="{{ name }}">{{ label }}</label>
</div>
{% endmacro %}

{% macro form_errors(errors) %}
    {% if errors %}
    <div class="alert alert-danger">
        <ul>
        {% for error in errors %}
            <li>{{ error }}</li>
        {% endfor %}
        </ul>
    </div>
    {% endif %}
{% endmacro %}


# === MACROS UTILITAIRES ===

# macros/utils.html
{% macro render_pagination(pagination) %}
<nav aria-label="Pagination">
    <ul class="pagination">
        <li class="page-item {% if not pagination.has_prev %}disabled{% endif %}">
            <a class="page-link" href="?page={{ pagination.prev_num }}">Précédent</a>
        </li>
        
        {% for page in pagination.iter_pages() %}
            {% if page %}
                <li class="page-item {% if page == pagination.page %}active{% endif %}">
                    <a class="page-link" href="?page={{ page }}">{{ page }}</a>
                </li>
            {% else %}
                <li class="page-item disabled"><span class="page-link">...</span></li>
            {% endif %}
        {% endfor %}
        
        <li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
            <a class="page-link" href="?page={{ pagination.next_num }}">Suivant</a>
        </li>
    </ul>
</nav>
{% endmacro %}

{% macro render_flash_messages() %}
    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
            {% for category, message in messages %}
                <div class="alert alert-{{ category }} alert-dismissible">
                    {{ message }}
                    <button type="button" class="close">&times;</button>
                </div>
            {% endfor %}
        {% endif %}
    {% endwith %}
{% endmacro %}

{% macro breadcrumb(items) %}
<nav aria-label="breadcrumb">
    <ol class="breadcrumb">
        {% for item in items %}
            <li class="breadcrumb-item {% if loop.last %}active{% endif %}">
                {% if not loop.last %}
                    <a href="{{ item.url }}">{{ item.title }}</a>
                {% else %}
                    {{ item.title }}
                {% endif %}
            </li>
        {% endfor %}
    </ol>
</nav>
{% endmacro %}

{% macro icon(name, size='16') %}
<svg class="icon icon-{{ name }}" width="{{ size }}" height="{{ size }}">
    <use xlink:href="#icon-{{ name }}"></use>
</svg>
{% endmacro %}


# === GESTION DES ASSETS ===

# Avec Flask-Assets ou Webassets
{% assets "css_bundle" %}
    <link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}

{% assets "js_bundle" %}
    <script src="{{ ASSET_URL }}"></script>
{% endassets %}

# Versioning manuel
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}?v={{ version }}">

# Hash pour cache busting
<script src="{{ url_for('static', filename='js/app.js') }}?h={{ file_hash('js/app.js') }}"></script>


# === INTERNATIONALISATION (i18n) ===

from jinja2 import Environment
from jinja2.ext import i18n

# Configuration
env = Environment(extensions=['jinja2.ext.i18n'])

# Installer traductions
import gettext
translations = gettext.translation('messages', localedir='locales', languages=['fr'])
env.install_gettext_translations(translations)

# Dans templates
{% trans %}Hello{% endtrans %}
{% trans %}Welcome to our site{% endtrans %}

# Avec variables
{% trans name=user.name %}Hello {{ name }}{% endtrans %}
{% trans count=items|length %}You have {{ count }} items{% endtrans %}

# Pluralisation
{% trans count=items|length %}
    One item
{% pluralize %}
    {{ count }} items
{% endtrans %}

# Contexte pour traductions ambiguës
{% trans %}File{% endtrans %}                    {# Fichier ou Lime? #}
pgettext('document', 'File')                      {# Fichier #}
pgettext('tool', 'File')                          {# Lime #}

# Messages traduits avec format
{{ _('Hello %(name)s') % {'name': user.name} }}
{{ gettext('Welcome') }}
{{ ngettext('%(num)d item', '%(num)d items', count) }}


# === SÉCURITÉ - BEST PRACTICES ===

# 1. TOUJOURS auto-escape HTML
env = Environment(autoescape=select_autoescape(['html', 'xml']))

# 2. Valider input utilisateur
{% if email is email %}
    {{ email }}
{% else %}
    Email invalide
{% endif %}

# 3. Limiter accès attributs sensibles
class SafeUser:
    def __init__(self, user):
        self._user = user
        self.name = user.name
        self.email = user.email
    
    def __getattr__(self, name):
        if name.startswith('_'):
            raise AttributeError
        return getattr(self._user, name)

# 4. Utiliser sandbox pour templates utilisateurs
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()

# 5. Éviter eval/exec dans templates
# JAMAIS: {{ eval(user_input) }}
# JAMAIS: {% set result = exec(code) %}

# 6. Échapper URLs
<a href="{{ url | urlencode }}">Link</a>

# 7. CSRF tokens dans formulaires
<form method="POST">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    ...
</form>

# 8. Content Security Policy headers
# Configurer au niveau serveur/framework


# === PERFORMANCE - OPTIMISATIONS ===

# 1. Cache templates compilés
env = Environment(
    loader=FileSystemLoader('templates'),
    bytecode_cache=FileSystemBytecodeCache()
)

# 2. Éviter logique complexe dans templates
# [X] Mauvais
{% for user in users %}
    {% if user.is_active and user.role == 'admin' and user.created > cutoff %}
        ...
    {% endif %}
{% endfor %}

# [OK] Bon (filtre en Python)
active_admins = [u for u in users if u.is_active and u.role == 'admin']
template.render(active_admins=active_admins)

# 3. Utiliser select/reject pour filtrage
{{ users | selectattr('active') | list }}
{{ items | rejectattr('deleted') | list }}

# 4. Streaming pour grandes données
template = env.get_template('large.html')
for chunk in template.generate(big_data):
    yield chunk

# 5. Lazy loading avec do extension
{% do expensive_calculation() %}  {# Exécuté mais pas affiché #}

# 6. Limiter profondeur includes
# Maximum 3-4 niveaux d'imbrication

# 7. Précompiler expressions fréquentes
is_admin = env.compile_expression('user.role == "admin"')
if is_admin(user=current_user):
    ...


# === DEBUGGING ===

# Extension debug
{% debug %}
# Affiche toutes les variables du contexte

# Print custom
{% do print(variable) %}

# Assertions
{% if not user %}
    {% do raise('User required') %}
{% endif %}

# Logging personnalisé
{% do app.logger.info('Template rendered', extra={'user': user.id}) %}

# Mode strict undefined
from jinja2 import StrictUndefined
env = Environment(undefined=StrictUndefined)
# Lève exception si variable non définie

# Tracer variables utilisées
import jinja2.meta
source = template.source
ast = env.parse(source)
variables = jinja2.meta.find_undeclared_variables(ast)
print(f"Variables utilisées: {variables}")


# === TESTING TEMPLATES ===

import pytest
from jinja2 import Environment, DictLoader

@pytest.fixture
def env():
    return Environment(loader=DictLoader({
        'test.html': '{{ name | upper }}'
    }))

def test_template_render(env):
    template = env.get_template('test.html')
    result = template.render(name='alice')
    assert result == 'ALICE'

def test_custom_filter(env):
    env.filters['reverse'] = lambda x: x[::-1]
    template = env.from_string('{{ text | reverse }}')
    assert template.render(text='hello') == 'olleh'

def test_undefined_variable():
    from jinja2 import StrictUndefined
    env = Environment(undefined=StrictUndefined)
    template = env.from_string('{{ missing }}')
    
    with pytest.raises(Exception):
        template.render()


# === INTÉGRATION FLASK ===

from flask import Flask, render_template

app = Flask(__name__)

# Ajouter filtre global
@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

# Ajouter test global
@app.template_test('palindrome')
def is_palindrome(s):
    return s == s[::-1]

# Ajouter global function
@app.template_global()
def format_price(amount):
    return f"${amount:,.2f}"

# Context processor
@app.context_processor
def inject_now():
    from datetime import datetime
    return {'now': datetime.now()}

# Route
@app.route('/')
def index():
    return render_template('index.html', 
                          users=users,
                          title='Home')


# === INTÉGRATION DJANGO ===

# settings.py
TEMPLATES = [{
    'BACKEND': 'django.template.backends.jinja2.Jinja2',
    'DIRS': [os.path.join(BASE_DIR, 'jinja2')],
    'APP_DIRS': True,
    'OPTIONS': {
        'environment': 'myapp.jinja2.environment',
        'extensions': ['jinja2.ext.i18n'],
    },
}]

# myapp/jinja2.py
from jinja2 import Environment

def environment(**options):
    env = Environment(**options)
    env.globals.update({
        'static': static,
        'url': reverse,
    })
    return env

# Vue
from django.shortcuts import render
from django.template import loader

def my_view(request):
    return render(request, 'template.html', context)


# === INTÉGRATION FASTAPI ===

from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
    return templates.TemplateResponse(
        "index.html",
        {"request": request, "title": "Home"}
    )

# Ajouter filtres personnalisés
templates.env.filters['currency'] = lambda x: f"${x:,.2f}"


# === PATTERNS AVANCÉS ===

# Pattern: Component Library
# components/button.html
{% macro button(text, type='primary', size='md', **attrs) %}
<button 
    class="btn btn-{{ type }} btn-{{ size }}"
    {% for key, value in attrs.items() %}
        {{ key }}="{{ value }}"
    {% endfor %}
>
    {{ text }}
</button>
{% endmacro %}

# Pattern: Layout Switching
{% set layout = 'admin' if user.is_admin else 'default' %}
{% extends "layouts/" ~ layout ~ ".html" %}

# Pattern: Feature Flags
{% if features.new_design %}
    {% include 'new_design.html' %}
{% else %}
    {% include 'old_design.html' %}
{% endif %}

# Pattern: Conditional CSS Classes
<div class="card {{ 'active' if item.active }} {{ 'featured' if item.featured }}">

# Ou avec filtre
<div class="{{ ['card', 'active' if item.active, 'featured' if item.featured] | select | join(' ') }}">

# Pattern: Dynamic Template Selection
{% include 'cards/' ~ card.type ~ '.html' %}

# Pattern: Template Inheritance Chain
# base.html -> layout.html -> page_type.html -> specific_page.html

# Pattern: Partial Rendering (AJAX)
{% if request.is_ajax %}
    {% include 'partials/results.html' %}
{% else %}
    {% extends 'base.html' %}
    {% block content %}
        {% include 'partials/results.html' %}
    {% endblock %}
{% endif %}


# === ANTI-PATTERNS À ÉVITER ===

# [X] 1. Logique métier dans templates
{% set total = 0 %}
{% for item in items %}
    {% set total = total + item.price * item.quantity %}
{% endfor %}
# [OK] Calculer en Python

# [X] 2. Requêtes DB dans templates
{% for comment in post.comments.filter(active=True) %}
# [OK] Filtrer en Python avant

# [X] 3. Templates trop complexes
{% for ... %}
    {% for ... %}
        {% if ... %}
            {% for ... %}
# [OK] Décomposer en macros/includes

# [X] 4. Duplication de code
# Même HTML répété partout
# [OK] Utiliser macros/includes

# [X] 5. Variables non échappées
{{ user_input | safe }}
# [OK] Toujours valider et échapper

# [X] 6. Noms de variables ambigus
{{ item }}, {{ i }}, {{ x }}
# [OK] Noms descriptifs

# [X] 7. Blocks vides non overridables
{% block content %}
    <p>Contenu fixe</p>
{% endblock %}
# [OK] Laisser extensible

# [X] 8. Chemins templates hardcodés
{% include '/var/www/app/templates/header.html' %}
# [OK] Chemins relatifs


# === MIGRATION DEPUIS AUTRES MOTEURS ===

# Django Template -> Jinja2
# {{ variable }}                -> {{ variable }}
# {% if %}...{% endif %}        -> {% if %}...{% endif %}
# {% for %}...{% endfor %}      -> {% for %}...{% endfor %}
# {{ variable|filter }}         -> {{ variable|filter }}
# {% load tags %}               -> {% import 'macros.html' as macros %}
# {% include "file.html" %}     -> {% include 'file.html' %}
# {% block %}...{% endblock %}  -> {% block %}...{% endblock %}

# Différences principales:
# - Jinja2: () pour appels de méthode
# - Jinja2: tests avec 'is' au lieu de filtres
# - Jinja2: macros au lieu de inclusion tags

# Mako -> Jinja2
# ${variable}                   -> {{ variable }}
# <%def name="func()">          -> {% macro func() %}
# <%include file="x.html" />    -> {% include 'x.html' %}
# <%inherit file="base.html"/>  -> {% extends 'base.html' %}

# Mustache -> Jinja2
# {{variable}}                  -> {{ variable }}
# {{#section}}...{{/section}}   -> {% for item in section %}...{% endfor %}
# {{^inverted}}...{{/inverted}} -> {% if not inverted %}...{% endif %}
# {{>partial}}                  -> {% include 'partial.html' %}


[OK] EXEMPLES PRATIQUES COMPLETS


# === EXEMPLE 1: Blog System ===

# templates/blog/base.html
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Blog{% endblock %}</title>
    <link rel="stylesheet" href="/static/css/blog.css">
</head>
<body>
    {% include 'blog/partials/header.html' %}
    
    <main class="container">
        {% block content %}{% endblock %}
    </main>
    
    {% include 'blog/partials/footer.html' %}
</body>
</html>

# templates/blog/post.html
{% extends 'blog/base.html' %}
{% from 'blog/macros.html' import render_comments, social_share %}

{% block title %}{{ post.title }} - {{ super() }}{% endblock %}

{% block content %}
<article class="post">
    <header>
        <h1>{{ post.title }}</h1>
        <div class="meta">
            <span class="author">Par {{ post.author.name }}</span>
            <time datetime="{{ post.created_at.isoformat() }}">
                {{ post.created_at | datetimeformat('%d %B %Y') }}
            </time>
            <span class="reading-time">{{ post.content | wordcount // 200 }} min de lecture</span>
        </div>
        
        {% if post.tags %}
        <div class="tags">
            {% for tag in post.tags %}
                <a href="{{ url_for('blog.tag', slug=tag.slug) }}" class="tag">
                    #{{ tag.name }}
                </a>
            {% endfor %}
        </div>
        {% endif %}
    </header>
    
    {% if post.cover_image %}
    <img src="{{ post.cover_image }}" alt="{{ post.title }}" class="cover">
    {% endif %}
    
    <div class="content">
        {{ post.content | safe }}
    </div>
    
    {{ social_share(post.title, request.url) }}
    
    <section class="comments">
        <h2>Commentaires ({{ post.comments | length }})</h2>
        {{ render_comments(post.comments) }}
    </section>
</article>

{% if related_posts %}
<aside class="related">
    <h3>Articles similaires</h3>
    {% for related in related_posts[:3] %}
        {% include 'blog/partials/post_card.html' with context %}
    {% endfor %}
</aside>
{% endif %}
{% endblock %}

# templates/blog/macros.html
{% macro render_comments(comments, depth=0) %}
<div class="comments-list" style="margin-left: {{ depth * 20 }}px">
    {% for comment in comments %}
    <div class="comment">
        <div class="comment-header">
            <strong>{{ comment.author.name }}</strong>
            <time>{{ comment.created_at | timeago }}</time>
        </div>
        <div class="comment-body">
            {{ comment.content | urlize | safe }}
        </div>
        
        {% if comment.replies %}
            {{ render_comments(comment.replies, depth + 1) }}
        {% endif %}
    </div>
    {% endfor %}
</div>
{% endmacro %}

{% macro social_share(title, url) %}
<div class="social-share">
    <a href="https://twitter.com/intent/tweet?text={{ title | urlencode }}&url={{ url | urlencode }}"
       target="_blank" class="share-twitter">
        Twitter
    </a>
    <a href="https://www.facebook.com/sharer/sharer.php?u={{ url | urlencode }}"
       target="_blank" class="share-facebook">
        Facebook
    </a>
    <a href="https://www.linkedin.com/sharing/share-offsite/?url={{ url | urlencode }}"
       target="_blank" class="share-linkedin">
        LinkedIn
    </a>
</div>
{% endmacro %}


# === EXEMPLE 2: E-commerce Product Page ===

# templates/shop/product.html
{% extends 'shop/base.html' %}
{% from 'shop/macros/product.html' import product_gallery, price_display, stock_badge %}

{% block title %}{{ product.name }} - Boutique{% endblock %}

{% block content %}
<div class="product-page">
    <div class="product-gallery">
        {{ product_gallery(product.images) }}
    </div>
    
    <div class="product-info">
        <h1>{{ product.name }}</h1>
        
        <div class="product-rating">
            {% for i in range(5) %}
                <span class="star {{ 'filled' if i < product.rating }}">*</span>
            {% endfor %}
            <span class="reviews-count">({{ product.reviews | length }} avis)</span>
        </div>
        
        <div class="product-price">
            {% if product.discount %}
                <span class="price-original">{{ product.price | currency }}</span>
                <span class="price-current">
                    {{ (product.price * (1 - product.discount)) | currency }}
                </span>
                <span class="discount-badge">-{{ (product.discount * 100) | int }}%</span>
            {% else %}
                <span class="price-current">{{ product.price | currency }}</span>
            {% endif %}
        </div>
        
        {{ stock_badge(product.stock) }}
        
        <div class="product-description">
            {{ product.description | markdown | safe }}
        </div>
        
        {% if product.variants %}
        <form method="POST" action="{{ url_for('cart.add', product_id=product.id) }}">
            <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
            
            {% for variant_type, options in product.variants.items() %}
            <div class="variant-selector">
                <label>{{ variant_type | title }}</label>
                <div class="options">
                    {% for option in options %}
                    <label class="option">
                        <input type="radio" 
                               name="{{ variant_type }}" 
                               value="{{ option.id }}"
                               {% if loop.first %}checked{% endif %}
                               {% if not option.in_stock %}disabled{% endif %}>
                        <span>{{ option.name }}</span>
                    </label>
                    {% endfor %}
                </div>
            </div>
            {% endfor %}
            
            <div class="quantity-selector">
                <label>Quantité</label>
                <input type="number" name="quantity" value="1" min="1" max="{{ product.stock }}">
            </div>
            
            <button type="submit" class="btn-add-cart" 
                    {% if product.stock == 0 %}disabled{% endif %}>
                {% if product.stock > 0 %}
                    Ajouter au panier
                {% else %}
                    Rupture de stock
                {% endif %}
            </button>
        </form>
        {% endif %}
        
        <div class="product-features">
            <h3>Caractéristiques</h3>
            <dl>
                {% for key, value in product.features.items() %}
                <dt>{{ key }}</dt>
                <dd>{{ value }}</dd>
                {% endfor %}
            </dl>
        </div>
    </div>
</div>

<section class="product-reviews">
    <h2>Avis clients</h2>
    {% for review in product.reviews | sort(attribute='created_at', reverse=True) %}
    <div class="review">
        <div class="review-header">
            <div class="rating">
                {% for i in range(5) %}
                    <span class="star {{ 'filled' if i < review.rating }}">*</span>
                {% endfor %}
            </div>
            <strong>{{ review.author_name }}</strong>
            <time>{{ review.created_at | timeago }}</time>
        </div>
        <p>{{ review.content }}</p>
        
        {% if review.verified_purchase %}
        <span class="badge">Achat vérifié</span>
        {% endif %}
    </div>
    {% else %}
    <p class="no-reviews">Aucun avis pour le moment. Soyez le premier à donner votre avis!</p>
    {% endfor %}
</section>
{% endblock %}


# === EXEMPLE 3: Admin Dashboard ===

# templates/admin/dashboard.html
{% extends 'admin/base.html' %}
{% from 'admin/macros/stats.html' import stat_card, chart %}
{% from 'admin/macros/tables.html' import data_table %}

{% block content %}
<div class="dashboard">
    <h1>Tableau de bord</h1>
    
    <div class="stats-grid">
        {{ stat_card('Utilisateurs', stats.users.total, stats.users.change, 'users') }}
        {{ stat_card('Ventes', stats.sales.total | currency, stats.sales.change, 'sales') }}
        {{ stat_card('Commandes', stats.orders.total, stats.orders.change, 'orders') }}
        {{ stat_card('Revenus', stats.revenue.total | currency, stats.revenue.change, 'revenue') }}
    </div>
    
    <div class="charts-grid">
        <div class="chart-container">
            <h3>Ventes mensuelles</h3>
            {{ chart('sales-chart', sales_data, 'line') }}
        </div>
        
        <div class="chart-container">
            <h3>Répartition des commandes</h3>
            {{ chart('orders-chart', orders_data, 'pie') }}
        </div>
    </div>
    
    <div class="recent-activity">
        <h3>Activité récente</h3>
        {{ data_table(
            recent_orders,
            columns=['ID', 'Client', 'Total', 'Status', 'Date'],
            actions=True
        ) }}
    </div>
</div>
{% endblock %}


[OK] RESSOURCES ET DOCUMENTATION

# Documentation officielle
# https://jinja.palletsprojects.com/

# Cheatsheet rapide
# https://jinja.palletsprojects.com/en/templates/

# Code source
# https://github.com/pallets/jinja

# Extensions communautaires
# - jinja2-time: filtres date/time avancés
# - jinja2-humanize: formatage humain (filesizes, etc.)
# - jinja2-markdown: support Markdown
# - jinja2-pluralize: pluralisation avancée

pip install jinja2-time jinja2-humanize

# Outils de développement
# - Jinja2 Live Parser: https://j2live.ttl255.com/
# - Jinja2 Template Tester
# - VSCode extensions: "Better Jinja"

# Livres et tutoriels
# - "Flask Web Development" par Miguel Grinberg
# - Real Python - Jinja Templating
# - Full Stack Python - Jinja2

# Communauté
# - Discord: Pallets Projects
# - Stack Overflow: tag [jinja2]
# - Reddit: r/flask