Chapitre 4 : Le système de gabarit de Django
Au chapitre précédent, vous avez peut être remarqués la façon particulière dont nous retournions le texte de nos vues d’exemple. À savoir que le HTLM était codé « en dur », directement dans notre code Python.
Ce procédé engendre plusieurs problèmes :
- Tout changement dans le graphisme de la page implique une modification du code Python. L’apparence d’un site a tendance à changer plus fréquemment que le code Python sous-jacent, il serait donc plus pratique de pouvoir changer l’apparence sans avoir à modifier le code Python.
- Écrire du code Python et faire du design HTML sont deux disciplines différentes, et la plupart des environnements de développement professionnels séparent ces responsabilités entre des personnes différentes (voir même des départements différents). Les graphistes et les codeurs HTML/CSS ne doivent pas avoir à éditer du code Python pour faire le travail ; ils doivent avoir affaire au HTML.
- De la même façon, il est plus efficace que les programmeurs puissent travailler sur le code Python pendant que les graphistes travaillent sur les gabarits, plutôt qu’une personne attende de l’autre qu’elle termine l’édition d’un seul fichier qui contient à la fois du Python et du HTML.
Pour toutes ces raisons, il est plus propre et plus maintenable de séparer le design des pages du code Python lui même. Nous pouvons faire cela avec le système de gabarit de Django, dont nous parlons tout au long de ce chapitre.
Les bases du système de gabarit
Un gabarit Django (ndt : on utilise aussi l’anglicisme “template”) est une chaîne de texte qui est destinée à séparer la présentation d’un document des données qu’il contient. Un modèle définit des paramètres fictifs ainsi que divers morceaux de logique de base (autrement dit, les balises du gabarit) qui régissent la façon dont le document doit être affiché. D’habitude, les gabarits sont utilisés pour produire du HTML, mais les gabarits Django sont aussi capables de générer tout format basé sur du texte.
Illustrons cela par un exemple simple de gabarit. Ce gabarit montre une page HTML qui remercie une personne d’avoir effectué une commande auprès d’une entreprise. Voyez cela comme une lettre type :
<html>
<head><title>Ordering notice</title></head>
<body>
<p>Dear {{ person_name }},</p>
<p>Thanks for placing an order from {{ company }}. It's scheduled to
ship on {{ ship_date|date:"F j, Y" }}.</p>
<p>Here are the items you've ordered:</p>
<ul>
{% for item in item_list %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% if ordered_warranty %}
<p>Your warranty information will be included in the packaging.</p>
{% endif %}
<p>Sincerely,<br />{{ company }}</p>
</body>
</html>
Ce gabarit est composé de HTML simple, comprenant quelques variables disséminées parmi des balises de gabarit. Étudions cela :
Tout le texte encadré par une paire d’accolades (par exemple, {{ person_name }}) est une variable. Cela signifie « insert la valeur de la variable portant ce nom là ». Comment préciser la valeur de ces variables ? Nous parlerons de ceci dans un instant.
Tout le texte entouré par une simple accolade et un symbole de pourcentage (par exemple, {% if ordered_warranty %}), est une balise de gabarit. La définition d’une balise est assez large : une balise indique seulement au système de gabarit de « faire quelque chose ».
Ce gabarit d’exemple contient deux balises : la balise {% for item in item_list %} (une balise for) et la balise {% if ordered_warranty %} (une balise if). Une balise for agit comme une simple instruction de boucle, vous autorisant à boucler sur chaque élément dans une séquence. Une balise if, comme vous êtes en droit de l’attendre, fonctionne comme une instruction logique « if » . Dans ce cas particulier, la balise vérifie si la valeur de la variable ordered_warranty vaut True. Si c’est le cas le système de gabarit affichera tout ce qui se trouve entre le {% if ordered_warranty %} et le {% endif %}. Sinon, le système de gabarit ne l’affichera pas. Le système de gabarit autorise aussi {% else %} et d’autres instructions logiques variées.
Pour finir, le second paragraphe de ce gabarit propose un exemple de filtre, avec lequel vous pouvez modifier l’affichage d’une variable. Dans cet exemple, {{ ship_date|date:"F j, Y" }}, nous passons la variable ship_date au filtre date, donnant au filtre date l’argument "F j, Y". Le filtre date formate les dates dans un format donné, spécifié par cet argument. Les filtres sont attachés grâce à l’utilisation du caractère tube (|), en référence au tube sous Unix (ndt : on trouve aussi la traduction « pipe », comme dans « pipeline », ou encore « branchement »).
Chaque gabarit Django a accès à divers filtres et balises embarqués, la plupart d’entre eux étant abordés dans les sections qui suivent. L’annexe A contient la liste intégrale des balises et des filtres, il est judicieux de vous familiariser avec elle de façon à en connaitre les possibilités. Il vous est aussi possible de créer vos balises et vos filtres, ce que nous couvrons au chapitre 10.
Utilisation du système de gabarit
Afin d’embarquer le système de gabarit dans votre code Python, suivez simplement ces deux étapes :
- Créez un objet Template en fournissant le modèle de code brut sous forme de chaîne de caractères. Django offre aussi un moyen de créer cet objet Template en lui indiquant le chemin vers un fichier de gabarit sur le système de fichier ; Nous examinerons cela dans un instant.
- Appelez la méthode render() de l’objet Template avec un jeu de variables donné (c’est à dire, le contexte). Ceci retourne, sous forme de chaîne, un gabarit généré dynamiquement, avec toutes les variables et les blocs étiquettés évalués selon le contexte.
Les paragraphes suivants décrivent chaque étape plus en détail.
Création d’objets Template
La manière la plus simple de créer un objet Template est de l’instancier directement. La classe Template se trouve dans le module django.template, et le constructeur prends un argument, le code brut du gabarit. Allons faire un tour du côté de l’interpréteur interactif Python pour voir comment tout ceci se traduit en terme de code.
Exemples avec l’interpréteur intéractif
Tout au long du livre, nous présentons des exemples de sessions utilisant l’interpréteur interactif Python. Vous pouvez reconnaîtres ces exemples grâce au triple symbole « supérieur à » (>>>), qui désigne l’invite de l’interpréteur. Si vous copiez les exemples du livre, ne copiez pas les symboles « supérieur à ».
Des instructions sur plusieurs lignes, dans l’interpréteur interactif, prennent la forme de trois points (...), par exemple :
>>> print """Ceci est une
... chaîne qui occupe
... trois lignes."""
Ceci est une
chaîne qui occupe
trois lignes.
>>> def ma_fonction(valeur):
... print valeur
>>> ma_fonction('Bonjour')
Bonjour
Ces trois points au début des lignes additionnelles sont insérés par le shell Python - Ils ne proviennent pas de notre saisie. Nous les incluons ici pour traduire fidèlement la sortie réelle de l’interpréteur. Si vous copiez nos exemples pour suivre le propos, ne copiez pas ces points.
À l’intérieur du répertoire de projet créé par django-admin.py startproject (comme indiqué au chapitre 2), tapez python manage.py shell pour démarrer l’interpréteur interactif (ndt : on dit aussi le shell). Voici une petite démonstration basique :
>>> from django.template import Template
>>> t = Template("My name is {{ name }}.")
>>> print t
Si vous suivez avec la console, vous verrez quelque chose comme :
<django.template.Template object at 0xb7d5f24c>
Ce 0xb7d5f24c sera à chaque fois différent, et importe peu ; c’est simplement l’«identité » Python de l’objet Template.
Paramétrages de Django
Lorsque vous utilisez Django, vous devez lui préciser quels sont les réglages à prendre en compte. C’est ce qui est fait de façon transparente lorsque vous utlisez la commande python manage.py shell typique. Vous avez d’autres options possibles décrites dans l’annexe E.
Lorsque vous créez un objet Template, le système de gabarit compile le code brut en un code interne, sous forme optimisée, prêt à être affiché. Mais si votre modèle de gabarit embarque la moindre erreur de syntaxe, l’appel à la fonction Template() lévera une exception TemplateSyntaxError :
>>> from django.template import Template
>>> t = Template('{% notatag %} ')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
...
django.template.TemplateSyntaxError: Invalid block tag: 'notatag'
Le système lève une exception TemplateSyntaxError dans les cas suivants :
- Invalid block tags — balise de bloc invalide
- Invalid arguments to valid block tags — arguments invalides pour des balises de bloc valides
- Invalid filters — filtres invalides
- Invalid arguments to valid filters — arguments invalides pour des filtres valides
- Invalid template syntax — syntaxe du gabarit invalide
- Unclosed block tags — balise de bloc ouverte (pour les balises de bloc nécessitant une balise fermante).
Afficher un Template
Une fois l’objet Template obtenu, vous pouvez lui passer des données en lui donnant un contexte. Un contexte est simplement un jeu de variables et leurs valeurs associées. Un gabarit utilise cela pour remplir ses variables balisées et évaluer ses balises de bloc.
Un contexte est représenté sous Django par la classe Context, qui se trouve dans le module django.template. Son constructeur prends un argument optionel : un dictionnaire mettant en correspondance les noms de variable avec les valeurs de variable. Appeler la méthode render() de l’objet Template avec le contexte pour «remplir » le gabarit :
>>> from django.template import Context, Template
>>> t = Template("My name is {{ name }}.")
>>> c = Context({"name": "Stephane"})
>>> t.render(c)
'My name is Stephane.'
Dictionnaires et Contextes
Un dictionnaire Python est une correspondance entre des clefs connues et des valeurs variables. Un Context est semblable à un dictionnaire, mais un Context fourni des fonctionnalités supplémentaires, abordées au Chapitre 10.
Les noms de variable doivent débuter par une lettre (A-Z ou a-z) et peuvent contenir des chiffres, des soulignements et des points (les points sont un cas à part dont nous parlerons dans un instant). Les noms de variable sont sensibles à la casse.
Voici un exemple de compilation et de rendu de gabarit, en utilisant le gabarit d’exemple du début de ce chapitre :
>>> from django.template import Template, Context
>>> raw_template = """<p>Dear {{ person_name }},</p>
...
... <p>Thanks for ordering {{ product }} from {{ company }}. It's
scheduled
... to ship on {{ ship_date|date:"F j, Y" }}.</p>
...
... {% if ordered_warranty %}
... <p>Your warranty information will be included in the
packaging.</p>
... {% endif %}
...
... <p>Sincerely,<br />{{ company }}</p>"""
>>> t = Template(raw_template)
>>> import datetime
>>> c = Context({'person_name': 'John Smith',
... 'product': 'Super Lawn Mower',
... 'company': 'Outdoor Equipment',
... 'ship_date': datetime.date(2009, 4, 2),
... 'ordered_warranty': True})
>>> t.render(c)
"<p>Dear John Smith,</p>\n\n<p>Thanks for ordering Super Lawn Mower
from
Outdoor Equipment. It's scheduled \nto ship on April 2,
2009.</p>\n\n\n
<p>Your warranty information will be included in the
packaging.</p>\n\n\n
<p>Sincerely,<br />Outdoor Equipment</p>"
Étudions pas à pas les instructions de ce code :
Tout d’abord, nous importons les classes Template et Context, qui toutes deux résident dans le module django.template.
Nous affectons le texte brut de notre gabarit à la variable raw_template. Remarquez que nous utilisons le triple guillemet double (“”“) pour désigner la chaîne, car elle court sur plusieurs lignes ; en Python, les chaînes encadrées par un seul guillemet ne peuvent être divisées sur plusieurs lignes.
Ensuite, nous créons un objet template, t, en passant raw_template à la classe constructeur Template.
Nous importons le module datetime depuis la bibliothèque standard Python, parce que nous en aurons besoin dans les instructions suivantes.
Puis nous créons un objet Context, c. Le contructeur de Context prend en paramètre un dictionnaire Python, qui assure la correspondance entre les noms et les valeurs des variables. Ici, par exemple, nous spécifions que la person_name est 'John Smith', que product est 'Super Lawn Mower', et ainsi de suite.
Finalement, nous appelons la méthode render() sur notre objet template, en lui précisant le contexte. Ceci retourne le rendu du gabarit - autrement dit, il remplace les variables du gabarit par les valeurs actuelles de ces variables, puis il execute tous les blocs balisés.
Notez que le paragraphe « warranty » a été affiché parce que la variable ordered_warranty était placée à True. Notez aussi la date, April 2, 2009, qui s’affiche selon la chaîne de formattage 'F j, Y' (Nous expliquerons les chaînes de formatage pour le filtre date bientôt).
Voici les fondamentaux de l’utilisation du système de gabarit de Django : écrivez simplement un gabarit, créez un objet Template, créez un Context, et appellez la méthode render()
Plusieurs contextes, un seul gabarit
Une fois que vous avez un objet Template, vous pouvez l’utiliser selon plusieurs contextes, par exemple :
>>> from django.template import Template, Context
>>> t = Template('Hello, {{ name }}')
>>> print t.render(Context({'name': 'John'}))
Hello, John
>>> print t.render(Context({'name': 'Julie'}))
Hello, Julie
>>> print t.render(Context({'name': 'Pat'}))
Hello, Pat
Tant que vous utilisez la même source de gabarit pour afficher plusieurs contextes, il est plus efficace de créer l’objet Template une seule fois, pour appeler ensuite render() plusieurs fois :
# Bad
for name in ('John', 'Julie', 'Pat'):
t = Template('Hello, {{ name }}')
print t.render(Context({'name': name}))
# Good
t = Template('Hello, {{ name }}')
for name in ('John', 'Julie', 'Pat'):
print t.render(Context({'name': name}))
Le parcours des gabarits par Django est assez rapide. En arrière plan, la plupart de l’analyse se fait via un appel unique d’une courte expression rationnelle.
Ceci est un contraste frappant avec les moteurs de gabarit reposant sur le XML, qui supportent les temps de traitement d’un analyseur XML et ont tendance à être largement plus lents que le modèle de rendu propre à Django.
Recherche de variable contextuelle
Jusqu’ici, dans les exemples, nous avons transmis de simples valeurs au contexte - principalement des chaînes, plus un exemple datetime.date. Cependant, le système de gabarit prends élégament en charge des structures de données plus complexes, tels que les listes, les dictionnaires, et les objets personnalisés.
La clef pour traverser des structures de données complexes dans les gabarits de Django est le symbole point (.). Utilisez un point pour avoir accès aux clefs des dictionnaires, aux attributs, aux indices ou aux méthodes d’un objet.
Ceci est plus parlant avec quelques exemples. Ainsi, supposons que vous passez un dictionnaire Python à un gabarit. Pour accéder aux valeurs de ce dictionnaire par l’intermédiaire de ses clefs, utilisez un point :
>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
'Sally is 43 years old.'
De façon similaire, les points permettent aussi l’accès aux attributs des objets. Par exemple, un objet Python datetime.date a des attributs year, month, et day, que vous pouvez atteindre dans un gabarit Django en utilisant le point :
>>> from django.template import Template, Context
>>> import datetime
>>> d = datetime.date(1993, 5, 2)
>>> d.year
1993
>>> d.month
5
>>> d.day
2
>>> t = Template('The month is {{ date.month }} and the year is {{
date.year }}.')
>>> c = Context({'date': d})
>>> t.render(c)
'The month is 5 and the year is 1993.'
Cet exemple utilise une classe personnalisée :
>>> from django.template import Template, Context
>>> class Person(object):
... def __init__(self, first_name, last_name):
... self.first_name, self.last_name = first_name, last_name
>>> t = Template('Hello, {{ person.first_name }} {{ person.last_name
}}.')
>>> c = Context({'person': Person('John', 'Smith')})
>>> t.render(c)
'Hello, John Smith.'
Les points s’utilisent aussi pour appeler les méthodes des objets. Par exemple, chaque chaîne en Python possèdent les méthodes upper() et isdigit(), que vous pouvez appeler dans les gabarits Django en utilisant la même syntaxe pointée.
>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var': 'hello'}))
'hello -- HELLO -- False'
>>> t.render(Context({'var': '123'}))
'123 -- 123 -- True'
Notez que vous n’avez pas à inclure les parenthèses dans les appels de méthode. Il n’est pas non plus possible de passer des arguments aux méthodes ; vous pouvez seulement appeler des méthodes qui ne nécessitent pas d’argument (nous expliquerons cette philosophie plus tard dans ce chapitre).
Finalement, les points sont aussi utilisés pour accéder aux indices des listes, par exemple :
>>> from django.template import Template, Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apples', 'bananas', 'carrots']})
>>> t.render(c)
'Item 2 is carrots.'
Les indices de liste négatifs ne sont pas permis. Par exemple, la variable de gabarit {{ items.-1 }} causera une erreur TemplateSyntaxError.
Les listes Python
Les listes Python proposent des indices à partir de 0, le premier élément est donc à l’indice 0, le second à l’indice 1, et ainsi de suite.
La recherche par point peut être résumée comme ceci : lorsque le système de gabarit rencontre un point dans un nom de variable, il essai les recherches suivantes, dans cet ordre :
- Dictionaire (par exemple, foo["bar"])
- Attribut de recherche (par exemple, foo.bar)
- Appel de méthode (par exemple, foo.bar())
- Liste de recherche indexée (par exemple, foo[bar])
Le système utilise la première recherche qui fonctionne. C’est la logique du court-circuit.
La recherche pointée peut s’effectuer à plusieurs niveaux de profondeur. Par exemple, le code à suivre utilise {{ person.name.upper }}, traduit en une recherche sur le dictionnaire (person['name']) puis en appel de méthode (upper()) :
>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name.upper }} is {{ person.age }} years
old.')
>>> c = Context({'person': person})
>>> t.render(c)
'SALLY is 43 years old.'
Comportement de l’appel de méthode
Les appels de méthode sont légérement plus complexes que les autres types de recherche. Voici quelques indications qu’il faut garder à l’esprit :
Si, pendant la recherche de méthode, une méthode lève une exception, celle-ci sera propagée, à moins que l’expression possède un attribut silent_variable_failure dont la valeur est True. Si l’exception possède un attribut silent_variable_failure, la variable rendue sera une chaîne vide, par exemple :
>>> t = Template("My name is {{ person.first_name }}.") >>> class PersonClass3: ... def first_name(self): ... raise AssertionError, "foo" >>> p = PersonClass3() >>> t.render(Context({"person": p})) Traceback (most recent call last): ... AssertionError: foo >>> class SilentAssertionError(AssertionError): ... silent_variable_failure = True >>> class PersonClass4: ... def first_name(self): ... raise SilentAssertionError >>> p = PersonClass4() >>> t.render(Context({"person": p})) "My name is ."Un appel de méthode fonctionnera uniquement si la méthode n’a pas d’argument requis. Sinon, le système se déplacera au prochain type de recherche (recherche de liste indexée).
Évidemment, quelques méthodes ont des effets de bord, et ce serait au mieux inconscient, au pire une faille de sécurité, que d’autoriser le système de gabarit à y avoir accès.
Admettons que, pour l’exemple, vous ayer un objet BankAccount qui possède une méthode delete(). Un gabarit ne doit pas être autorisé à inclure quelque chose du genre {{ account.delete }}.
Pour prévenir cela, ajoutez l’attribut de fonction alters_data à la méthode.
def delete(self): # Delete the account delete.alters_data = True
Le système de gabarit n’exécutera pas les méthodes marquées de la sorte par cet appel de méthode. Autrement dit, si un gabarit comporte {{ account.delete }}, cette balise n’exécutera pas la méthode delete(). Elle échouera silencieusement.
Comment sont gérées les variables invalides ?
Par défaut, si une variable n’existe pas, le système de gabarit la renvoie sous forme de chaîne vide, échouant silencieusement, comme dans l’exemple suivant :
>>> from django.template import Template, Context
>>> t = Template('Your name is {{ name }}.')
>>> t.render(Context())
'Your name is .'
>>> t.render(Context({'var': 'hello'}))
'Your name is .'
>>> t.render(Context({'NAME': 'hello'}))
'Your name is .'
>>> t.render(Context({'Name': 'hello'}))
'Your name is .'
Le système échoue en silence plutôt que de lever une exception car il est censé être résistant à l’erreur humaine. Dans ce cas, toutes les recherches échouent car les noms de variables n’ont pas la bonne casse ou le nom correct. Dans le monde réel, il est inacceptable pour un site web de devenir inaccessible à cause d’une petite erreur de syntaxe dans le gabarit.
Notez qu’il est possible de changer le comportement par défaut de Django à cet égard, en modifiant un paramètre dans votre configuration de Django. Nous détaillerons cela au chapitre 10.
Jouer avec les objets Context
La plupart du temps, vous instancierez des objets Context en passant un dictionnaire complet à la fonction Context(). Vous pouvez ajouter ou enlever les éléments d’un objet Context lorsqu’il est instancié, en utilisant la syntaxe standard des dictionnaires Python :
>>> from django.template import Context
>>> c = Context({"foo": "bar"})
>>> c['foo']
'bar'
>>> del c['foo']
>>> c['foo']
''
>>> c['newvariable'] = 'hello'
>>> c['newvariable']
'hello'
Étiquettes de gabarit et filtres simples
Comme nous l’avons mentionné auparavant, le système de gabarit est livré avec des filtres et des balises prêts à l’usage. Le paragraphe qui suit se propose d’explorer les balises et les filtres les plus courants.
Balises
if/else
(c’est à dire qu’il existe, qu’il n’est pas vide et que ce ne soit pas une valeur Booléenne fausse), les système affichera tout ce qui se trouve entre {% if %} et {% endif %}, par exemple :
{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% endif %}
Une balise {% else %} est optionnelle :
{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% else %}
<p>Get back to work.</p>
{% endif %}
La véracité dans Python
En Python, la liste vide ([]), le tuple (()), le dictionaire ({}), la chaîne de caractère (''), le zero (0), et l’objet spécial None sont False, faux, dans un contexte Booléen. Tout le reste est True, vrai.
La balise {% if %} accepte and, or, ou not pour tester des variables multiples, ou pour inverser une variable donnée. Par exemple :
{% if athlete_list and coach_list %}
Both athletes and coaches are available.
{% endif %}
{% if not athlete_list %}
There are no athletes.
{% endif %}
{% if athlete_list or coach_list %}
There are some athletes or some coaches.
{% endif %}
{% if not athlete_list or coach_list %}
There are no athletes or there are some coaches. (OK, so
writing English translations of Boolean logic sounds
stupid; it's not our fault.)
{% endif %}
{% if athlete_list and not coach_list %}
There are some athletes and absolutely no coaches.
{% endif %}
La balise {% if %} n’autorise pas les clauses and et or à l’intérieur d’une même balise, car l’ordre logique pourrait être alors ambigu. Par exemple, ceci n’est pas valide :
{% if athlete_list and coach_list or cheerleader_list %}
L’utilisation des parenthèses pour contrôler l’ordre des opérations n’est pas supporté. Si vous êtes dans le cas où vous avez besoin de ces parenthèses, pensez à placer la logique dans la vue de façon à simplifier le gabarit. Et même si vous avez besoin de combiner and et or pour faire de la logique avancée, utilisez simplement des balises {% if %} imbriquées, comme par exemple :
{% if athlete_list %}
{% if coach_list or cheerleader_list %}
We have athletes, and either coaches or cheerleaders!
{% endif %}
{% endif %}
Les usages multiples d’un même opérateur logique sont possibles, mais vous ne pouvez combiner des opérateurs différents. Par exemple, ceci est valide :
{% if athlete_list or coach_list or parent_list or teacher_list %}
Il n’y a pas de balise {% elif %}. Utilisez des balises {% if %} imbriquées pour accomplir la même chose :
{% if athlete_list %}
<p>Here are the athletes: {{ athlete_list }}.</p>
{% else %}
<p>No athletes are available.</p>
{% if coach_list %}
<p>Here are the coaches: {{ coach_list }}.</p>
{% endif %}
{% endif %}
Assurez vous de fermer chaque {% if %} par un {% endif %}. Sinon, Django lévera une TemplateSyntaxError.
for
La balise {% for %} vous permet de boucler sur chacun des éléments dans une séquence. Comme l’instruction for en Python, la syntaxe est for X in Y, où Y est la séquence sur laquel nous bouclons et X est le nom de la variable à utiliser pour un cycle particulier de la boucle. À chaque tour de boucle, le système de gabarit interprétera tout ce qui se trouve entre {% for %} et {% endfor %}.
Par exemple, vous pouvez utiliser ce qui suit pour afficher une liste d’athlètes à partir d’une variable athlete_list :
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
Ajoutez reversed à la balise pour boucler à rebour :
{% for athlete in athlete_list reversed %}
...
{% endfor %}
Il est possible d’imbriquer les balises {% for %} :
{% for country in countries %}
<h1>{{ country.name }}</h1>
<ul>
{% for city in country.city_list %}
<li>{{ city }}</li>
{% endfor %}
</ul>
{% endfor %}
Il n’y a rien de prévu pour sortir d’une boucle avant qu’elle soit terminée. Si vous voulez faire ceci, modifiez la variable sur laquelle vous bouclez afin d’inclure uniquement les valeurs sur lesquelles vous voulez boucler. De la même façon, il n’y a pas de support pour l’instruction « continue » qui indiquerait à la boucle de retourner immédiatement au début d’elle même (consultez le paragraphe «Philosophies et limitations » un peu plus loin dans ce chapitre pour connaître la raison de cette décision architecturale).
La balise {% for %} place une variable de gabarit magique, forloop, à l’intérieure de la boucle. Cette variable a quelques attributs qui vous informent sur la progression de la boucle :
forloop.counter est toujours un entier représentant le nombre de fois où la boucle à été parcourue. Elle commence à un, le premier passage de boucle initialise donc forloop.counter à 1. Voici un exemple :
{% for item in todo_list %} <p>{{ forloop.counter }}: {{ item }}</p> {% endfor %}forloop.counter0 est similaire à forloop.counter, sauf qu’elle commence à zero. Sa valeur sera initialisée à 0 lors du premier passage dans la boucle.
forloop.revcounter est toujours initialisée à la valeur d’un entier représentant le nombre d’éléments restant dans la boucle. Lors du premier passage de boucle, forloop.revcounter sera égale au nombre total d’éléments dans la séquence parcourue. Le dernier passage de boucle, forloop.revcounter aura pour valeur 1.
forloop.revcounter0 est semblable à forloop.revcounter, hormis sont indexation à zéro. Le premier passage de boucle, forloop.revcounter0 sera initialisée à la valeur du nombre d’éléments dans la séquence, moins 1. Au dernier passage de boucle, elle vaudra 0.
forloop.first est une valeur Booléenne initialisée à True si c’est la première fois que l’on traverse la boucle. C’est pratique pour les cas spéciaux :
{% for object in objects %} {% if forloop.first %}<li class="first">{% else %}<li>{% endif %} {{ object }} </li> {% endfor %}forloop.last est une valeur Booléenne initialisée à True si c’est la dernière fois que l’on traverse la boucle. Un usage courant de cela est l’utilisation du charactère « pipe » entre une liste de liens :
{% for link in links %}{{ link }}{% if not forloop.last %} | {% endif %}{% endfor %} The above template code might output something like this: Link1 | Link2 | Link3 | Link4forloop.parentloop est une référence à l’objet forloop pour la boucle parente, dans le cas de boucles imbriquées. Voici un exemple :
{% for country in countries %} <table> {% for city in country.city_list %} <tr> <td>Country #{{ forloop.parentloop.counter }}</td> <td>City #{{ forloop.counter }}</td> <td>{{ city }}</td> </tr> {% endfor %} </table> {% endfor %}
La variable magique forloop est disponible uniquement à l’intérieure d’une boucle. Après que l’explorateur de gabarit ait atteint {% endfor %}, forloop disparaît.
Context et la variable forloop
À l’intérieur du bloc {% for %}, les variables existantes sont déplacées afin d’éviter d’écraser la variable magique forloop. Django met à disposition ce contexte mouvant dans forloop.parentloop. Vous n’avez généralement pas à vous soucier de cela, mais si vous utilisez une variable nommée forloop (quoique nous ne vous le conseillons pas), elle sera appelée forloop.parentloop une fois à l’intérieure du bloc {% for %}.
ifequal/ifnotequal
Le système de modèles de Django n’est délibérément pas un véritable langage de programmation, et ne vous permet donc pas d’exécuter des commandes Python arbitraires (Plus de détails à ce propos dans le paragraphe « Philosophies et Limitations »). Cependant, puisqu’il est courant dans un gabarit d’avoir à comparer deux valeurs et d’afficher quelque chose si elles sont égales, Django fournit la balise {% ifequal %} pour cet usage.
La balise {% ifequal %} compare deux valeurs et affiche tout ce qui est entre {% ifequal %} et {% endifequal %} si les valeurs sont égales.
Cet exemple compare les variables de gabarit user et currentuser :
{% ifequal user currentuser %}
<h1>Welcome!</h1>
{% endifequal %}
Les arguments peuvent être des chaînes codées en dur, avec des simple ou double guillemets, le code suivant est donc valide :
{% ifequal section 'sitenews' %}
<h1>Site News</h1>
{% endifequal %}
{% ifequal section "community" %}
<h1>Community</h1>
{% endifequal %}
Tout comme {% if %}, la balise {% ifequal %} supporte l’optionnel {% else %} :
{% ifequal section 'sitenews' %}
<h1>Site News</h1>
{% else %}
<h1>No News Here</h1>
{% endifequal %}
Seuls les variables de gabarit, les chaînes, les entiers et les nombres décimaux sont autorisés comme arguments de {% ifequal %}. Voici des exemples corrects :
{% ifequal variable 1 %}
{% ifequal variable 1.23 %}
{% ifequal variable 'foo' %}
{% ifequal variable "foo" %}
Tous les autres types de variables, comme les dictionnaires Python, les listes ou les Booléens, ne peuvent être codés en dur dans {% ifequal %}. Voici des exemples corrects :
{% ifequal variable True %}
{% ifequal variable [1, 2, 3] %}
{% ifequal variable {'key': 'value'} %}
Si vous devez tester si quelque chose est vrai ou faux, utilisez la balise {% if %} à la place de {% ifequal %}.
Commentaires
Tout comme dans le HTML ou dans un langage de programmation tel que Python, le langage de gabarit de Django autorise les commentaires. Pour commenter quelque chose, utilisez {# #} :
{# Ceci est un commentaire #}
Le commentaire ne s’affichera pas lors de l’affichage du gabarit.
Un commentaire ne peut s’étendre sur plusieurs lignes. Cette limitation améliore les performances lors de l’analyse du gabarit. Dans le gabarit suivant, le rendu à l’affichage ressemblera exactement au modèle (c’est à dire que la balise ne sera pas analysée comme étant un commentaire) :
Ceci est {# ceci n'est pas
un commentaire #}
test.
Filtres
Comme expliqué précédemment dans ce chapitre, les filtres de gabarits sont une façon simple de modifier la valeur des variables avant qu’elles ne soient affichées. Les filtres ressemblent à cela :
{{ name|lower }}
Ceci affiche la valeur de la variable {{ name }} après avoir été traitée par le filtre lower, qui converti le texte en bas de casse. Utilisez un branchement (|) pour appliquer un filtre.
Les filtres peuvent être chaînés — autrement dit, la sortie d’un filtre est envoyée au suivant. Voici une recette courante pour échapper le contenu textuel, puis convertir les sauts de lignes en balises <p> :
{{ mon_texte|escape|linebreaks }}
Certains filtres prennent des arguments. Un argument de filtre ressemble à ceci :
{{ bio|truncatewords:"30" }}
Ceci affiche les 30 premiers mots de la variable bio. Les arguments de filtres sont toujours entre guillemets double. Voici quelques uns des filtres les plus importants ; L’annexe F couvre le reste.
addslashes : ajoute une barre oblique inverse avant toute barre oblique, guillemet simple ou double. Ceci est pratique si le texte produit est inclu dans une chaîne Javascript.
date : formate un objet date ou datetime selon le motif d’une chaîne de formatage donnée en paramètre, par exemple :
{{ pub_date|date:"F j, Y" }}Les chaînes de formatage sont détaillées dans l’annexe F.
escape : encode tous les guillemets, les esperluettes, les symboles « supérieur à » et « inférieur à », dans la chaîne donnée. Ceci est pratique pour nettoyer les données soumises par les utilisateurs et pour s’assurer qu’elles sont en XML ou XHTML valide. Plus précisemment, escape fait les conversions suivantes :
- Converti & en &
- Converti < en <
- Converti > en >
- Converti " (guillemet double) en "
- Converti ' (guillemet simple) en '
length : retourne la taille de la valeur. Vous pouvez l’utiliser sur une liste ou une chaîne, ou n’importe quel objet Python qui sait comment determiner sa taille (c’est à dire tout objet qui comporte un méthode __len__()).
Philosophies et limitations
Maintenant que vous avez une idée du langage de gabarit propre à Django, nous devons vous indiquer quelques unes de ses limitations volontaires, ainsi que certaines philosophies expliquant pourquoi tout ceci fonctionne ainsi.
Plus que tout autre composant des applications web, les opinions de programmeur au sujet du système de gabarit varient largement. Le simple fait que Python à lui seul possède des douzaines, sinon des centaines, d’implémentation open source de langage de gabarit, illustre ce point. Chacun a probablement été créé parce que son développeur trouvait que les langages de gabarit existants étaient insuffisants. (en fait, on dit qu’il s’agit d’un rituel initiatique pour un développeur Python que d’écrire sont propre langage de gabarit ! Si vous ne l’avez pas déjà fait, envisagez le. C’est un exercice amusant).
Avec ceci en tête, peut-être aimerez vous savoir que Django n’impose pas que vous utilisiez son langage de gabarit. Parce que Django se destine à être un framework web complet qui fourni toutes les pièces nécessaires à la productivité des développeurs, il est souvent plus pratique d’utiliser le système de gabarit de Django plutôt que d’autre bibliothèques de gabarit Python, mais ce n’est en aucune façon un pré-requis. Comme vous le verrez dans la section — Utiliser des gabarits dans les vues — à venir, il est très facile d’utiliser un autre langage de gabarit avec Django.
Bien sûr, nous avons une forte préférence pour la façon dont le langage de gabarit sous Django fonctionne. Le système de gabarit à ses racines dans la manière dont est fait le développement web à World Online et dans l’expérience combinée des créateurs de Django. Voici quelques unes de ces philosophies :
La logique métier doit être séparée de la logique de présentation. Nous voyons un système de gabarit comme un outil qui contrôle la présentation et la logique reliée à cette présentation — un point c’est tout. Le système de gabarit ne doit pas supporter des fonctionnalités allant au-delà de cet objectif de base.
Pour cette raison, il est impossible d’appeler du code Python directement depuis un gabarit Django. Toute la « programmation » est fondamentalement limitée à la portée de ce que les balises de gabarit peuvent faire. Il est possible d’écrire des balises de gabarit personnalisées qui fassent des choses arbitraires, mais à priori, les balises de gabarit ne permettent pas, intentionnellement, l’exécution de code Python abitraire.
La syntaxe doit être séparée du HTML/XML. Bien que le système de gabarit de Django soit avant tout utilisé pour produire du HTML, il est conçu pour être tout aussi utilisable pour les formats non-HTML, tel que le texte brut. D’autres langages de gabarit se basent sur XML, plaçant toute la logique de gabarit à l’intérieur des balises ou des attributs XML, mais Django évite délibérément cette limitation.
Se baser sur du XML valide pour écrire des gabarits introduit tout un monde d’erreurs humaines et de messages d’erreurs difficiles à comprendre, alors qu’ utiliser un parseur XML pour parcourir les gabarits engendre un coût inacceptable dans le traitement des gabarits.
les graphistes sont censés être familiés du code HTML. Le système de gabarits n’est pas conçu de façon à ce que le gabarit s’affiche parfaitement dans les éditeurs WYSIWYG tel que Dreamweaver. C’est une limitation trop sévère et cela ne permettrait pas à la syntaxe d’être aussi concise qu’aujourd’hui. Django attends des auteurs de gabarits qu’ils soient à l’aise avec l’édition de HTML en direct.
Les graphistes ne sont pas censés être des programmeurs Python. Les auteurs du système de gabarit reconnaissent que les modèles de page web sont le plus souvent écrits par des designers, pas par des programmeurs, et par conséquent ne devraient pas présumer des connaissances en Python.
Cependant, le système a également l’intention d’accueillir de petites équipes dans lesquelles les modèles sont créés par des programmeurs Python. Il propose un moyen d’étendre la syntaxe du système en écrivant du code Python brut. (Plus de détails sur le sujet au chapitre 10).
L’objectif n’est pas d’inventer un language de programmation. L’objectif est d’offrir juste assez de fonctionnalités « programmatiques », tels que les imbrications ou les boucles, qui sont essentiels pour les choix relatifs à la présentation.
En conséquence de ces partis pris, le langage de gabarit de Django présente les limitations suivantes :
- Un gabarit ne peut pas définir une variable ou changer la valeur d’une variable. Il est possible d’écrire des balises de gabarit personnalisées qui accomplissent ces objectifs (voir le chapitre 10), mais le jeu de balises de gabarit par défaut sous Django ne l’autorise pas.
- Un gabarit ne peut pas appeler du code Python brut. Il n’est pas possible de « basculer en mode Python » ou d’utiliser des structures Python brutes. Encore une fois, il est possible d’écrire des balises de gabarit personnalisées pour faire cela, mais les balises de bases fournies par Django ne l’autorisent pas.
Utilisation des gabarits dans les vues
Vous avez appris les bases de l’utilisation du système de gabarit ; Utilisons à présent ces connaissances pour créer une vue. Souvenez-vous de la vue current_datetime dans monsite.views, que nous avons commencé au chapitre précédent. Voici à quoi elle ressemble :
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
Modifions cette vue afin d’utiliser le système de gabarit de Django. À première vue, vous pourriez penser à faire quelque chose comme ceci :
from django.template import Template, Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
t = Template("<html><body>It is now {{ current_date
}}.</body></html>")
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
Évidemment, on utilise le système de gabarit, mais cela ne résoud pas le problème que nous avons mis en valeur dans l’introduction de ce chapitre.
Ici, le gabarit est toujours embarqué dans le code Python. Arrangeons cela en plaçant le gabarit dans un fichier séparé, que cette vue chargera.
Vous pourriez commencer par enregistrer votre gabarit quelque part sur votre système de fichier et utiliser les fonctionnalités d’ouverture de fichier proposées par Python pour lire le contenu du gabarit. Voici à quoi cela pourrait ressembler, en considérant que le gabarit soit enregistré sous /home/djangouser/templates/mytemplate.html :
from django.template import Template, Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
# Simple way of using templates from the filesystem.
# This doesn't account for missing files!
fp = open('/home/djangouser/templates/mytemplate.html')
t = Template(fp.read())
fp.close()
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
Cette approche, cependant, n’est pas élégante, pour plusieurs raisons :
- Elle ne gère pas le cas d’un fichier manquant. Si le fichier mytemplate.html n’existe pas ou n’est pas lisible, l’appel à open() lévera une exception IOError.
- Elle code en dur l’emplacement de votre gabarit. Si vous devez utiliser cette technique pour chaque fonction view, vous allez devoir répéter les emplacements de gabarit. Sans parler de l’importante saisie que cela implique !
- Elle embarque beaucoup de code fonctionnel ennuyeux. Vous avez mieux à faire que d’écrire des appels à open(), fp.read() et fp.close() à chaque fois que vous chargez un gabarit.
Pour résoudre ces problèmes, nous allons utiliser le chargement de gabarit et les répertoires de gabarits, tous deux étant décrits dans les sections qui suivent.
Chargement de gabarit
Django fournit une API pratique et puissante pour charger les gabarits depuis le disque, dans le but de supprimer à la fois les redondances dans vos appels au chargement de gabarit et dans vos gabarits eux mêmes.
Afin d’utiliser cette API de chargement de gabarit, vous devrez tout d’abord indiquer au framework l’emplacement où vous stockez vos gabarits. L’endroit tout indiqué pour ce faire se trouve dans votre fichier de configuration.
Un fichier de configuration Django est l’endroit où placer la configuration de votre instance Django (c’est à dire votre projet Django). C’est un simple module Python avec divers niveaux de variables, un pour chaque paramétrage.
Lorsque vous avez lancé la commande django-admin.py startproject mysite au chapitre 2, le script à créé pour vous un fichier de configuration par defaut, appelé settings.py. Jetez un oeil au contenu de ce fichier. Il contient des variables qui ressemblent à ceci (pas nécessairement dans cet ordre) :
DEBUG = True TIME_ZONE = 'America/Chicago' USE_I18N = True ROOT_URLCONF = 'mysite.urls'
C’est assez explicite ; les paramètres et leurs valeurs respectives sont de simple variables Python. Puisque le fichier de configuration est un simple module Python, vous pouvez faire des choses dynamiques comme par exemple vérifier la valeur d’une variable avant d’en paramétrer une autre. (Ceci signifie aussi que vous devez éviter les erreurs de syntaxe Python dans votre fichier de configuration).
Nous approfondirons les fichiers de configuration à l’annexe E, mais pour l’instant, examinons le paramètre TEMPLATE_DIRS. Ce paramètre indique au mécanisme de chargement de gabarit où rechercher ces gabarits. Par défaut, c’est un tuple vide. Choisissez un répertoire où vous aimeriez sauvegarder vos gabarits et ajoutez le à TEMPLATE_DIRS, comme ceci :
TEMPLATE_DIRS = (
'/home/django/mysite/templates',
)
Il reste quelques petites choses à noter :
Vous pouvez choisir le répertoire que vous voulez, cependant le répertoire et les gabarits qu’il contient doivent être lisibles par le compte utilisateur sous lequel tourne le serveur web. Si vous n’avez aucune idée d’un emplacement approprié pour placer vos gabarits, nous vous recommendons de créer un répertoire templates à l’intérieur de votre projet Django (autrement dit, le dossier monsite que vous avez créé au chapitre 2, si vous avez suivi jusqu’ici les exemples du livre).
N’oubliez pas la virgule à la fin de la chaine de caractère précisant le dossier de gabarit ! Python à besoin d’une virgule lorsqu’il s’agit d’un unique élément afin de différencier le tuple d’une simple expression entre parenthèse. C’est une erreur courante chez les débutants.
Si vous voulez éviter ce genre d’erreur, vous pouvez faire de TEMPLATE_DIRS une liste à la place d’un tuple, puisque les listes à un seul élément ne nécessitent pas de virgule finale :
TEMPLATE_DIRS = [ '/home/django/mysite/templates' ]Un tuple étant légérement plus correcte, d’un point de vue sémantique, qu’une liste (les tuples ne peuvent être modifiés après leur création, et rien ne devrait pouvoir changer les paramètres une fois qu’ils ont été lus), nous vous recommandons d’utiliser un tuple pour votre paramètre TEMPLATE_DIRS.
Si vous êtes sous Windows, ajoutez la lettre de votre disque et utilisez la barre oblique (« slash ») propre au style Unix, plutôt que la barre oblique inverse (« anti-slash »), comme ceci :
TEMPLATE_DIRS = ( 'C:/www/django/templates', )Il est plus simple d’utiliser les chemins absolus (c’est à dire, les chemins de répertoire en partant de la racine du système de fichier). Si vous voulez être un peu plus flexible et permettre le découplage, vous pouvez profiter du fait que les fichiers de configuration de Django ne sont rien d’autre que du code Python pour construire le contenu de TEMPLATE_DIRS de façon dynamique, par exemple avec :
import os.path TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'), )Cet exemple utilise la variable magique de Python, __file__, qui est automatiquement remplacée par le nom du fichier de module Python dans lequel le code réside.
Une fois TEMPLATE_DIRS paramétrée, la prochaine étape est de changer le code de la vue de façon à utiliser les fonctionnalités de chargement de gabarits de Django plutôt que de coder en dur le chemin des gabarits. Retournons à notre vue current_datetime, et modifions la de façon à obtenir :
from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
t = get_template('current_datetime.html')
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
Dans cet exemple, nous utilisons la fonction django.template.loader.get_template() au lieu de charger le gabarit manuellement depuis le système de fichier. La fonction get_template() prends le nom d’un gabarit en argument, détermine l’emplacement du gabarit sur le système de fichier, ouvre ce fichier, et retourne un objet Template compilé.
Si get_template() ne peut trouver le gabarit du même nom, elle lève une exception TemplateDoesNotExist. Pour voir à quoi cela ressemble, démarrez à nouveau le serveur de développement de Django, comme au chapitre 3, en lançant python manage.py runserver à l’intérieur du répertoire de votre projet Django. Ensuite, pointez votre navigateur sur la page qui active la vue current_datetime (c’est à dire http://127.0.0.1:8000/time/). En admettant que votre paramètre DEBUG soit configuré à True et que vous n’avez pas encore créé un gabarit current_datetime.html, vous devriez voir une page d’erreur Django mettant en valeur l’erreur TemplateDoesNotExist.
Figure 4-1 : La page d’erreur affichée lorsque qu’un gabarit ne peut être trouvé.
Cette page d’erreur est similaire à celle détaillée au chapitre 3, offrant un complément d’information de débuggage : une section Template-loader postmortem. Cette section vous indique quel gabarit Django a tenté de charger, puis la raison de l’échec de chacune des tentatives (par exemple, File does not exist). Cette information est inestimable lorsque vous tentez de débuguer les erreurs de chargement de gabarit.
Comme vous l’avez probablement remarqué dans le message de la figure 4-1, Django a essayé de trouver le gabarit en combinant le répertoire configuré dans TEMPLATE_DIRS avec le nom du gabarit transmis à get_template(). Dès lors, si votre TEMPLATE_DIRS contient '/home/django/templates', Django cherche le fichier '/home/django/templates/current_datetime.html'. Si TEMPLATE_DIRS contient plus d’un répertoire, chacun d’entre eux est vérifés jusqu’à ce que le gabarit soit trouvé ou jusqu’à ce qu’ils aient tous été explorés.
Pour aller plus loin, créez le fichier current_datetime.html à l’intérieur de votre répertoire de gabarits en utilisant le code suivant :
<html><body>It is now {{ current_date }}.</body></html>
Rafraîchissez la page dans votre navigateur, et vous devriez voir le rendu complet de la page.
render_to_response()
Puisque charger un gabarit, préciser un Context, et retourner un objet HttpResponse avec le résultat d’un rendu de gabarit est une chose très courante, Django fourni un raccourci qui vous permet de faire tout cela en une seule ligne de code. Ce raccourci est une fonction nommée render_to_response(), qui réside dans le module django.shortcuts. La plupart du temps, vous utiliserez render_to_response() au lieu de charger des gabarits et de créer des objets Context et HttpResponse manuellement.
Voici l’exemple current_datetime réécrit afin d’utiliser render_to_response() :
from django.shortcuts import render_to_response
import datetime
def current_datetime(request):
now = datetime.datetime.now()
return render_to_response('current_datetime.html',
{'current_date': now})
Quelle différence ! Explorons les changements de code :
- Nous n’avons plus à importer get_template, Template, Context, ou HttpResponse. À la place, nous importons django.shortcuts.render_to_response. Seul import datetime reste.
- À l’intérieur de la fonction current_datetime, nous continuons à calculer now, mais le chargement du gabarit, la création du contexte, le rendu du gabarit et la création HttpResponse sont tous pris en charge par l’appel de render_to_response(). Puisque render_to_response() retourne un objet HttpResponse, nous pouvons simplement retourner cette valeur dans la vue.
Le premier argument de render_to_response() doit être le nom du gabarit à utiliser. Le second, si précisé, doit être un dictionnaire à utiliser pour la création d’un Context pour ce gabarit. Si vous ne fournissez pas un deuxième argument, render_to_response() utilisera un dictionnaire vide.
L’astuce locals()
Considérons notre dernière version de current_datetime :
def current_datetime(request):
now = datetime.datetime.now()
return render_to_response('current_datetime.html',
{'current_date': now})
Très souvent, comme dans cet exemple, vous vous retrouverez à calculer des valeurs, à les stocker dans des variables (par exemple, now dans le code précédent), puis à envoyer ces variables au gabarit. Les programmeurs particulièrement fainéants devraient noter qu’il est légérement redondant d’avoir à préciser les noms de variables temporaires et de devoir donner des noms au variables de gabarit. Ce n’est pas seulement redondant, c’est aussi de la saisie supplémentaire.
Si vous êtes l’un de ces programmeurs fainéants et que vous aimez garder votre code particulièrement concis, vous pouvez profiter d’une fonction Python embarquée, appelée locals(). Elle retourne un dictionnaire mettant en correspondance tous les noms de variable locale et leur valeur. Ainsi, la vue précédente peut être réécrite de cette façon :
def current_datetime(request):
current_date = datetime.datetime.now()
return render_to_response('current_datetime.html', locals())
Ici, à place de préciser manuellement le dictionnaire de contexte comme précédemment, nous passons la valeur de locals(), qui incluera toutes les variables définies au moment de l’exécution de la fonction. En conséquence, nous avons renommé la variable now en current_date, parcequ’il s’agit du nom de variable qu’attend le gabarit. Dans cet exemple, locals() n’offre pas de grosse amélioration, mais cette technique peut vous épargner de la saisie si vous avez beaucoup de variables de gabarit à définir, ou si vous êtes particulièrement fainéant.
Une chose à laquelle vous devez être attentif lorsque vous utilisez locals() est que cette fonction inclue toutes les variables locales, ce qui peut comprendre plus de variables accessibles par votre gabarit que vous ne le désirez vraiment. Dans notre exemple précédent, locals() incluera aussi request. Que ceci soit important pour vous dépend de votre application.
Une dernière chose à considérer est que locals() implique une petite surchage, car lorsque vous l’appellez, Python doit créer le dictionnaire dynamiquement. Si vous spécifiez le dictionnaire de contexte manuellement, vous évitez cette surcharge.
Sous-répetoire dans get_template()
Stocker tous vos gabarits dans un seul répertoire peut devenir laborieux. Vous préférerez alors les classer dans des sous-répertoires de votre dossier de gabarits, et c’est une bonne chose. En fait, nous vous recommendons de faire ainsi ; quelques fonctionnalités plus avancées de Django (comme le système des vues génériques, que nous couvrirons au chapitre 9), attendent cette organisation des gabarits comme une convention par défaut.
Stocker vos gabarits dans des sous-répertoires de votre dossier de gabarit est très simple. Dans vos appels à get_template(), ajoutez simplement le nom du sous-répertoire et un slash devant le nom du gabarit, comme ceci :
t = get_template('dateapp/current_datetime.html')
Puisque render_to_response() encapsule get_template(), vous pouvez faire la même chose avec le premier argument de render_to_response().
Il n’y a pas de limite à la profondeur de votre arborescence. N’hésitez pas à en utiliser autant que vous le désirez.
Note
Pour les utilisateurs Windows, assurez-vous d’utiliser le slash / plutôt que les anti-slashs \. get_template() sous-entends une désignation des noms de fichier selon le style Unix.
La balise de gabarit include
À présent que nous avons couvert le mécanisme de chargement des gabarits, nous pouvons introduire une balise de gabarit préconçue qui en bénéficie : {% include %}. Cette balise vous permet d’inclure le contenu d’un autre gabarit. L’argument de balise doit être le nom du gabarit à inclure, et le nom du gabarit peut être soit une variable ou une chaîne codée en dur, entre guillemets simples ou doubles.
À chaque fois que vous avez le même code dans de multiples gabarits, considérez l’utilisation de {% include %} pour éviter la duplication.
Ces deux exemples incluent le contenu du gabarit nav.html. Les exemples sont équivalents et illustrent le fait que les guillemets simples ou doubles sont permis :
{% include 'nav.html' %}
{% include "nav.html" %}
Cet exemple inclu le contenu du gabarit includes/nav.html :
{% include 'includes/nav.html' %}
Cet exemple inclu le contenu du gabarit dont le nom se trouve dans la variable template_name :
{% include template_name %}
Tout comme dans get_template(), le nom de fichier du gabarit est déterminé en ajoutant le répertoire de gabarit dans TEMPLATE_DIRS au nom de gabarit requis.
Les gabarits importés sont évalués selon le contexte du gabarit appelant.
Si le gabarit avec le nom indiqué n’est pas trouvé, Django fera l’une de ces deux choses :
- Si DEBUG``est paramétré à ``True, vous verrez l’exception TemplateDoesNotExist dans une page d’erreur Django.
- Si DEBUG``est paramétré à ``False, la balise échouera silencieusement, n’affichant rien à la place de la balise.
Héritage de gabarit
Nos gabarits n’ont été jusqu’à présent que de simple bout de HTML, mais dans le monde réel, vous utiliserez le système de gabarit de Django pour créer des page HTML entières. Ceci nous amène à un problème courant du développement web : au sein d’un site web, comment réduire la duplication et la redondance de zones communes à différentes pages, telle que la barre de navigation ?
Un façon classique de résoudre ce problème est d’utiliser les directives server-side includes, directives que vous pouvez embarquer dans vos pages HTML pour inclure une page web à l’intérieur d’une autre. En effet, Django supporte cette approche, avec la balise de gabarit {% include %} que nous venons de décrire. Cependant, la meilleure façon de résoudre ce problème avec Django est d’utiliser une stratégie plus élégante, appellée l’héritage de gabarit.
Par essence, l’héritage de gabarit vous autorise à construire un squelette de base qui contient toutes les parties communes de votre site et qui définit des blocs que tous les gabarits fils peuvent écraser.
Voyons un exemple de ceci en créant un gabarit plus complet pour notre vue current_datetime. Éditons le fichier current_datetime.html :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>The current time</title>
</head>
<body>
<h1>My helpful timestamp site</h1>
<p>It is now {{ current_date }}.</p>
<hr>
<p>Thanks for visiting my site.</p>
</body>
</html>
Ceci semble correct, mais qu’arrive t-il lorsque nous voulons créer un gabarit pour une autre vue ? Par exemple pour la vue hours_ahead du chapitre 3 ? Si nous voulons à nouveau faire un bon gabarit HTML, valide, nous devons faire quelque chose comme ceci :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>Future time</title>
</head>
<body>
<h1>My helpful timestamp site</h1>
<p>In {{ hour_offset }} hour(s), it will be {{ next_time
}}.</p>
<hr>
<p>Thanks for visiting my site.</p>
</body>
</html>
Évidemment, nous avons simplement dupliqué beaucoup de HTML. Imaginez si nous avions eu un site plus caractéristique, proposant une barre de navigation, quelques feuilles de styles, et peut être un peu de javascript ? Nous aurions fini par placer toute sorte de HTML redondant dans chaque gabarits.
La solution ‘server-side include’ à ce problème consiste à factoriser le code commun dans les gabarits et de les placer dans des gabarits à part, qui seront ensuite inclus dans chaque gabarit. Vous pourriez placer le code d’en-tête du gabarit dans un fichier appelé header.html :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head>
Vous pourriez aussi placer le code du pied de page dans un fichier appelé footer.html :
<hr>
<p>Thanks for visiting my site.</p>
</body>
</html>
Avec une stratégie d’inclusion, les en-têtes et pied de page restent simple. C’est le centre de la page qui est désorganisé. Dans cet exemple, les deux pages proposent un titre <h1>My helpful timestamp site</h1>, mais ce titre ne peut faire partie de header.html puisque le <title>, titre des deux pages, est différent. Si nous incluons <h1> dans l’en-tête, nous allons devoir inclure <title>, ce qui ne nous autorisera pas à le personaliser sur chacune des pages. Voyez-vous où nous voulons en venir ?
Le système d’héritage de gabarit sous Django résoud ces problèmes. Vous pouvez voir cela comme une version ‘à l’enver’ des serve-side includes. À la place de définir le morceaux de code en commun, vous définissez les morceaux de code qui sont différents.
La première étape consiste à définir le squelette d’un gabarit de base pour votre page, que les gabarits enfants rempliront par la suite. Voici un gabarit de base pour notre exemple :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<h1>My helpful timestamp site</h1>
{% block content %}{% endblock %}
{% block footer %}
<hr>
<p>Thanks for visiting my site.</p>
{% endblock %}
</body>
</html>
Ce gabarit, que nous appelerons base.html, definit le squelette d’un simple document HTML que nous utiliserons pour toutes les pages du site. C’est le travail des gabarits enfants d’écraser, d’ajouter, ou de laisser en l’état le contenu des blocs. (Si vous suivez cet exemple chez vous, enregistrez ce fichier dans votre dossier de gabarit).
Nous utilisons ici une balise de gabarit que vous n’avez pas encore vu auparavant : la balise {% block%}. L’unique chose que font ces balises {% block %} est d’indiquer au moteur de gabarit qu’un gabarit enfant peut écraser ces portions dans le gabarit parent.
Maintenant que nous avons ce gabarit de base, nous pouvons modifier notre gabarit current_datetime.html afin de l’utiliser :
{% extends "base.html" %}
{% block title %}The current time{% endblock %}
{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}
Pendant que nous y sommes, créons un gabarit pour la vue hours_ahead du chapitre 3. (Si vous suivez les exemples de code, nous vous laissons modifer hours_ahead de façon à utiliser le système de gabarit). Voici à quoi cela devrait ressembler :
{% extends "base.html" %}
{% block title %}Future time{% endblock %}
{% block content %}
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
{% endblock %}
N’est-ce pas merveilleux ? Chaque gabarit contient uniquement le code qui lui est unique. Pas de redondance. Si vous devez faire un changement impactant l’intégralité du site, modifiez simplement base.html, et tous les autres gabarits reflèterons immédiatement la modification.
Voici comment tout cela fonctionne. Lorsque vous chargez le gabarit current_datetime.html, le moteur de gabarit repère la balise {% extends %}, précisant que ce gabarit est un gabarit enfant. Le moteur charge immédiatement le gabarit parent, dans notre cas, base.html.
À cet instant, le moteur de gabarit remarque les trois balises {% block %} dans base.html et remplace ces blocs avec le contenu du gabarit enfant. Ainsi, le titre que nous avons défini dans {% block title %} sera utilisé, tout comme {% block content %}.
Remarquez que puisque le gabarit enfant ne définit pas le bloc footer, le système de gabarit utilise la valeur du gabarit parent à la place. Le contenu d’une balise {% block %} dans un gabarit parent est toujous utilisé par défaut.
L’héritage n’affecte en rien la façon dont le contexte fonctionne, et vous pouvez utiliser autant de niveaux d’héritage que nécessaire. Une manière courante d’utiliser l’héritage est l’approche à trois niveaux suivante :
- Créez un gabarit base.html qui contient les caractéristiques principales de votre site. Il s’agit des choses qui changent rarement, voir jamais.
- Créez un gabarit base_SECTION.html pour chacune des ‘sections’ de votre site (par exemple, base_photos.html et base_forum.html). Ces gabarits étendent base.html et incluent les styles et graphismes spécifiques à ces sections.
- Créez un gabarit individuel pour chaque type de page, telles que les pages d’un forum, d’une gallerie de photographies. Ces gabarits étendent la section appropriée du gabarit.
Cette approche optimise la réutilisation du code et rends facile l’ajout d’éléments aux parties communes, telle qu’une barre de navigation.
Voici quelques astuces pour travailler avec l’héritage de gabarits :
- Si vous utilisez {% extends %} dans un gabarit, ce doit être la première balise dans ce gabarit. Sinon, l’héritage de gabarit ne fonctionnera tout simplement pas.
- Généralement, plus il y a de balise {% block %} dans votre gabarit de base, mieux c’est. Souvenez-vous, les gabarits enfants n’ont pas à définir tous les blocs parents, vous pouvez donc remplir par défaut de nombreux blocs, pour ensuite définir uniquement ceux dont vous avez besoin dans les gabarits enfants. Il est préférable d’avoir le plus de point d’accroche possible.
- Si vous vous surprenez à dupliquer du code dans de nombreux gabarits, cela signifie probablement que vous devriez déplacer ce code dans le {% block %} d’un gabarit parent.
- Si vous avez besoin d’obtenir le contenu d’un bloc depuis un gabarit parent, la variable {{ block.super }} fera l’affaire. C’est pratique si vous voulez ajouter du contenu à un bloc parent au lieu de le remplacer complétement.
- Vous ne devez pas définir plusieurs balises {% block %} portant le même nom à l’intérieur d’un même gabarit. Cette limitation existe parce qu’une balise de bloc fonctionne dans les deux directions. Autrement dit, une balise de bloc ne fournit pas uniquement un trou à boucher, elle définit aussi le contenu qui remplit le trou dans le gabarit parent. S’il existe des balises {% block %} nommées de la même façon dans un gabarit, le gabarit parent ne saura pas lequels des blocs utiliser.
- Le nom du gabarit que vous passez à {% extends %} est chargé en utilisant la même méthode qu’utilise get_template(). Autrement dit, le nom du gabarit est ajouté à votre paramètre TEMPLATE_DIRS.
- Dans la plupart des cas, l’argument de {% extends %} sera une chaine de caractères, mais ce peut être aussi une variable, si vous ne connaissez pas encore le nom du gabarit parent au moment de l’appel. Ceci vous permet de faire des choses dynamiques et fort sympatiques.
Et ensuite ?
La plupart des sites web modernes sont pilotés par des bases de données : le contenu du site web est stocké dans une base de données relationnelle. Ceci permet une séparation propre des données et de la logique (de la même façon que les vues et les gabarits autorisent la séparation de la logique et de l’affichage).
Le chapitre suivant couvre les outils que Django met à disposition pour intéragir avec une base de données.
Dernière modification: 2008-08-07 12:45:56.483716