Templates



Nos pages sont pour l'instant encore un peu tristounettes et basiques. Si nous voulons plus de richesse, il va falloir du HTML, voire du CSS. Mais générer tout ce contenu en python dans des f-strings peut vite devenir compliqué et tout aussi illisible qu'un programme en PHP :P.

Bottle (comme tous les autres frameworks) fournit des outils pour faciliter la création de pages : ce sont les templates.

Un template est un fichier qui contient du HTML et des balises spéciales pour intégrer des données dynamiques.

Plusieurs moteurs de templates existent, mais nous allons utiliser le moteur intégré de bottle, qui est très simple à utiliser et suffisant pour nos besoins.

Moteur de template SimpleTemplate

Bottle utilise le moteur de template SimpleTemplate (ou stpl). Le principe du template est de pouvoir être "rendu" avec des données dynamiques, en remplaçant les balises spéciales par les valeurs correspondantes.

Télécharger le code

from bottle import SimpleTemplate
tpl = SimpleTemplate('Hello {{name}}!')
print(tpl.render(name='World')) # affiche u'Hello World!'
assert tpl.render(name='World') == 'Hello World!'

ou bien

from bottle import SimpleTemplate
tpl = SimpleTemplate('Hello {{name}}!')
dico = {'name': 'World'}
print(tpl.render(**dico)) # affiche 'Hello World!'
assert tpl.render(**dico) == 'Hello World!'

Référence : https://bottlepy.org/docs/dev/stpl.html

En résumé, un template est une sorte de f-string boostée.

Utilisation

Pour utiliser les templates, il faut d'abord importer la fonction template de bottle.

from bottle import template #...

On peut alors utiliser les templates de deux manières :

  • soit en les définissant dans des chaînes de caractères
  • soit en les définissant dans des fichiers séparés, par défaut placés dans le répertoire views 1.

Templates dans le code

Télécharger le code

HOMEPAGE = """
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Page d'accueil</title>
</head>
<body>
    <h1>Page d'accueil</h1>
    <p>Hello {{ name }}</p>
</body>
</html>
"""

@route('/')
def hello():
    return template(HOMEPAGE, name="World")

Templates dans des fichiers

Fichier views/index.html (télécharger le code) :

<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Page d'accueil</title>
  </head>
  <body>
    <h1>Page d'accueil</h1>
    <p>Hello {{ name }}</p>
  </body>
</html>

Fichier main.py (télécharger le code) :

@route('/')
def index():
    return template('index.html', name="World")

Syntaxe

Expressions en ligne

Les expressions en ligne sont entourées de {{ et }}. Elles peuvent contenir n'importe quelle expression python (des variables, des opérations arithmétiques, des appels de fonctions, etc...) à partir du moment où cette expression renvoie une chaîne de caractères ou quelque chose qui peut être converti en chaîne de caractères.

<p>Hello {{ name }}</p>
<p>2 + 2 = {{ 2 + 2 }}</p>
<p>Le résultat est {{ "positif" if a > 0 else "negatif" }}</p>

L'expression est évaluée lors du rendu et a accès à tous les arguments passés au template (le contexte). Les caractères HTML spéciaux sont échappés pour éviter les injections XSS. Par exemple, si name contient <script>alert("Hello")</script>, le code rendra &lt;script&gt;alert("Hello")&lt;/script&gt; et non pas le script.

Si vous voulez afficher le code HTML tel quel (sans échappement), il faut utiliser la balise {{! et }} :

<p>Hello {{! name }}</p>

Code python inclus

Il est possible d'inclure des blocs de python dans le template. Les lignes de code commencent par %, ou bien <% et %> pour les blocs de code. Le code python "inclus" suit la même syntaxe que le python normal, avec une exception notable : l'indentation est ignorée, pour permettre d'écrire du html joliment, tous les blocs qui nécessiteraient une indentation en python doivent être fermés explicitement avec un mot clef end :

% name = "Bob" # une ligne de code python
<p>Du contenu HTML</p>
<% 
# Un bloc de code python 
name = name.lower().strip()
%>
<p>Encore du html</p>
<p>Bonjour {{ name }}</p>
% # Conditions
% if name == "Bob":
  <p>Bonjour Bob</p>
% else:
  <p>Bonjour autre</p>
% end
% # Boucle for range
<ul>
% for i in range(1, 11):
  <li>{{ i }}</li>
% end
</ul>
% # Boucle sur un dictionnaire
% # à appeler avec `template('index.html', dico={'a': 1, 'b': 2})`
<ul>
% for key, value in dico.items():
  <li>{{ key }} : {{ value }}</li>
% end
</ul>
% # Fonctions, bloc de code ...
<% 
def add(a, b):
    return a + b
end
%>
<p>2 + 2 = {{ add(2, 2) }}</p>

Fonctions de template

Quelques fonctions utiles sont directement utilisables dans les templates :

  • include(sub_template, **context) : inclut un sous-template dans le template courant. Le contexte est passé au sous-template.
  • setdefault(name, default) : définit une valeur par défaut pour name dans le contexte. Si name n'est pas défini, il sera défini avec la valeur de default.
  • defined(name) : renvoie True si le nom est défini dans le contexte, False sinon.
  • get(name, default=None) : renvoie la valeur de name dans le contexte, ou default si name n'est pas défini.

include est très utile pour éviter de répéter du code dans plusieurs templates. Par exemple, si vous avez un template header.html qui contient le code HTML d'en-tête de votre site, vous pouvez l'inclure dans tous vos templates.

setdefault est aussi très utile pour définir des valeurs par défaut pour les variables dans le contexte, de manière à pouvoir appeler les templates en fournissant juste les variables qui vous intéressent.

% setdefault('page_title', "Page d'accueil")
% setdefault('erreur', None)
% # ...
<h1>{{page_title}}</h1>
% if erreur:
  <p class="erreur">{{ erreur }}</p>
% end
# ... appels possibles de ce template
    return template('tpl08.html', page_title="Page d'accueil", erreur="Erreur de saisie")
    return template('tpl08.html', page_title="Page d'accueil")
    return template('tpl08.html')

Liste complète des fonctions de template : https://bottlepy.org/docs/dev/stpl.html#template-functions

En pratique

Vue index / vue hello_user

Créer une vue index qui affiche une page d'accueil HTML. Vous pouvez utiliser du CSS pour "décorer" votre page, le css devant pour l'instant être inclus directement dans le HTML. Ajoutez un lien vers la route /hello/Bob (ou autre) pour tester la route hello_user.

<head>
  <!-- .... -->
  <style>
    body {
      background-color: #f0f0f0;
      color: #333;
      font-family: Arial, sans-serif;
    }
    h1 {
      color: #007bff;
    }
  </style>
</head>

Modifier la vue hello_user (route /hello/<name>) pour qu'elle affiche un message de bienvenue joliment présenté (en utilisant un template hello_user.html). Rajouter un lien vers la page d'accueil.

Correction

Fichier main.py (télécharger le code) :

from bottle import route, run, template

# Page d'accueil
@route('/')
def index():
    return template('index.html')

# Bonjour personnalisé
@route('/hello/<name>')
def hello_user(name):
    return template('hello_user.html', name=name)

Fichier views/index.html (télécharger le code) :

<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Page d'accueil</title>
    <style>
      body {
        background-color: #f0f0f0;
        color: #333;
        font-family: Arial, sans-serif;
      }
      h1 {
        color: #007bff;
      }
    </style>
  </head>
  <body>
    <h1>Page d'accueil</h1>
    <p>Bienvenue sur notre site !</p>
    <p><a href="/hello/Bob">Dire bonjour à Bob</a></p>
  </body>
</html>

Fichier views/hello_user.html (télécharger le code) :

<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Bonjour {{ name }}</title>
    <style>
      body {
        background-color: #f0f0f0;
        color: #333;
        font-family: Arial, sans-serif;
      }
      h1 {
        color: #007bff;
      }
    </style>
  </head>
  <body>
    <h1>Bonjour {{ name }}</h1>
    <p>Bienvenue sur notre site !</p>
    <p><a href="/">Retour à la page d'accueil</a></p>
  </body>
</html>

Table de multiplication

Créer une route /tabmul/<nb:int>, associée à un template tabmul.html, qui affiche la table de multiplication de nb, et qui propose une liste de liens permettant d'afficher les tables de multiplication de 1 à 10.

Table de multiplication

Correction

Fichier main.py (télécharger le code)

#...
# Table de multiplication
@route('/tabmul/<nb:int>')
def tabmul(nb):
    return template('tabmul.html', nb=nb)

Fichier views/tabmul.html (télécharger le code)

<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Table de multiplication de {{ nb }}</title>
  </head>
  <body>
    <h1>Table de multiplication de {{ nb }}</h1>
    <ul>
      % for i in range(1, 11):
      <li>{{ nb }} x {{ i }} = {{ nb * i }}</li>
      % end
    </ul>
    <h2>Autres tables de multiplication</h2>
    <ul>
      % for i in range(1, 11):
      <li>Table de <a href="/tabmul/{{ i }}">{{ i }}</a></li>
      % end
    </ul>
    <p><a href="/">Retour à la page d'accueil</a></p>
  </body>
</html>

1

On peut modifier le chemin de recherche des templates en modifiant la variable TEMPLATE_PATH (from bottle import TEMPLATE_PATH), qui contient une liste de chemins dans lesquels bottle cherchera les templates. Par défaut, les chemins sont : “./” et “./views”, c’est à dire le répertoire courant (celui dans lequel on a lancé le serveur qui n’est pas forcément celui du fichier python du serveur) et le sous-répertoire “views” du répertoire courant.

Pour être ne plus dépendre du répertoire courant, il est pratique de modifier TEMPLATE_PATH pour lui indiquer comme chemin de recherche le répertoire du fichier python du serveur. Cela peut se faire à l’aide de la ligne

from os.path import dirname
TEMPLATE_PATH.insert(0, join(dirname(__file__), "xxxxx")

Cette ligne récupère le répertoire de __file__ (une variable spéciale de python qui contient le chemin absolu vers le fichier dans lequel elle se trouve), et insère cette valeur au début de TEMPLATE_PATH (= ce sera le premier chemin recherché), en ayant joint ce répertoire avec le répertoire “xxxxx” dans lequel on veut chercher les templates.