Pour cette session nous utiliserons un projet déjà existant. Veuillez récupérer ZIP l’archive accessible à l’URL suivante tp_templates_start.zip.
Ce projet contient 3 vues:
- view_a
- view_b
- view_c
qui ont le même code et font appel à des templates dont le code est très similaire, et donc affichent une page web similaire, dont seule une partie du code HTML change. Jetez un œil à ces fonctions et à ces templates.
Il y a clairement duplication de code, ce qui contrevient au principe Do not repeat yourself
recherché lors du développement de logiciels.
Dans cette session, nous verrons comment utiliser des fonctions avancées du moteur de templates “Jinja2” pour limiter les répétitions de code.
Templates Jinja avancées
Pour cet exemple, nous utiliserons du code CSS intégré directement dans la page entre deux balises <style>
. Cette pratique est généralement découragée, mais simplifiera la compréhension dans ce cas.
Héritage de templates
Introduction
On peut avec Jinja définir des blocs à l’intérieur d’une template avec les instructions block
:
{% block nom_du_bloc %}
ancienne valeur
{% endblock %}
On peut ensuite de définir une template “fille”, héritant d’une template mère, et qui modifiera la valeur d’un des blocs de la template mère. Cela se fait en:
- déclarant une nouvelle template (la template fille)
- indiquant que la template fille étend la template mère avec l’instruction
extends
:{% extends "template_mere.jinja2" %}
- en redéfinissant le bloc dans la template fille:
{% block nom_du_bloc %} nouvelle valeur {% endblock %}
Ce principe est par exemple utilisé pour des éléments d’un site web qui seront fixés dans un template mère quand ils ne changent pas d’une page à l’autre (header, nav, footer, … - vous pouvez trouver la sémantique des ces éléments ici) ; dans un template fille pour la partie qui change.
Exercice
Définir un nouveau fichier template templates/layout.html.jinja2
, et y mettre le code suivant:
<html>
<head>
<title>{{ title }}</title>
<!-- <style> est une balise qui permet d'inclure du code CSS dans une page web,
sans définir un fichier externe de style CSS -->
<style>
.active {
color: red;
font-weight: bold;
}
</style>
</head>
<body>
<header>
<a href="/view/a" class="active">Template A</a> |
<a href="/view/b">Template B</a> |
<a href="/view/c">Template C</a>
<hr>
</header>
{% block body %}
{% endblock %}
<footer>
<hr>
Flask SAR - 2019
</footer>
</body>
</html>
et modifier les templates:
- templates/template_a.html.jinja2
- templates/template_b.html.jinja2
- templates/template_c.html.jinja2
pour qu’elles étendent la template layout.html.jinja2
et adapter le message affiché au fichier de template:
{% extends "layout.html.jinja2" %}
{% block body %}
<p>Template A</p>
{% endblock %}
Nous avons factorisé le code des templates. Cependant, l’affichage de la page active dans la barre du haut n’est pas bon : la template A est marquée sélectionnée, même si nous naviguons sur les pages des templates B et C. Nous verrons dans la section suivante comment gérer cet affichage avec des variables Jinja.
Manipulation de variables
Nous allons maintenant voir comment ajouter des variables jinja2 dans les templates filles, afin d’indiquer à la template “racine” quelle est la page active ainsi que le nom de la page. Ces variables seront prises en compte dans le code de la template layout.html.jinja2
.
Premièrement, modifier les templates:
- templates/template_a.html.jinja2
- templates/template_b.html.jinja2
- templates/template_c.html.jinja2
pour qu’elles aient la structure suivante:
{% extends "layout.html.jinja2" %}
{% set title = "template A" %}
{% set active_page = "view_a" %}
{% block body %}
<p>Template A</p>
{% endblock %}
Nous allons maintenant modifier la template layout.html.jinja2
, afin qu’elle prenne en compte les précédentes variables:
<html>
<head>
<title>{{ title }}</title>
<style>
.active {
font-weight: bold;
color: red;
}
</style>
</head>
<body>
<header>
<a href="{{ url_for("view_a") }}" {% if active_page == "view_a" %}class="active"{% endif %}>Template A</a> |
<a href="{{ url_for("view_b") }}" {% if active_page == "view_b" %}class="active"{% endif %}>Template B</a> |
<a href="{{ url_for("view_c") }}" {% if active_page == "view_c" %}class="active"{% endif %}>Template C</a>
<hr>
</header>
{% block body %}
{% endblock %}
<footer>
<hr>
Flask SAR - 2019
</footer>
</body>
</html>
En allant sur l’URL http://127.0.0.1:5000/view/b, vous devriez obtenir le résultat suivant:
Macro
Jinja fournit un mécanisme de macros qui permet de factoriser certaines instructions sous la forme d’une fonction. Leur intérêt est de limiter les répétitions de code.
Dans l’exemple précédent, nous avions ce code:
<a href="{{ url_for("view_a") }}" {% if active_page == "view_a" %}class="active"{% endif %}>Template A</a> |
<a href="{{ url_for("view_b") }}" {% if active_page == "view_b" %}class="active"{% endif %}>Template B</a> |
<a href="{{ url_for("view_c") }}" {% if active_page == "view_c" %}class="active"{% endif %}>Template C</a>
qui posait les problèmes suivants:
- répétition trois fois du même code
- la structure de contrôle
if
est mise sur une seule ligne, afin de rendre le code plus concis, mais au final l’ensemble est moins lisible.
Nous allons donc définir une macro dans le fichier layout.html.jinja2
qui prend deux paramètres. Ces paramètres sont réutilisables ensuite dans le corps de la macro:
{% macro set_active_if(variable, name) %}
{% if variable == name %}class="active"{% endif %}
{% endmacro %}
La macro étant définie, elle est utilisable en l’appelant comme une fonction pyth:
<a href="{{ url_for("view_a") }}" {{ set_active_if(active_page, "view_a") }}>Template A</a> |
<a href="{{ url_for("view_b") }}" {{ set_active_if(active_page, "view_b") }}>Template B</a> |
<a href="{{ url_for("view_c") }}" {{ set_active_if(active_page, "view_c") }}>Template C</a>
Le code est maintenant plus concis, et la condition if
est plus lisible. Vous trouverez plus d’informations sur les macros dans la documentation de Jinja.
Ajouter des fichiers CSS et Javascript avec Jinja
Les fichiers CSS et Javascript sont généralement des fichiers statiques, c’est-à-dire que leur contenu ne change pas en fonction des requêtes des utilisateurs.
Avec Flask, il faut mettre les fichiers CSS et Javascript dans le dossier static
. Les fichiers présents dans le dossier static
sont publics, et ont chacun une URL. Vous pouvez donc créer un nouveau projet Flask tp_static
.
Il est possible de récupérer l’URL d’un fichier statique avec la fonction url_for
comme dans l’exemple suivant:
from flask import url_for # a mettre en debut de fichier
fichier_css_url = url_for("static", filename="css/fichier.css")
print(fichier_css_url)
Sous dossiers de ressources
Créez trois dossiers css
, js
et img
dans le dossier static
d’un projet Flask.
Créez le fichier static/css/style.css
avec le code css suivant :
.logo {
width: 400px;
}
Créez le fichier static/js/script.js
avec le code suivant :
(function update_caption() {
let caption_text = "logo IMT Atlantique";
document.getElementById("caption_logo").innerText = caption_text;
})();
Ensuite, vous pouvez faire les actions suivantes :
- allez à cette page https://www.imt-atlantique.fr/fr/l-ecole/logotypes-imt-atlantique
- téléchargez un des logos, mettez le dans le dossier
static/img/
et renommez lelogo.jpg
(adaptez l’extension de fichier à celle du logo téléchargé)
Maintenant que nous avons préparé les resources statiques pour cet exercice, créez une template jinja templates/static_resources.html.jinja2
qui contient le code suivant:
<html>
<head>
<title>Static resources with Flask</title>
<link rel="stylesheet" href="{{ url_for("static", filename="css/style.css") }}"/>
</head>
<body>
<img class="logo" src="{{ url_for("static", filename="img/logo.jpg") }}"/>
<span id="caption_logo"></span>
</body>
<script src="{{ url_for("static", filename="js/script.js") }}"></script>
</html>
et appeler cette template depuis une nouvelle fonction Python:
@app.route('/static_resources_view')
def static_resources_view():
return flask.render_template("static_resources.html.jinja2")
En allant sur le lien http://127.0.0.1:5000/static_resources_view, vous devriez obtenir le résultat suivant :
En affichant le code source de la page, on peut constater que le texte sous le logo de l’IMT n’apparait pas dans le code HTML:
En effet, nous l’avons fait apparaitre en modifiant le DOM de la page Web avec Javascript. Nous verrons dans une session future comment faire des modifications avancées avec Javascript.
À noter également que si vous accédez aux outils de développeurs (dev tools) comme indiqué dans le troubleshooting de la page annexes, vous pouvez néanmoins bien voir la modification effectuée sur la page par Javascript.
Correction
Vous pouvez récupérer une archive ZIP tp_templates_correction.zip contenant la correction de cette séance.