Chapitre 10: Extension du moteur de gabarit
Même si la plupart de vos interactions avec le langage de gabarit de Django se feront avec le rôle d’auteur de gabarit, vous pourriez vouloir personnaliser et étendre ce moteur — pour à la fois lui faire faire quelquechose qu’il ne fasse pas déjà, ou pour faciliter votre travail de quelque façon.
Ce chapitre explore en profondeur le système de template de Django. Il couvre ce que vous devez savoir si vous désirez étendre le système ou si vous êtes simplement curieux de connaître sont fonctionnement.
Si vous cherchez à utiliser le système de gabarit de Django comme un composant d’une autre application (c’est à dire, sans le reste du framework), assurez vous de lire la section plus avant dans ce chapitre, «Configurer le moteur de gabarit en mode autonome».
Revue du langage de gabarit
Tout d’abord, révisons rapidement quelques termes introduits au chapitre 4 :
Un gabarit est un document texte, ou une chaîne Python standard, qui est balisé en utilisant le langage de gabarit de Django. Un gabarit peut contenir des balises de blocs et des variables.
Une balise de bloc est un symbole qui fait quelquechose à l’intérieur d’un gabarit. Cette définition est délibérément vague. Par exemple, une balise de bloc peut produire du contenu, servir de structure de contrôle (une instruction if ou une boucle for), récupèrer du contenu depuis une base de données, ou permettre l’accès à d’autre balises de gabarit.
Les balises de bloc sont encadrées par {% et %} :
{% if is_logged_in %} Thanks for logging in! {% else %} Please log in. {% endif %}Une variable est un symbole à l’intérieur d’un gabarit qui produit une valeur.
les balises de variable sont encadrées par {{ et }}:
Mon prénom est {{ prenom }}. Mon nom est {{ nom }}.Un contexte est une correspondance nom -> valeur (similaire a un dictionnaire en Python) qui est transmis à un template.
Un gabarit retourne un contexte en remplaçant les «trous» des variables par les valeur provenant du contexte et exécute toutes les balises de bloc.
Pour plus de détails sur les bases concernant ces termes, référez vous au chapitre 4
Le reste de ce chapitre aborde les manières d’étendre le moteur de gabarit. Mais tout d’abord, regardons rapidement la mécanique interne laissée de côté au chapitre 4 pour des raisons de simplicité.
RequestContext and Context Processors
Lors du rendu d’un gabarit, vous avez besoin d’un contexte. D’habitude il s’agit d’une instance de django.template.Context, mais Django fournit aussi une sous classe spéciale, django.template.RequestContext, qui agit légérement différement. RequestContext ajoute tout un tas de variables à votre contexte de gabarit par défaut — pensez à l’objet HttpRequest ou aux informations au sujet de l’utilisateur actuellement authentifié.
Utilisez RequestContext lorsque vous ne voulez pas avoir à spécifier le même jeu de variables dans une série de gabarits. Par exemple, considérez ces quatres vues :
from django.template import loader, Context
def view_1(request):
# ...
t = loader.get_template('template1.html')
c = Context({
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR'],
'message': 'I am view 1.'
})
return t.render(c)
def view_2(request):
# ...
t = loader.get_template('template2.html')
c = Context({
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR'],
'message': 'I am the second view.'
})
return t.render(c)
def view_3(request):
# ...
t = loader.get_template('template3.html')
c = Context({
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR'],
'message': 'I am the third view.'
})
return t.render(c)
def view_4(request):
# ...
t = loader.get_template('template4.html')
c = Context({
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR'],
'message': 'I am the fourth view.'
})
return t.render(c)
(Notez que nous n’utilisons pas délibérément le raccourcis render_to_response() dans ces exemples — nous chargeons manuellement les gabarits, construisons les objets de contexte et assurons le rendu des gabarits. Nous allons «épeler» toutes les étapes dans un but de clareté)
Chacune des vues transmet les mêmes trois variables — app, user et ip_address — à son gabarit. Ne serait-il pas sympathique de pouvoir supprimer cette redondance ?
RequestContext et les processeurs de contexte ont été crées pour résoudre ce problème. Les processeurs de contexte vous laissent préciser un nombre de variables qui est fixé automatiquement pour chaque contexte — sans avoir à spécifier les variables dans chaque appel à render_to_response(). Vous n’avez plus qu’à utiliser RequestContext à la place de Context lorsque vous retounrez un gabarit.
L’approche la plus «bas niveau» pour utiliser les processeurs de contexte est de créer quelques processeurs puis de les passer à RequestContext. Voici comment les exemples précédents peuvent être écrit avec l’aide des processeurs de contexte :
from django.template import loader, RequestContext
def custom_proc(request):
"A context processor that provides 'app', 'user' and
'ip_address'."
return {
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR']
}
def view_1(request):
# ...
t = loader.get_template('template1.html')
c = RequestContext(request, {'message': 'I am view 1.'},
processors=[custom_proc])
return t.render(c)
def view_2(request):
# ...
t = loader.get_template('template2.html')
c = RequestContext(request, {'message': 'I am the second
view.'},
processors=[custom_proc])
return t.render(c)
def view_3(request):
# ...
t = loader.get_template('template3.html')
c = RequestContext(request, {'message': 'I am the third
view.'},
processors=[custom_proc])
return t.render(c)
def view_4(request):
# ...
t = loader.get_template('template4.html')
c = RequestContext(request, {'message': 'I am the fourth
view.'},
processors=[custom_proc])
return t.render(c)
Parcourons ce code :
- Pour commencer, nous définissons une fonction custom_proc. C’est un processeur de contexte — il prends un objet HttpRequest et retourne un dictionnaire de varaibles à utiliser dans le contexte du gabarit. C’est tout ce qu’il fait.
- Nous avons modifiés les quatres fonctions de la vue afin d’utiliser RequestContext à la place de Context. Il y a deux différences dans la méthode de construction du contexte. Dans la première, RequestContext nécessite que le premier argument soit un objet HttpRequest - celui qui était transmis à la fonction vue en premier lieu (request). dans la seconde, RequestContext prends un argument processors optionnel, qui est une liste ou un tuple des fonctions processeurs de contexte à utiliser. Ici, nous passons custom_proc, le processeur personnalisé définit ci-dessus.
- Les vues n’ont plus besoin d’inclure app, user ou ip_address dans la construction du contexte, parceque ceux-ci in its context construction, because those are provided by custom_proc.
- Chaque vue conserve la souplesse d’introduire n’importe quelles variables de gabarit personnalisées dont elle aurait besoin. Dans cet exemple, la variable de gabarit message est différente dans chaque vue.
Au chapitre 4, nous avons introduit le raccourcis render_to_response(), qui vous évite d’avoir à appeler loader.get_template(), puis crée un Context, pour ensuite appeler la méthode render() sur le gabarit. De façon à démontrer le fonctionnement bas niveau des processeurs de contexte, l’exemple ci-desssous n’utilise pas render_to_response(). Il est possible — et préférabel — d’utiliser les processeurs de contexte avec render_to_response(). Faîtes cela avec l’argument context_instance, comme ceci :
from django.shortcuts import render_to_response
from django.template import RequestContext
def custom_proc(request):
"A context processor that provides 'app', 'user' and
'ip_address'."
return {
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR']
}
def view_1(request):
# ...
return render_to_response('template1.html',
{'message': 'I am view 1.'},
context_instance=RequestContext(request,
processors=[custom_proc]))
def view_2(request):
# ...
return render_to_response('template2.html',
{'message': 'I am the second view.'},
context_instance=RequestContext(request,
processors=[custom_proc]))
def view_3(request):
# ...
return render_to_response('template3.html',
{'message': 'I am the third view.'},
context_instance=RequestContext(request,
processors=[custom_proc]))
def view_4(request):
# ...
return render_to_response('template4.html',
{'message': 'I am the fourth view.'},
context_instance=RequestContext(request,
processors=[custom_proc]))
Ici, nous avons réduit le code du rendu de gabarit de chaque vues à une seule ligne (emballée).
C’est une amélioration mais, en évaluant la concision de ce code, nous devons admettre que nous ne semble pas loin d’atteindre l’extrême inverse ?
Nous avons supprimé la redondance des données (nos variables de gabarit) au prix de l’ajout de redondance dans le code (dans l’appel à processors). Utiliser les processeurs de contexte ne vous épargne pas beaucoup de saisie si vous devez tout le temps taper processors.
Pour cette raison, Django fournit un support globale pour les processeurs de contexte. Le paramètre TEMPLATE_CONTEXT_PROCESSORS désigne les processeurs de contexte qui doivent toujours êtres appliqués à RequestContext. Ceci supprime le besoin d’avoir à préciser processors à chaque utilisation de RequestContext.
Par défaut, TEMPLATE_CONTEXT_PROCESSORS est réglé comme suit :
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
)
Ce réglage est un tuple de «callables» (ndt: «d’appellables») qui utilisent la même inteface que notre fonction custom_proc à venir — les fonctions qui prennent un objet requête pour argument et retournent un dictionnaire d’éléments injecté dans le contexte. Notez que les valeurs dans TEMPLATE_CONTEXT_PROCESSORS sont des chaînes, ce qui signifie que les processeurs doivent être quelquepart dans votre Python path (vous pouvez donc les consulter depuis le paramétre).
Chaque processeur est appliqué dans l’ordre. Ainsi, si un processeur ajoute une variable au contexte et qu’un second processeur ajoute une variable du même nom, la seconde écrasera la première.
Django fournit de nombreux processeurs de contexte simples, y compris ceux qui sont activés par défaut :
django.core.context_processors.auth
Si TEMPLATE_CONTEXT_PROCESSORS contient ce processeur, chaque RequestContext contiendra ces variables :
- user: une instance django.contrib.auth.models.User représentant l’utilisateur actuellement connecté (une une instance AnonymousUser, si le client n’est pas connecté).
- messages: une listes des messages (sous forme de chaînes) pour l’utilisateur actuellement connecté. En coulisses, cette variables appèlle request.user.get_and_delete_messages() pour chaque requête. Cette méthode collecte les messages des utilisateurs et les supprime dans la base de données.
- perms: une instance de django.core.context_processors.PermWrapper, qui représente les permissions que possède l’utilisateur actuellement connecté.
Lisez le chapitre 12 plus plus d’informations sur les utilisateurs, les permissions et les messages.
django.core.context_processors.debug
Ce processeur descend l’information de débugage au niveau de la couche de gabarit. Si TEMPLATE_CONTEXT_PROCESSORS contient ce processeur, chaque RequestContext possédera ces variables :
- debug: la valeur de votre paramètre DEBUG (soit True ou False). Vous pouvez utiliser cette variable dans les gabarits pour vérifier si vous êtes en mode «débug».
- sql_queries: une liste de dictionnaires {'sql': ..., 'time': ...} représentants chaque requête SQL qui a eue lieu jusqu’à présent au cours de la requête et combien de temps cela a pri. La liste est ordonnée selon l’ordre d’émmission des requêtes.
Puisque les informations de débugage sont sensibles, ce processeur de contexte n’ajoutera des variables au context seulement si les deux conditions sont vraies :
- Le paramètre DEBUG est à True.
- La requête provient d’une adrese IP présente dans le paramètre INTERNAL_IPS.
django.core.context_processors.i18n
Si ce processeur est activé, chaque RequestContext contiendra ces variables :
- LANGUAGES: La valeur du paramètre LANGUAGES.
- LANGUAGE_CODE: request.LANGUAGE_CODE si existant; sinon, la valeur du paramètre LANGUAGE_CODE.
L’annexe E fournit de plus amples détails au sujet de ces deux paramètres.
django.core.context_processors.request
:
Si ce processeur est activé, chaquey RequestContext contiendra une variable request, à savoir l’object HttpRequest courant. Notez que ce processeur n’est pas activé par défaut; vous devez l’activer.
Conseils pour l’écriture de vos propres processeurs de contexte
Voici quelques astuces pour le faire vous même :
Rendez chaque processeur de contexte responsable du plus petit jeu de fonctionnalité possible. L’utilisation de multiple processeurs est aisée, vous pouvez donc tout aussi bien séparer la fonctionalité en éléments logiques réutilisables à l’avenir.
Gardez à l’esprit que tout processeur de contexte dans TEMPLATE_CONTEXT_PROCESSORS sera disponible dans chaque gabarit soumis à ce fichier de paramètres. Aussi essayez de choisir des noms de variables qui ne soient pas susceptibles d’entrer en conflit avec les noms de variables que vos squelettes pourrait utiliser par ailleur. Puisque les noms de variables ne sont pas sensibles à la casse, ce n’est pas une mauvaise idée que de réserver les lettres capitales aux variables qu’un processeur fournit.
Peu importe l’endroit ou ils résident dans le système de fichier, dès lors qu’ils se trouvent sur votre Python path vous pourrez les pointer depuis le paramètre TEMPLATE_CONTEXT_PROCESSORS. Ceci dit, la convention est de les conserver dans un fichier nommé context_processors.py à l’intérieur de votre appli ou de votre projet.
It doesn?t matter where on the filesystem they live, as long as they?re on your Python path so you can point to them from the TEMPLATE_CONTEXT_PROCESSORS setting. With that said, the convention is to save them in a file called context_processors.py within your app or project.
Au cœur du chargement de gabarit
Généralement, vous stockerez vos gexabarits dans des fichiers de votre système, mais vous pouvez utiliser des chargeurs de gabarit personnalisés pour le chargement des gabarits depuis d’autres sources.
Django propose deux façons de charger les gabarits:
- django.template.loader.get_template(template_name): get_template retourne le gabarit compilé (un objet Template) pour le gabarit d’un nom donné. Si le gabarit n’existe pas, une exception TemplateDoesNotExist sera levée.
- django.template.loader.select_template(template_name_list): select_template est similaire à get_template, sauf qu’elle prends une liste de noms de gabarit. De la liste, elle retourne le premier gabarit qui existe. Si aucun des gabarits n’existe, une exception TemplateDoesNotExist sera levée.
Comme nous l’avons vu au chapitre 4, chacun de ces fonctions utilise par défaut votre paramètre TEMPLATE_DIRS pour charger les gabarits. En interne, cependant, ces fonctions délèguent en fait à un chargeur de gabarit les modifications lourdes.
Certains chargeurs sont désactivés par défaut, mais vous pouvez les activer en éditant le paramètre TEMPLATE_LOADERS. TEMPLATE_LOADERS doit être un tuple de chaînes, où chaque chaîne représente un chargeur de gabarit. Ces chargeurs de gabarit sont fournis avec Django:
django.template.loaders.filesystem.load_template_source: ce chargeur charge les gabarits depuis le système de fichier, selon TEMPLATE_DIRS. C’est activé par défaut.
django.template.loaders.app_directories.load_template_source: Ce chargeur charge les gabarits depuis les applications Django sur le système de fichier. Pour chaque application dans INSTALLED_APPS, le chargeur cherche un sous-répertoire templates. Si le répertoire existe, Django y recherche des gabarits.
Ceci signifie que vous pouvez stocker des gabarits avec vos applications individuelles, facilitant la distribution des applications Django avec des gabarits par défaut. Par exemple, si INSTALLED_APPS contient ('myproject.polls', 'myproject.music'), alors get_template('foo.html') cherchera des gabarits dans cet ordre :
- /path/to/myproject/polls/templates/foo.html
- /path/to/myproject/music/templates/foo.html
Notez que le chargeur effectue une optimisation lors de son premier import: il cache une liste des INSTALLED_APPS qui possèdent un sous-répertoire templates.
Ce chargeur est activé par défaut
django.template.loaders.eggs.load_template_source: ce chargeur est semblable à app_directories, mis à part qu’il charge le gabarit depuis les oeufs Python plutôt que depuis le système de fichier. Ce chargeur est désactivé par défaut; vous devrez l’activer si vous utilisz des oeufs pour distribuer votre application.
Django utilise le chargeur de gabarit selon le paramètre TEMPLATE_LOADERS. Il utilise chaque chargeur jusqu’à ce qu’un chargeur trouve une correspondance.
Étendre le système de gabarit
À présent que vous comprenez un peu plus la mécanique du système de gabarit, voyons comment étendre le système avec du code personnalisé.
La personnalisation de gabarit vient principalement sous la forme de balises de gabarit personnalisées ou de filtres. Bien que le langage de gabarit de Django propose de nombreuses balises et filtres embarqués, vous construirez probablement vos propres bibliothèques de balises et de filtres qui répondent à vos besoins personnels. Heureusement, il est assez facile de définir votre propre fonctionalité.
Créer une bibliothèque de gabarit
Que vous écriviez des balises ou des filtres personnalisés, la première chose à faire est de créer une bibliothèque de gabarits — une portion d’infrastructure sur laquelle Django peut s’accrocher.
La création d’une bibliothèque de gabarit est un processus à deux étapes:
Commencez par décider de l’application Django qui doit héberger la bibliothèque de template. Si vous avez créé une application via manage.py startapp, vous pouvez la placer ici, ou vous pouvez créer une autre application uniquement pour la bibliothèque de gabarits.
Quelque soit la voie choisie, assurez vous d’ajouter l’application à votre paramètre INSTALLED_APPS. Nous expliquerons cela rapidement.
Ensuite, créez un répertoire templatetags dans le dossier de l’application Django approprié. Il doit être au même niveau que models.py, views.py, et autre. Par exemple:
books/ __init__.py models.py templatetags/ views.pyCréez deux fichiers vierges dans le répertoire templatetags: un fichier __init__.py (pour indiquer à Python qu’il s’agit d’un paquet contenant du code Python) et un fichier qui contiendra vos définitions personnalisées de balise/filtre. Le nom de ce dernier fichier est celui que vous utiliserez pour charger la balise plus tard. Par exemple, si vos balises/filtres personnalisés sont dans un fichier poll_extras.py, vous écrirez la ligne suivante dans un gabarit:
{% load poll_extras %}La balise {% load %} ressemble à votre paramètre INSTALLED_APPS et n’autorise que le chargement de bibliothèques de gabarit à l’intérieur d’applications Django installées. C’est une fonctionnalité liée à la sécurité; elle vous permet d’héberger du code Python pour beaucoup de bibliothèques de gabarit sur un ordinateur unique sans autoriser l’accès à toutes pour chaque installation DJango.
Si vous écrivez une bibliothèque de gabarit qui n’est pas liée à tel ou tel modèle/vue, il est correct et tout à fait normal d’avoir un paquet d’application qui contient seulement un paquet templatetags. Il n’y a pas de limite sur le nombre de modules que vous placez dans le paquet templetags. Gardez simplement à l’esprit qu’une instruction {% load %} chargera les balises/filtres pour le nom de module Python donné, pas le nom de l’application.
Une fois créé ce module Python, il ne vous restera plus qu’à écrire un peu de code Python, selon que vous écrirez des filtres ou des balises.
Pour être une bibliothèque de balise valide, le module doit contenir une variable de niveau-module appelée register qui est une instance template.Library. Cette instance template.Library est la structure de données dans laquelle toutes les balises et filtres sont enregistrés. Ainsi, au début de votre module, insérez ce qui suit:
from django import template register = template.Library()
Note
Pour un bon nombre d’exemples, lisez le code source des filtres et des balises par défaut sous Django. Ils sont dans django/template/defaultfilters.py et django/template/defaulttags.py, respectivement. Quelques applications dans django.contrib contiennent aussi des bibliothèques de gabarit.
Lorsque vous aurez créé cette variable register, vous l’utiliserez pour créer des gabarits de filtres et balises.
Écrire des filtres de gabarit personnalisés
Les filtres personnalisés sont juste des fonctions Python qui prennent un ou deux arguments:
- La valeur de la variable (input)
- La valeur de l’argument, qui peut avoir une valeur par défaut ou être tout bonnement ignorée.
Par exemple, dans le filtre {{ var|foo:"bar" }}, le filtre foo transmettrait le contenu de la variable var et l’argument "bar".
Les fonctions filtre doivent toujours retourner quelque chose. Elles ne doivent pas lever d’exceptions, et doivent échouer silencieusement. S’il y a une erreur, elles doivent retourner soit l’entrée originale ou une chaîne vide, selon ce qui fait le plus sens.
Voici un exemple de définition de filtre:
def cut(value, arg):
"Removes all values of arg from the given string"
return value.replace(arg, '')
Et voici un exemple d’utilisation de ce filtre:
{{ somevariable|cut:"0" }}
La plupart des filtres ne prennent pas d’arguments. Dans ce cas, laissez simplement l’argument de votre fonction:
def lower(value): # Only one argument.
"Converts a string into all lowercase"
return value.lower()
Lorsque vous aurez écrit votre définition de filtre, vous devrez l’enregistrer avec votre instance Library, pour la rendre disponible au langage de gabarit de Django:
register.filter('cut', cut)
register.filter('lower', lower)
La méthode Library.filter() prends deux arguments:
- Le nom du filtre (une chaîne)
- La fonction filtre en elle-même
Si vous utilisez Python2.4 et suivant, vous pouvez à la place utiliser register.filter() comme un décorateur:
@register.filter(name='cut')
def cut(value, arg):
return value.replace(arg, '')
@register.filter
def lower(value):
return value.lower()
Si vous laissez l’argument name, comme dans le second exemple, Django utilisera le nom de la fonction comme nom de filtre.
Voici pour suivre un exemple complet de bibliothèque de gabarit, fournissant le filtre cut:
from django import template
register = template.Library()
@register.filter(name='cut')
def cut(value, arg):
return value.replace(arg, '')
Écrire des balises de gabarit personnalisées
Les balises sont plus complexes que les filtres, parceque les balises peuvent faire à peu près tout.
Le chapitre 4 décrit le fonctionnement en deux temps du système de gabarit: compilatio et rendu. Pour définir une balise personnalisée de gabarit, vous devez indiquer à Django la façon de gérer les deux étapes lorsque il arrive à votre balise.
Lorsque Django compile un gabarit, il le découpe le texte brut du gabarit en nœuds. Chque nœud est une instance de django.template.Node et possède une méthode render(). Ainsi, un gabarit compilé est simplement une liste d’objet Node.
Lorsque vous appelez render() sur un gabarit compilé, le gabarit appelle render() sur chaque Node de sa liste de noeuds, avec le contexte donné. Les résultats sont tous concaténés ensemble pour former la sortie du gabarit. Donc, pour définir une balise de gabarit personnalisée, vous spécifiez comment la balise de gabarit brute est convertie en Node (la fonction compilation) et ce que la méthode render() du nœud fait.
Dans les sections qui suivent, nous couvrons toutes les étapes de l’écriture d’une balise personnalisée.
Écrire la fonction de compilation
Pour chaque balise de gabarit rencontrées, l’analyseur de gabarit appel une fonction avec le contenu de la balise et l’objet analyseur en lui même. Cette fonction est responsable de retourner une instance de Node basée sur le contenu de la balise.
Par exemple, écrivons une balise de gabarit, {% current_time %}, qui affiche le date/heure courante, formatée selon un paramètre précisé dans la balise, en syntaxe strftime (voir http://www.djangoproject.com/r/python/strftime/). C’est une bonne idée de décider de la syntaxe de balise avant tout. Dans notre cas, disons que la balise doit être utilisée comme ceci:
<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
Note
Oui, cette balise de gabarit est redondante — la balise DJango par défaut {% now %} fait la même chose avec une syntaxe plus simple. Cette balise de gabarit est présentée ici à titre d’exemple.
L’analyseur de cette fonction récupèrera le paramètre pour créer un objet Node:
from django import template
def do_current_time(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, format_string = token.split_contents()
except ValueError:
msg = '%r tag requires a single argument' %
token.contents[0]
raise template.TemplateSyntaxError(msg)
return CurrentTimeNode(format_string[1:-1])
Il se passe pas mal de chose ici en fait:
- parser est l’objet analyseur de gabarit. Nous n’en avons pas besoin dans cet exemple.
- token.contents est une chaîne brute du contenu de la balise. Dans notre exemple, il s’agit de 'current_time "%Y-%m-%d %I:%M %p"'.
- La méthode token.split_contents() sépare les arguments selon les espaces tout en conservant les chaînes entre guillemets ensemble. Évitez l’utilisation de token.contents.split() (qui utilise simplement la sémantique de découpe en standard sous Python). Elle n’est pas aussi robuste, puisqu’elle tranche naïvement sur toutes les espaces, y compris celles à l’intérieur des chaînes entre guillemets.
- Cette fonction à en charge la levée de django.template.TemplateSyntaxError, accompagnée de messages utiles, pour toute erreur de syntaxe.
- Ne codez pas en dur le nom de la balise dans votre message d’erreur, parce que cela couple le nom de la balise avec la fonction. token.split_contents()[0] sera toujours le nom de votre balise — même lorsque la balise n’a pas d’arguments.
- Les fonctions retournent un CurrentTimeNode (que nous allons créer d’ici peu) contenant tout ce que doit savoir le noeud au sujet de cette balise. Dans ce cas, il transmet juste l’argument "%Y-%m-%d %I:%M %p". Les guillemets encadrants la balise de gabarit sont retirés avec format_string[1:-1].
- Les fonctions de compilation des balises de gabarit doivent retournées une sous-classe Node; tout autre valeur de retour est une erreur.
Écrire le noeud de gabarit
La seconde étape dans l’écriture des balises personnalisées est de définir une sous-classe Node qui possède une méthode render(). En prenant l’exemple précédent, nous devons définir CurrentTimeNode:
import datetime
class CurrentTimeNode(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
now = datetime.datetime.now()
return now.strftime(self.format_string)
Ces deux fonctions (__init__ et render) reviennent aux deux étapes dans le rendu de gabarit (compilation et rendu). Aussi, la fonction d’initialisation n’a t-elle besoin que de stocker format_string pour usage ultérieur, et la fonction render() effectue tout le véritable travail.
Tout comme les filtres de gabarit, ces fonctions de rendu doivent échouer silencieusement au lieu de lever des erreurs. Le seul moment ou les balises sont autorisées à lever des erreurs se trouve à lors de la compilation.
Enregistrer la balise
Finalement, vous devez enregistrer la balise avec votre instance de module Library (ndt: Bibliothèque). Enregistrer des balises personnalisées est très similaire à l’enregistrement des filtres personnalisés (comme expliqué ci-dessus). Instanciez simplement une instance template.Library et appelez sa méthode tag(). Par exemple:
register.tag('current_time', do_current_time)
la méthode tag() prends deux arguments:
- Le nom de la balise de gabarit (chaîne). S’il est omis, le nom de la fonction de compilation sera utilisé.
- La fonction de compilation.
Comme pour l’enregistrement de filtre, il est possible d’utiliser register.tag comme un décorateur, à partir de Python 2.4 et suivant:
@register.tag(name="current_time")
def do_current_time(parser, token):
# ...
@register.tag
def shout(parser, token):
# ...
Si vous ommettez l’argument name, comme dans le second exemple, Django utilisera le nom de la fonction comme nom de la balise.
Paramètrer une variable dans le contexte
L’exemple de la section précédente retournais simplement une valeur. Il est souvent utile de paramétrer les variables de gabarit au lieu de retourner des valeurs. De cette façon, les auteurs de gabarit peuvent utiliser uniquement les variables que vos balises de gabarit définissent.
Pour paramétrer une variable dans le contexte, utilisez une instruction de dictionnaire sur l’objet dans la méthode render(). Voici une version mise à jour de CurrentTimeNode qui paramètre une variable de gabarit, current_time, au lieu de la retourner:
class CurrentTimeNode2(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
now = datetime.datetime.now()
context['current_time'] =
now.strftime(self.format_string)
return ''
Notez que render() retourne une chaîne vide. render() doit toujours retourner une chaîne, donc si tout ce que fait la balise de gabarit est de paramétrer une variable, render() devra retourner une chaîne de caractère vide.
Voici comment vous pourriez utiliser cette nouvelle version de la balise:
{% current_time2 "%Y-%M-%d %I:%M %p" %}
<p>The time is {{ current_time }}.</p>
Mais il y a un problème avec CurrentTimeNode2: le nom de la variable current_time est codé en dur. Cela signifie que vous devrez vous assurer que votre gabarit n’utilise pas {{ current_time }} nulle part ailleur, puisque {% current_time2 %} ira aveuglement écraser la valeur de cette variable.
Une solution plus propre est de faire en sorte que la balise de gabarit spécifie le nom de la variable qui doit être paramétrée, comme ceci:
{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>
Pour ce faire, vous devrez refactoriser à la fois la fonction de compilation et la classe Node, comme suit:
import re
class CurrentTimeNode3(template.Node):
def __init__(self, format_string, var_name):
self.format_string = format_string
self.var_name = var_name
def render(self, context):
now = datetime.datetime.now()
context[self.var_name] =
now.strftime(self.format_string)
return ''
def do_current_time(parser, token):
# This version uses a regular expression to parse tag
contents.
try:
# Splitting by None == splitting by spaces.
tag_name, arg = token.contents.split(None, 1)
except ValueError:
msg = '%r tag requires arguments' % token.contents[0]
raise template.TemplateSyntaxError(msg)
m = re.search(r'(.*?) as (\w+)', arg)
if m:
fmt, var_name = m.groups()
else:
msg = '%r tag had invalid arguments' % tag_name
raise template.TemplateSyntaxError(msg)
if not (fmt[0] == fmt[-1] and fmt[0] in ('"', "'")):
msg = "%r tag's argument should be in quotes" %
tag_name
raise template.TemplateSyntaxError(msg)
return CurrentTimeNode3(fmt[1:-1], var_name)
À présent do_current_time() transmet format string et le nom de variable à CurrentTimeNode3.
Parcourir jusqu’à un autre bloc de balise
Les balises de gabarit peuvent fonctionner comme des blocs contenant d’autres balises (pensez à {% if %}, {% for %}, etc.). Pour créer une balise de gabarit comme ceci, utilisez parser.parse() dans votre fonction de compilation.
Voici comment la balise standard {% comment %} est implémentée:
def do_comment(parser, token):
nodelist = parser.parse(('endcomment',))
parser.delete_first_token()
return CommentNode()
class CommentNode(template.Node):
def render(self, context):
return ''
parser.parse() prends un tuple de nom de balises de bloc à parcourir. Elle retourne une instance de django.template.NodeList, qui est une liste de tous les objets Node que l’analyseur à rencontré avant de croiser l’une des balises précisées dans le tuple.
Ainsi dans l’exemple précédent, nodelist est une liste de tous les noeuds entre {% comment %} et {% endcomment %}, sans compter {% comment %} et {% endcomment %} elles mêmes.
Après l’appel de parser.parse(), l’analyseur n’as pas encore «consommé» la balise {% endcomment %}, aussi le code doit explicitement appeller parser.delete_first_token() pour prévenir cette balise d’une double analyse.
Enfin CommentNode.render() retourne simplement une chaîne vide. Tout ce qui est entre {% comment %} et {% endcomment %} est ignoré.
Parcourir jusqu’à un autre bloc et enregistrer le contenu
Dans l’exemple précédent, do_comment() néglige tout ce qui est entre {% comment %} et {% endcomment %}. Il est aussi possible de faire quelque chose avec le code entre les balises de bloc.
Par exemple, voici une balise de gabarit personnalisée, {% upper %}, qui capitalise tout ce qui se trouve entre elle même et {% endupper %}:
{% upper %}
This will appear in uppercase, {{ your_name }}.
{% endupper %}
Comme dans l’exemple précédent, nous utiliserons parser.parse(). Cette fois, nous passons la nodelist resultante à Node:
@register.tag
def do_upper(parser, token):
nodelist = parser.parse(('endupper',))
parser.delete_first_token()
``DJANGO_SETTINGS_MODULE`` return UpperNode(nodelist)
class UpperNode(template.Node):
def __init__(self, nodelist):
self.nodelist = nodelist
def render(self, context):
output = self.nodelist.render(context)
return output.upper()
Le seule concept nouveau ici est self.nodelist.render(context) dans UpperNode.render(). Ceci appelle simplement render() sur chaque Node de la liste de noeuds.
Pour plus d’exemples ou pour des rendus complexes, consultez le code source de {% if %}, {% for %}, {% ifequal %}, et {% ifchanged %}. Il réside dans django/template/defaulttags.py.
Raccourcis pour les balises simples
Beaucoup de balises de gabarit prennent un argument unique — une chaîne ou une référence de variable de gabarit — et retourne une chaîne après avoir effectuée un traitement basé uniquement sur l’argument d’entrée et quelques informations externes. Par exemple la balise current_time que nous avons écrite auparavant est de cette variété. Nous lui fournissons une chaîne format, elle retourne l’heure sous forme de chaîne.
Pour faciliter la création de ces types de balises, Django fournit une fonction d’assistance, simple_tag. Cette fonction, qui est une méthode de django.template.Library, prends une fonction acceptant un argument, l’emballe dans une fonction render avec les autres parties mentionnée précédemment, et l’enregistre avec le système de gabarit.
Notre fonction current_time précédente peut ainsi être écrite comme ceci:
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
register.simple_tag(current_time)
À partir de Python 2.4, la syntaxe «décorateur» fonctionne aussi:
@register.simple_tag
def current_time(token):
...
Les trois chose à noter à propos de la fonction d’aide simple_tag sont les suivantes:
- Seul l’argument (unique) est transmis à notre fonction.
- La vérification du nombre d’arguments requis à déjà été faite lors de notre appel de fonction, nous n’avons donc pas besoin de le faire.
- Les guillemets autour de l’argument (s’il existe), ont déjà été retirés, nous recevons donc une chaîne pleine.
Balises d’inclusion
Une autre balise courante est celle de type affichant des données par le rendu d’un un autre gabarit. Par exemple, l’interface d’administration de Django utilise des balises de gabarit personnalisées pour afficher les boutons au bas des pages de formulaire «ajouter/modifier». Ces boutons ont toujours la même apparence, mais les cibles de liens changent en selon l’objet qui est édité. Ce sont des cas idéaux pour l’utilisation d’un petit gabarit qui est composé selon les détails provenant de l’objet courrant.
Ces sortes de balises sont appellées balises d’inclusion. L’écriture des balises d’inclusion est probablement mieux démontrée par l’exemple. Écrivons une balise qui produise une liste de choix pour un simple objet Poll (sondage) à choix multiples. Nous utiliserons la balise de cette façon:
{% show_results poll %}
Le résultat sera quelque chose ressemblant à:
<ul> <li>First choice</li> <li>Second choice</li> <li>Third choice</li> </ul>
Tout d’abord, nous définnissons la fonction qui prends l’arguement et retourne un dictionnaire de données pour résultat. Remarquez que nous devons retourner seulement un dictionnaire, pas quelque chose de plus compliqué. Il sera utilisé comme contexte pour le morceaux de gabarit:
def show_books_for_author(author):
books = author.book_set.all()
return {'books': books}
Ensuite, nous créons le gabarit utilisé pour le rendu de la sortie de balise. Continuant notre exemple, le gabarit est très simple:
<ul>
{% for book in books %}
<li> {{ book }} </li>
{% endfor %}
</ul>
Finallement, nous créons et enregistrons la balise d’inclusion en appellant la méthode inclusion_tag() sur l’objet Library.
Poursuivant notre exemple, si le gabarit précédent est dans un fichier nommé polls/result_snippet.html, nous enregistrons la balise comme ceci:
register.inclusion_tag('books/books_for_author.html')(show_books_for_author)
Comme toujours, la syntaxe du décorateur en Python 2.4 fonctionne, nous aurions donc pu tout aussi bien l’écrire ainsi:
@register.inclusion_tag('books/books_for_author.html')
def show_books_for_author(show_books_for_author):
...
Parfois, vos balises d’inclusions doivent accéder aux valeurs du contexte de gabarit parent. Pour résoudre ceci, Django fournit une option takes_context pour l’inclusion des tags. Si vous précisez takes_context lors de la création d’une balise de gabarit, la balise n’aura pas d’arguments requis, et la fonction Python sous-jacente aura un argument: le contexte de gabarit au moment de l’appel de balise.
Par exemple, admettons que vous soyez en train d’écrire une balise d’inclusion destinée à toujours être utilisée dans un contexte contenant les variables home_link et home_title, varaibles qui pointent vers la page principale. Voici à quoi ressemblerait la fonction Python:
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
return {
'link': context['home_link'],
'title': context['home_title'],
}
Note
Le premier paramètre de la fonction doit être appelé context.
La gabarit link.html doit contenir ce qui suit:
Aller directement au <a href="{{ link }}">{{ title }}</a>.
Ensuite, à chauqe fois que vous voudrez utiliser cette balise personnalisée, chargez sa bibliothèque et appellez là sans aucun arguments, comme cela:
{% jump_link %}
Écrire des chargeurs de gabarit personnalisés
Le chargeur de gabarit embarqué dans Django (décrit dans la section précédente «Au cœur du chargement de gabarit») couvrira généralement tous vos besoins en matière de chargement de gabarit, mais il est très facile d’écrire le votre si vous avez besoin d’une logique de chargement spécifique. Par exemple, vous pouvez charger des gabarits depuis une base de données, ou directement depuis un dépôt Subversion en utilisant la dépendance Subversion pour Python, ou (comme montré briévement) depuis une archive ZIP.
Un chargeur de gabarit — c’est à dire, chaque entrée dans le paramètre TEMPLATE_LOADERS — s’attend à être appelé avec cette interface:
load_template_source(template_name, template_dirs=None)
L’argument template_name est le nom du gabarit à charger (comme transmis à loader.get_template() ou loader.select_template()), et template_dirs est une liste optionnelle de répertoires à parcourir en lieu et place de TEMPLATE_DIRS.
Si un chargeur est capable de charger un gabarit avec succès, il doit retourner un tuple: (template_source, template_path). Ici, template_source est la chaîne de gabarit qui devra être compilée par le moteur de gabarit, et template_path est le chemin d’où fut chargé le gabarit. Ce chemin doit être affiché à l’utilisateur pour débugage, ainsi il doit rapidement identifer la provenance du gabarit chargé.
Si le chargeur est incapable de charger un gabarit, il doit lever django.template.TemplateDoesNotExist.
Chaque fonction de chargement doit avoir aussi un attribut de fonction is_usable. C’est un Booléen qui informe le moteur de gabarit de la disponibilité d’un chargeur dans l’installation actuelle de Python. Par exemple, le chargeur d’oeufs (qui est capable de charger des gabarits depuis des oeufs Python) fixe is_usable à False si le module pkg_ressources n’est pas installé, puisque pkg_ressources est nécessaire à la lecture des données depuis les oeufs.
Un exemple devrait aider )à clarifier tout ceci. Voici une fonction d’un chargeur de gabarit qui peut charger des gabarit depuis un fichier ZIP. Elle utilise un paramètre personnalisé, TEMPLATE_ZIP_FILES, comme chemin de recherche, au lieu de TEMPLATE_DIRS, et s’attends à ce que chaque élément sur ce chemin soit un fichier ZIP contenant des gabarits:
import zipfile
from django.conf import settings
from django.template import TemplateDoesNotExist
def load_template_source(template_name, template_dirs=None):
"""Template loader that loads templates from a ZIP file."""
template_zipfiles = getattr(settings, "TEMPLATE_ZIP_FILES",
[])
# Try each ZIP file in TEMPLATE_ZIP_FILES.
for fname in template_zipfiles:
try:
z = zipfile.ZipFile(fname)
source = z.read(template_name)
except (IOError, KeyError):
continue
z.close()
# We found a template, so return the source.
template_path = "%s:%s" % (fname, template_name)
return (source, template_path)
# If we reach here, the template couldn't be loaded
raise TemplateDoesNotExist(template_name)
# This loader is always usable (since zipfile is included with
Python)
load_template_source.is_usable = True
Le seule étape qui reste si nous désirons utiliser ce chargeur est de l’ajouter au paramètre TEMPLATE_LOADERS. Si nous plaçons ce code dans un paquet appelé mysite.zip_loader, alors nous ajoutons mysite.zip_loader.load_template_source à TEMPLATE_LOADERS.
Utiliser la référence native au gabarit
L’interface d’administration de Django inclut une référence complète à toutes les balises de gabarit et filtres disponibles pour un site donné. Elle est destinée à être un outil que les développeurs de Django fournissent aux développeurs de gabarits. Pour le voir, rendez-vous dans l’interface d’administration et cliquez sur le lien Documentation en haut à droite de la page.
La référence est divisée en quatre sections: balises, filtres, modèles et vues. Les sections balises et filtres décrivent toutes les balises embarquées (en fait, les références balise et filtre du chapitre 4 proviennent directement de ces pages) tout comme toutes bibliothèques disponibles de balise ou de filtre personnalisés.
La page vues est la plus précieuse. Ici, haque URL de votre site possède une entrée séparée. Si la vue concernée inclu un docstring, un clique sur l’URL vous affichera:
- Le nom de la fonction vue qui génère cette vue
- Une courte description de ce que fait la vue
- Le contexte, ou une liste des variables disponibles dans le gabarit de la vue
- Le nom du ou des gabarits qui sont utilisés pour cette vue
Pour un exemple détaillé de la documentaton d’une vue, lisez le code source de la vue générique object_list, qui se trouve dans django/views/generic/list_detail.py.
Puisque les sites propulsés par Django utilisent habituellement des objets base de données, les pages des modèles décrivent chaque type d’objet du système, avec tous les champs disponibles sur cet objet.
Prises ensemble, les pages de documentation doivent vous indiquer toutes les balises, filtres, varaibles et objets disponibles dans un gabarit donné.
Configuration du système de gabarit en mode autonome
Note
Cette partie intéressera uniquement les personnes qui tentent d’utiliser le système de gabarit comme un composant de sortie vers une autre application. Si vous utilisez le système de gabarit comme composant d’une application Django, l’information détaillée ici ne vous concerne pas.
Normallement, Django chargera toute les informations de configuration dont il a besoin depuis son propre fichier de configuration par défaut, combiné avec les paramètres du module donné dans la variable d’environnement DJANGO_SETTINGS_MODULE. Mais si vous utilisez le système de gabarit indépendemment du reste de Django, l’approche par variable d’environnement n’est pas très pratique, parce que vous voudrez probablement configurer le système de gabarit dans la continuité du reste de votre application plutôt que d’avoir à gérer les fichiers de paramétrage et de pointer vers eux via les variables d’environnement.
Pour résoudre ce problème, vous devez utiliser l’option de configuration manuelle décrite entièrement dans l’annexe E. En résumé, vous devez importer les composants appropriés du système de gabarit, puis, avant même d’appeler l’une des fonctions de gabarit, appeler django.conf.settings.configure() avec tous les paramètres que vous désirez préciser.
Vous devriez au moins considérer les paramètres TEMPLATE_DIRS (si vous allez utiliser les chargeurs de gabarits), DEFAULT_CHARSET (même si le utf-8 par défaut est probablement correct), et TEMPLATE_DEBUG. Tous les paramètres disponibles sont détaillés à l’annexe E, et tout paramètre commençant par TEMPLATE_ est d’un intérêt évident.
Et ensuite ?
Jusqu’à présent, ce livre considérait que le contenu que vous affichiez était du HTML. Ce n’est pas une mauvaise hypothèse pour un livre qui traite du développement web, mais viendra le moment où vous voudrez utiliser Django pour retourner d’autres types de format de données.
Le chapitre suivant décrit la façon dont vous pouvez utiliser Django pour produire des images, des PDFs, et toute autre format de données que vous pourriez imaginer.
Dernière modification: 2008-08-05 15:03:04.313997