28 KiB
title, author
title | author |
---|---|
Django | Steve Kossouho |
Templates avec Django
:::notes Préférer commencer par les vues, pour avoir des vues où utiliser des templates... :::
Principe de templating
Le concept de [templating]{.naming} (gabarits en français) consiste à utiliser des fichiers contenant du texte
avec des emplacements réservés, qui seront remplis à la demande pour afficher des données personnalisées à l'utilisateur
(par exemple, le système permet de générer un email de bienvenue commençant par « Bonjour votre prénom
»).
Ce concept est fourni par de nombreuses bibliothèques, et ce dans plusieurs langages (PHP, Javascript, C++, Python etc.)
Exemples de systèmes de templating en Python
Il existe un certain nombre de moteurs de templating assez populaires en Python :
- Django : Intégré à Django, propose un bon compromis entre performance et sécurité.
- Jinja 3 : Basé sur Django (et Genshi ?), mais utilisable dans tout projet Python. Instagram
- Mako : Performant, autorise le code Python brut. Reddit
- Genshi : Utilisé dans TurboGears
Django est à l'origine de deux moteurs de templates populaires, tels que Jinja
pour Python et Twig
pour PHP qui en reprennent largement la syntaxe.
Principe de templating dans Django
Un moteur de templates propose une façon simple et rapide de générer du contenu en rédigeant des gabarits (des squelettes de document), des modèles où il ne reste plus qu'à renseigner des éléments manquants. Il existe plusieurs façons de procéder lorsqu'on conçoit un langage de templates :
- Un moteur peut offrir la possibilité d'insérer des expressions Python sans limite dans un document de template
- Un langage peut permettre d'écrire du Python brut pour générer du contenu
- Un langage peut offrir des fonctionnalités limitées pour éviter des injections de code ou l'écriture de code métier en dehors du code principal
Les considérations suivantes ont mené à la création du moteur de templates de Django :
- Les templates sont souvent rédigés et modifiés par des designers (HTML notamment)
- Écrire du code Python ne devrait se faire que dans des fichiers Python
- Exécuter du code Python en dehors de scripts Python normaux rend un programme plus dur à déboguer
- Un designer ou intégrateur ne devrait pas être tenu d'apprendre le Python
Ainsi, le moteur de templates de Django repose sur des paradigmes simples :
- Une syntaxe peu variée pour simplifier l'apprentissage (interpolation, attributs, filtres et balises)
- Pas une ligne de Python autorisée (même si la syntaxe est similaire)
- Système extensible, mais uniquement via du code Python
Le langage de templates de Django
Toute la section qui va suivre concerne les éléments de langage spécifiques aux templates de Django.
Créer des templates
Par défaut, Django ne sait trouver et exploiter des documents de templates que grâce à un paramètre TEMPLATES
dans le fichier settings
de votre projet :
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [], # Répertoires où trouver des templates
"APP_DIRS": True, # Utiliser le répertoire `templates` de mes applications
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
],
},
},
]
- Documentation sur le paramètre
TEMPLATES
- La clé
BACKEND
permet de choisir le moteur de templates de Django (parmi deux) - La clé
APP_DIRS
permet d'indiquer si les répertoirestemplates
des applications tierces sont automatiquement utilisés par le moteur. - La clé
DIRS
, elle, permet de spécifier des répertoires supplémentaires dans lesquels le moteur de templates de Django ira automatiquement chercher des fichiers de templates.
Nommage des templates
Dans Django, lorsque vous souhaitez afficher un template au navigateur, vous avez accès à une fonction de raccourci nommée django.shortcuts.render(request, template, context)
.
Si vous appelez cette fonction avec les arguments render(request, "index.html", {"value": 3.14159})
{.py} :
- Django va considérer le premier fichier
index.html
trouvé dans les répertoirestemplates
des applications tierces (celles indiquées danssettings.INSTALLED_APPS
) - Le fichier
index.html
sera rendu en ayant accès aux données decontext
- La fonction renverra à la fin un objet
HttpResponse
avec le document rendu. - Documentation sur la fonction
render
Exemple de template
from django.shortcuts import render
def index(request):
return render(request, "index.html", {"value": 3.14159})
Le template index.html
peut être trouvé dans le dossier templates
de votre application :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index</title>
</head>
<body>
<h1>Index</h1>
<p>Value: {{ value }}</p>
</body>
</html>
Commentaires
En HTML, la balise de commentaire s'écrit de la façon suivante :
<!-- Texte de commentaire HTML -->
C'est utile pour annoter un document, mais deux choses se produisent :
- Le commentaire est visible dans le navigateur.
- Écrire ce type de commentaire dans un document qui n'est pas du HTML ne fonctionnera pas.
Avec Django, on peut insérer un vrai commentaire de template via la balise suivante :
{# Texte de commentaire Django #}
Le commentaire ne sera jamais rendu par le moteur, au même titre que les commentaires Python sont ignorés par l'interpréteur. Essayez autant que possible de vous en servir pour plus facilement relire vos templates a posteriori.
Interpolation de données
Lorsque vous effectuez le rendu d'un template, le seul moyen d'y insérer des données est de les passer via un [contexte]{.naming}. Un contexte est un simple dictionnaire Python, où les clés sont des noms qui seront utilisables uniquement dans le template. Si en Python, on rend un template :
from django.shortcuts import render
def view_func(request):
return render(request, "template.html", {"name": "Maurice"})
Le template aura uniquement accès à une donnée nommée name
et dont la valeur sera "Maurice"
{.py}. (Voir Documentation sur les modifieurs de contexte pour savoir quels autres noms sont rendus disponibles dans le contexte de template)
Bonjour, {{ name }}
Interpoler un attribut d'une variable
Lorsqu'une des variables du contexte est un objet qui contient des attributs, vous pouvez les interpoler avec la syntaxe suivante :
{{ var1.attribute }}
Interpoler un élément d'une variable (clé ou index)
De la même façon, si votre variable de contexte est une liste ou un dictionnaire dont vous voulez récupérer un élement, et que vous connaissez son index ou bien sa clé, vous pouvez utiliser la syntaxe suivante :
{{ var1.nom_de_la_clé }} {# clé de dictionnaire qui est une chaîne #}
{{ var2.numéro_d'index }} {# index de séquence #}
Interpoler la valeur de retour d'une méthode
Pour terminer, si votre variable de contexte est un objet dont vous voulez récupérer la valeur de retour d'une méthode, vous pouvez écrire :
{{ var1.nom_de_la_methode }}
Cette méthode sera toujours appelée sans argument.
Interpolation et limitations
Le langage de templates de Django a des restrictions sur les interpolations :
- Vous ne pouvez pas passer d'arguments lorsque vous appelez une méthode et c'est intentionnel, seules les méthodes acceptant uniquement des arguments facultatifs sont utilisables.
- Vous ne pouvez pas utiliser d'attributs ou méthodes commençant par
_
{.py} (considérées comme privées en Python).
Filtres de templates
Lorsque vous effectuez une interpolation dans un template ({{ ... }}
), vous avez la possibilité de transformer l'interpolation, souvent pour des questions de présentation. Pour effectuer ce genre de traitement sur une interpolation, vous devez utiliser des [filtres]{.naming}.
Les filtres permettent de transformer une interpolation et fonctionnent sur chaînes, dates, listes et d'autres types. (voir filtres intégrés à Django)
Filtre basique (sans argument)
Un filtre s'utilise simplement en le "pipant" à l'expression à transformer. Le résultat affiché sera celui du filtre. Il est possible d'utiliser plusieurs filtres successivement en les chaînant.
{# Utilisation du filtre upper, pour mettre en majuscules #}
<h1>{{ name|upper }}</h1>
Filtre avec argument
Certains filtres acceptent un argument (et un seul maximum). Pour les utiliser, il suffit de faire suivre le nom du filtre par :
et d'indiquer à la suite la valeur du paramètre, généralement sous forme d'une chaîne de caractères. Ceci est documenté pour chaque filtre disponible.
{# Utilisation du filtre date pour formater un objet datetime #}
<p>Date de création : {{ moment|date:"d m Y" }}</p>
Filtres fréquents
Parmi les nombreux filtres disponibles de base dans Django, certains seront plus souvent utilisés que d'autres, comme par exemple :
Filtre | Description |
---|---|
upper et lower |
Pour changer la casse du texte (majuscules et minuscules). |
default:value |
Si la valeur équivaut à False , utiliser value par défaut. |
default_if_none:value |
Si la valeur équivaut à None , utiliser value par défaut. |
date:format |
Interpoler un objet datetime avec un format spécifique. |
length |
Interpoler la longueur d'une collection. |
safe |
Ne pas échapper le contenu à interpoler. Permet d'insérer du HTML. |
truncatechars:num et truncatewords:num |
Couper et placer une ellipse (tronquer le texte après un certain nombre de caractères ou de mots). |
Balises de templates
Nous venons de voir que l'on pouvait "filtrer" du contenu provenant d'une interpolation. Et s'il était possible d'utiliser des outils plus avancés, permettant de générer des données dynamiques, ou même de rendre du contenu conditionnel, par exemple ?
Le moteur de templates de Django fournit un système de [balises]{.naming} qui nous permet d'accéder à des comportements plus avancés. Grâce aux balises, nous allons pouvoir utiliser des boucles, conditionner le rendu d'un segment de template, configurer le rendu par le moteur ou encore modifier le contexte de rendu...
Syntaxe des balises
Pour utiliser des balises de gabarit, le plus important, c'est d'en connaitre la syntaxe. Pour utiliser une balise, il faut écrire :
{% tag_name argument1 argument2... %}
Entre les marqueurs {% ... %}
il suffit d'indiquer le nom de la balise et d'y ajouter, séparés par des espaces, autant d'arguments que ce qu'accepte la balise. Certaines balises fonctionnent par paire, comme {% if %}…{% endif %}
{.djangotemplate}, ou {% for %}…{% endfor %}
{.djangotemplate}. Ces balises sont documentées dans la documentation officielle de Django
Balises natives
Pour aborder le chapitre des balises, nous allons nous intéresser à certaines balises précises dédiées à des outils importants.
Conditions
Balise | Description |
---|---|
{% if %} |
Évalue une condition et affiche le contenu si la condition est vraie. |
{% elif %} |
Alternative à {% if %} si la première condition est fausse. |
{% else %} |
Définit un bloc à afficher si aucune des conditions précédentes n'est vraie. |
{% endif %} |
Termine une instruction conditionnelle. |
Boucles
Balise | Description |
---|---|
{% for ... in ... %} |
Itère sur une séquence d'objets. |
{% empty %} |
Définit un bloc à afficher si la séquence est vide. |
{% endfor %} |
Termine une boucle. |
Autres Balises Importantes
Balise | Description |
---|---|
{% include %} |
Inclut un autre template. |
{% extends %} |
Indique que le template hérite d'un autre template. |
{% block %} |
Définit un bloc de contenu modifiable dans un template hérité. |
{% endblock %} |
Termine un bloc de contenu. |
{% url %} |
Génère une URL vers une vue nommée. |
{% csrf_token %} |
Génère un jeton CSRF pour sécuriser les formulaires. |
{% comment %} et {% endcomment %} |
Commente une section du template. |
{% load %} |
Charge une bibliothèque de tags personnalisés. |
{% with %} et {% endwith %} |
Assigne une ou plusieurs variables pour une portée limitée. |
{% endwith %} |
Termine le bloc with . |
Balise | Description |
---|---|
{% autoescape %} |
Active ou désactive l'échappement automatique dans un bloc. |
{% endautoescape %} |
Termine le bloc autoescape . |
{% now %} |
Affiche la date et l'heure actuelles selon un format spécifié. |
{% spaceless %} |
Supprime les espaces blancs entre les balises HTML dans le contenu du bloc. |
{% endspaceless %} |
Termine le bloc spaceless . |
{% verbatim %} |
Ignore le rendu des balises Django dans le contenu du bloc. |
{% endverbatim %} |
Termine le bloc verbatim . |
Structures de contrôle : if
Vous pouvez grace aux balises {% if %}
{.djangotemplate} et {% else %}
{.djangotemplate} construire un document dont certaines zones seront rendues conditionnellement.
La syntaxe de la balise est similitaire aux structures conditionnelles en Python.
{% if expression %}
Contenu si la condition est vraie
{% elif expression %}
Contenu si la premiere condition est fausse
{% else %}
Contenu si la condition est fausse
{% endif %}
Structures de contrôle : for
Si vous souhaitez générer du contenu pour chaque élément d'un itérable, vous pouvez utiliser la balise {% for %}
{.djangotemplate}. La syntaxe
de la balise est similaire aux structures de boucle en Python, à l'exception de l'existence d'une clause {% empty %}
{.djangotemplate},
dont le contenu sera affiché si l'itérable est vide.
{% for name in iterable %}
Contenu de l'itération {{ name }}
{% empty %}
Contenu si la boucle est vide
{% endfor %}
Inclusion de templates
La balise {% include %}
{.djangotemplate} permet d'inclure un autre template dans le template actuel. L'intérêt d'inclure des templates est de pouvoir les utiliser comme composants dans certains cas répétitifs. Il est possible de transmettre un contexte spécifique lors de l'inclusion de templates.
{% for user in users %}
{% include "path/to/user_template.html" with user=user %}
{% endfor %}
<card>
<img src="{{ user.picture.url }}">
<p>{{ user.username }}</p>
</card>
Héritage de templates
Dans un site web, beaucoup de pages partagent la même structure. Par exemple, sur de nombreux sites, la barre de navigation est identique et le pied de page également. Il semblerait redondant de devoir répéter dans les templates de diverses sections le même code HTML, notamment l'en-tête HTML ou le pied de page.
Django propose un système d'héritage de templates, permettant de partager des blocs de contenu entre plusieurs templates. La fonctionnalité est offerte via deux balises, {% block %}
{.djangotemplate} et {% extends %}
{.djangotemplate}.
Dans l'héritage de templates, vous avez deux notions de base :
- Un [template de base]{.naming} ou template parent. Ce template est le template principal du site, et permet de fournir le contenu commun aux autres templates.
- Un [template enfant]{.naming}. Ce template est le template qui hérite du template de base, et qui ajoute ou modifie du contenu via des [blocs]{.naming}.
Template de base
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{# Contenu commun aux templates enfants #}
{% block body %}{% endblock body %}
</body>
</html>
Un template de base propose un contenu fixe qui servira de base aux templates enfants. Ce même template proposera des duos de balises {% block name %}
{.djangotemplate} et {% endblock name %}
{.djangotemplate}, dont le contenu sera personnalisable par les templates enfants.
Dans un [template de base]{.naming}, qui contient un document HTML classique, on a tendance à décrire des blocs personnalisables (via la balise {% block %}
{.djangotemplate}) pour des sections du document qui sont souvent les mêmes, en voici une liste :
- Le titre de la page (
<head><title>
) est souvent l'objet d'un bloc Django, nommétitle
oupage_title
- Si la page contient un titre ous forme de balise
<h1>
, son contenu peut faire l'objet d'un bloc Django, nommétitle
- Dans la balise HTML
<head>
, on crée souvent un bloc Django destiné à ajouter des fichiers CSS, souvent nommécss
- Dans la balise HTML
<head>
, on crée souvent un bloc Django destiné à ajouter des fichiers JavaScript, souvent nomméjs
Template enfant
{% extends "path/to/base_template.html" %}
{% block body %}
Contenu de remplacement du block body, spécifique au template enfant
{% endblock body %}
Un template enfant hérite via la balise {% extends %}
{.djangotemplate} de Django. Il peut ensuite uniquement modifier le contenu des blocs {% block %}
{.djangotemplate} et {% endblock %}
{.djangotemplate} de son template de base. Tout le reste du contenu du template enfant sera ignoré.
Réutiliser des blocs dans un template enfant
Dans un template enfant, lorsque vous personnalisez un bloc avec la balise {% block %}
{.djangotemplate}, vous avez la possibilité de récupérer
le contenu original du bloc provenant du template parent, grâce à une variable de contexte nommée block.super
{.djangotemplate}.
{% extends "path/to/base_template.html" %}
{% block body %}
{{ block.super }}
Contenu ajouté au block body, spécifique au template enfant
{% endblock body %}
Stratégies de création des templates
En général, lorsque l'on souhaite exploiter les fonctionnalités d'héritage de templates, on essaie d'organiser l'arborescence des templates de façon à faciliter leur maintenance. Quelques astuces à ce sujet :
- Le template de base peut se trouver à la racine d'un dossier de templates personnalisé (voir la clé
["DIRS"]
{.python} desettings.TEMPLATES
) - Le template de base peut aussi se trouver à la racine du répertoire
templates
de l'application Django principale. - Les autres templates se trouvent généralement dans un sous-dossier de
templates
portant le nom de l'application Django. - Les autres templates peuvent être de plusieurs types :
- Template pour une page entière
- Template utilisé avec la balise Django
{% include %}
{.djangotemplate} - Template pour générer le contenu d'e-mails
- etc.
- Tous ces templates peuvent être organisés dans des répertoires séparés indiquant leur usage.
Balises utiles
Parmi les balises disponibles par défaut dans Django, quelques autres sont indispensables :
Balise | Description |
---|---|
{% url %} {.djangotemplate} |
Rendre une URL que l'on a nommée |
{% with %} {.djangotemplate} et {% endwith %} {.djangotemplate} |
Créer un contexte temporaire |
{% load %} {.djangotemplate} |
Chargement de tags et filtres |
{% csrf_token %} {.djangotemplate} |
Insérer un champ de sécurité dans un formulaire |
{% url %}
La balise {% url %}
{.djangotemplate} permet de rendre une URL que l'on a nommée. Si nous avons défini l'URL suivante :
from django.urls import path
urlpatterns = [
path("url/path/<type:param>", view_name, name="named"),
]
Nous pouvons insérer dans un template l'URL fixe, quelle qu'elle soit, en utilisant la balise {% url %}
{.djangotemplate} comme suit.
Si l'URL exacte nécessite une valeur spécifique d'un argument ou de plusieurs arguments, nous pouvons utiliser la syntaxe suivante :
<a href="{% url "named" param=value%}">Anchor text</a>
{% with %}
Lorsque certaines expressions complexes sont nécessaires à plusieurs endroits dans un template, ex. :
{{ variable.method|filter1|filter2 }}
...
{{ variable.method|filter1|filter2 }}
On peut utiliser la balise {% with %}
{.djangotemplate} pour créer des variables de contexte temporaires.
Cela permet d'utiliser un nom de variable simple pour référencer cette expression si longue à rédiger.
{% with name=variable.method|filter1|filter2 %}
{{ name }}
...
{{ name }}
{% endwith %}
{% load %}
Dans les templates de Django, la balise {% load %}
{.djangotemplate} permet de charger des tags et des filtres personnalisés.
Par défaut, seuls les tags et filtres fournis par Django sont chargés. Lorsqu'une application fournit ses propres tags et filtres, il est possible de les charger via la balise {% load %}
{.djangotemplate}, d'une manière similaire aux importations de modules Python.
{% load mytags %}
{% load myfilters %}
La balise sera généralement nécessaire lors de l'usage de fichiers statiques (ex. CSS et JavaScript).
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
Balises et filtres personnalisés
Django permet de développer ses propres filtres et tags, qui pourront être utilisables dans nos
templates. Une fois créés, nous pouvons les importer dans un template via la balise {% load %}
{.djangotemplate}.
Voir la documentation officielle sur les filtres personnalisés
:::notes Faire la démo de l'écriture d'un filtre notamment et d'une balise simple, puis proposer un exercice. :::
Fichiers statiques
Documentation de la gestion des fichiers statiques
Lorsque l'on sert du HTML, le document référence souvent des fichiers statiques tels que des feuilles de style CSS, des fichiers de code JavaScript ou encore des images spécifiques au site (bannière, logo, tout média qui n'est pas envoyé dynamiquement par les utilisateurs du site).
Django simplifie l'utilisation de ces fichiers pour le développement, et offre également des commandes de gestion (manage.py
) pour préparer
le passage en production.
Il y a plusieurs étapes à suivre lorsque l'on souhaite utiliser des fichiers statiques en développement :
- Vérifier que
"django.contrib.staticfiles"
est présentsettings.INSTALLED_APPS
- Avoir un répertoire
static/
dans une ou plusieurs applications (ou créer un répertoire personnalisé) - Ajouter dans
settings.py
une liste dansSTATICFILES_DIRS
qui contient les répertoiresstatic
que vous avez créés - Ajouter
{% load static %}
{.djangotemplate} aux templates utilisant la balise{% static %}
{.djangotemplate} - Référencer le chemin relatif de la ressource via
{% static "chemin/relatif.ext" %}
À l'exécution, et uniquement en mode DEBUG = True
{.python}, le serveur de développement de Django sera capable
de trouver et servir correctement les ressources stockées dans les répertoires static
au navigateur.