La majoria de la teva interacció amb el llenguatge de plantilles de Django segurament hauran estat des del rol de l'autor de la plantilla. Aquest capítol aprofundeix dins el nucli del sistema de plantilles de Django; llegeix-lo si et cal estendre el sistema de plantilles, o si tens curiositat sobre com treballa internament.
Si penses en utilitzar el sistema de plantilles de Django com a part d'una altra aplicació -- p.e., sense la resta del marc-de-treball -- assegura't de llegir la secció de configuració al final del document.
Una plantilla és un document de text, o una cadena normal de Python, que te un sistema de marques del llenguatge de plantilles de Django. Una plantilla pot contenir etiquetes de bloc o variables.
Una etiqueta de bloc és un símbol dins una plantilla que fa quelcom.
Aquesta definició és deliberadament vaga. Per exemple, una etiqueta de bloc pot fer sortir contingut, servir com una estructura de control (un sentència "if" o un bucle "for"), agafen el contingut des d'una base de dades o activen l'accés a altres etiquetes de plantilla.
Les etiquetes de blocs estan envoltats per {% i %}:
{% if is_logged_in %}
Thanks for logging in!
{% else %}
Please log in.
{% endif %}
Una variables és un símbol dins una plantilla que retorna un valor.
Les etiquetes de variable estan envoltades de {{ i }}:
My first name is {{ first_name }}. My last name is {{ last_name }}.
Un context és un assignació "nom" -> "valor" (semblant al diccionari de Python) que es passa a la plantilla.
Una plantilla imprimeix un context per substituir els "forats" de la variable amb els valors del context i executant totes les etiquetes de bloc.
A baix nivell, utilitzant el sistema de plantilles amb Python és un procés de dos passos:
La forma més senzilla per crear un objecte Template és instanciant-lo directament. El contructor agafa un argument -- el codi de la plantilla:
>>> from django.template import Template
>>> t = Template("My name is {{ my_name }}.")
>>> print t
<django.template.Template object at 0x1150c70>
Darrera l'escenari
El sistema només analitza el teu codi de plantilla un cop -- quan crees l'objecte Template. A part d'aquí, el guarda internament com una estructura de "node" per rendiment.
Fins i tot el propi anàlisi és molt ràpid. La majoria de l'anàlisi succeeix via una única crida a una única i curta expressió regular.
Un cop has compilat l'objecte Template, pots imprimir un context -- o molts contextos -- amb ella. El constructor Context agafa un argument (opcional): un diccionari d'assignacions de noms de variables a valors de variables.
Crida el mètode render() de l'objecte Template amb el context per "omplir" la plantilla:
>>> from django.template import Context, Template
>>> t = Template("My name is {{ my_name }}.")
>>> c = Context({"my_name": "Adrian"})
>>> t.render(c)
"My name is Adrian."
>>> c = Context({"my_name": "Dolores"})
>>> t.render(c)
"My name is Dolores."
Els noms de les variables han de consistir en qualsevol lletra (A-Z), qualsevol dígit (0-9), un guió baix o un punt.
Els punts tenen un significat especia en la impressió de la plantilla. Un punt en un nom de variable significa operació de cerca. Específicament, quan el sistema de plantilla trobar un punt en un nom de variable, intenta un varietat d'opcions possibles. Per exemple, la variables {{ foo.bar }} podria espandir-se a qualsevol de les següents:
El sistema de plantilles utilitza la primera operació de cerca que funcioni; és una lògica del camí més curt.
Aquí hi ha uns quants exemples:
>>> from django.template import Context, Template
>>> t = Template("My name is {{ person.first_name }}.")
>>> d = {"person": {"first_name": "Joe", "last_name": "Johnson"}}
>>> t.render(Context(d))
"My name is Joe."
>>> class Person:
... def __init__(self, first_name, last_name):
... self.first_name, self.last_name = first_name, last_name
...
>>> p = Person("Ron", "Nasty")
>>> t.render(Context({"person": p}))
"My name is Ron."
>>> class Person2:
... def first_name(self):
... return "Samantha"
...
>>> p = Person2()
>>> t.render(Context({"person": p}))
"My name is Samantha."
>>> t = Template("The first stooge in the list is {{ stooges.0 }}.")
>>> c = Context({"stooges": ["Larry", "Curly", "Moe"]})
>>> t.render(c)
"The first stooge in the list is Larry."
Els mètodes de les operacions de cerca són lleugerament més complexes que els altres tipus d'operacions de cerca. Aquí hi ha algunes coses que cal tenir present:
Si l'excepció no té aquest atribut, la variable s'imprimirà com una cadena buida.
Per exemple:
>>> t = Template("My name is {{ person.first_name }}.")
>>> class Person3:
... def first_name(self):
... raise AssertionError("foo")
...
>>> p = Person3()
>>> t.render(Context({"person": p}))
Traceback (most recent call last):
...
AssertionError: foo
>>> class SilentAssertionError(AssertionError):
... silent_variable_failure = True
...
>>> class Person4:
... def first_name(self):
... raise SilentAssertionError("foo")
...
>>> p = PersonClass4()
>>> t.render(Context({"person": p}))
"My name is ."
Fixa't que django.core.exceptions.ObjectDoesNotExist, que és la classe base de totes les excepcions DoesNotExist de la API Django, té silent_variable_failure = True. Així si utilitzes les plantilles Django amb els objectes model de Django, qualsevol excepció DoesNotExist serà silenciosa.
I will now delete this valuable data. {{ data.delete }}
Per evitar això, posa un atribut de funció alters_data en el mètode. El sistema de plantilles no podrà executar un mètode si el mètode té activat el alters_data=True:
def sensitive_function(self): self.database_record.delete() sensitive_function.alters_data = True
Els mètodes generats dinàmicament delete() i save() dels objectes del model Django tenen el alters_data=True automàticament, per exemple.
Generalment, si una variable no existeix, el sistema de plantilles inserta el valor del paràmetre de configuració TEMPLATE_STRING_IF_INVALID, que per defecte té el valor d'una cadena buida.
Els filtres que s'apliquen a una variable invàlida només seran aplicats si TEMPLATE_STRING_IF_INVALID té el seu valor per defecte. Si TEMPLATE_STRING_IF_INVALID val qualsevol altre valor, els filtres de les variables seran ignorats.
Aquesta característiques són lleugerament diferents per les etiquetes de plantilla if, for i regroup. Si una variable invàlida és proporcionada a una d'aquestes etiquetes de plantilla, la variable s'interpretarà com a None. Els filtres sempre són per variables invàlides dins d'aquestes etiquetes plantilles.
La majoria de les vegades, instanciaràs objectes Context passant-lo com un diccionari complet a Context(). Però pots afegir i esborrar elements de l'objecte Context un cop ha estat instanciat, també, utilitzant la sintaxi del diccionari estàndard:
>>> c = Context({"foo": "bar"})
>>> c['foo']
'bar'
>>> del c['foo']
>>> c['foo']
''
>>> c['newvariable'] = 'hello'
>>> c['newvariable']
'hello'
A més, un objecte Context actua com una pila. Això és, pots afegit (push()) i extreure (pop()) contextes addicionals dins de la pila. Totes les operacions succeeixen a la part alta de la pila, però les operacions de cerca a la pila es fan des de dalt a baix fins que es trobi el valor.
Si extreus (pop()) masses, aconseguiràs una django.template.ContextPopException.
Aquí hi ha un exemple de com aquests múltiples nivells treballen:
# Create a new blank context and set a simple value: >>> c = Context() >>> c['foo'] = 'first level' # Push a new context onto the stack: >>> c.push() >>> c['foo'] = 'second level' # The value of "foo" is now what we set at the second level: >>> c['foo'] 'second level' # After popping a layer off, the old value is still there: >>> c.pop() >>> c['foo'] 'first level' # If we don't push() again, we'll overwrite existing values: >>> c['foo'] = 'overwritten' >>> c['foo'] 'overwritten' # There's only one context on the stack, so pop()ing will fail: >>> c.pop() Traceback (most recent call last): ... django.template.ContextPopException
Utilitzant un Context com a pila pot ser interesant en algunes etiquetes de plantilla personalitzada, com veuràs posteriorment.
Django ve amb una classe Context especial, django.template.RequestContext, que actua lleugerament diferents que el django.template.Context normal. La primera diferència és que pren un objecte HttpRequest (mireu el Capítol XXX) com a primer argument:
c = RequestContext(request, {
'foo': 'bar',
}
La segona diferència és que aquest automàticament publica el context amb uns quantes variables, d'acord amb el paràmetre de configuració TEMPLATE_CONTEXT_PROCESSORS.
El paràmetre TEMPLATE_CONTEXT_PROCESSORS és una tupla de crides anomenades processadors de context que agafen una objecte petició com a argument i retornen un diccionari d'elements que poden ser combinat dins del context. Per defecte, aquest paràmetre de configuració TEMPLATE_CONTEXT_PROCESSORS val:
("django.core.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n")
Cada processador s'aplica en ordre. És a dir, si un processador afegeix una variable en el context i el segon processador afegeix una variable amb el mateix nom, el segon sobre-escriurà al primer. Els processadors que venen per defecte s'expliquen després.
També, pots donar al RequestContext una llista de processadors addicionals, utilitzant el tercer argument opcional, processors. En aquest exemple, la instància RequestContext obté una variable ip_address:
def ip_address_processor(request):
return {'ip_address': request.META['REMOTE_ADDR']}
def some_view(request):
# ...
return RequestContext(request, {
'foo': 'bar',
}, processors=[ip_address_processor])
Aquí s'explica el que fa cada processador:
Si TEMPLATE_CONTEXT_PROCESSORS conté aquest processador, cada RequestContext contindrà aquestes tres variables
user
Una instància django.contrib.auth.models.User representant l'usuari actualment identificat (o una instància de AnonymousUser, si el client no està identificat).
messages
Una llista de missatges (com a cadenes) per l'usuari actualment identificat. Darrera de les escenes, aquest crida request.user.get_and_delete_messages() per cada petició. Aquest mètode recol·lecta els missatges de l'usuari i els esborra de la base de dades.
Fixa't que els missatges es creen amb user.add_message().
perms
Una instància de django.core.context_processors.PermWrapper, representant els permisos que l'usuari té.
Mira el Capítol per saber-ne més sobre els usuaris, permisos, i missatges.
Aquest processador posa informació de depuració sota la capa de la plantilla. Si està activar, només funcionarà si:
Si aquestes condicions són certes, els següents variables estaràn activades:
debug
Posat a True; pots utilitzar això en les plantilles per comprovar si estàs en mode DEBUG.
sql_queries
Una llista de diccionaris {'sql': ..., 'time': ...}, representant cada consulta SQL que s'ha realitzat durant la petició i quant de temps ha transcorregut. La llista està en l'ordre de les consultes.
Si aquest processador està activat, cada RequestContext contindrà aquestes dues variables:
LANGUAGES
El valor del paràmetres LANGUAGES
LANGUAGE_CODE
request.LANGUAGE_CODE, si existeix. Altrament, el valor del paràmetres LANGUAGE_CODE.
L'Apèndix XXX té més informació sobre aquests dos paràmetres.
Si està activat, cada RequestContext contindrà una variable request, que és l'objecte HttpRequest actual. Fixa't que aquest processador no està activat per defecte; l'hauràs d'activar.
Normalment, guardes plantilles en fitxers del sistema de fitxers (o en altres llocs si has escrit carregadors de plantilles personalitzats) en comptes d'usar la pròpia API de baix nivell Template.
Django cerca pels directoris de plantilles en un nombre de llocs, depenent del teu paràmetre de configuració template-loader (mira més avall "Tipus de Carrgadors"), però la forma més bàsica d'especificar els directoris de plantilles és utilitzant el paràmetre TEMPLATE_DIRS.
Aquest podria ser una llista o tupla de cadenes de caràcters que conté els camins complerts dels teus directoris de plantilles:
TEMPLATE_DIRS = ( "/home/html/templates/lawrence.com", "/home/html/templates/default", )
Les plantilles poden ser allí on vulguis, mentre siguin directoris que puguin llegir-se pel servidor Web. Poden tenir qualsevol extensió, com .html o .txt o poden no tenir cap extensió.
Fixa't que aquests camins haurien s'usar-se les barres de l'style Unix, fins i tot dins a Windows.
Django té dos formes de carregar les plantilles des dels fitxers:
django.template.loader.get_template(template_name)
get_template retorna la plantilla compilada (un objecte Template) per la plantilla amb el nom indicat. Si la plantilla no existeix, salta una excepció django.template.TemplateDoesNotExist.
django.template.loader.select_template(template_name_list)
select_template és com get_template, excepte que pren una llista de noms de plantilles. De la llista, retorna la primera plantilla que existeixi.
Per exemple, si crides a get_template('story_detail.html') i té el paràmetre TEMPLATE_DIRS anterior, aquests seran els fitxers que Django buscarà, per ordre:
Si crides a select_template(['story_253_detail.html', 'story_detail.html']), així és com Django la buscarà:
Quan Django troba una plantilla que existeix, atura la cerca.
extrem
Pots utilitzar select_template() per la super-flexible" "plantillabilitat". Per exemple, si estàs escrivint unes noticies i vols que algunes històries tinguin plantilles personalitzades, utilitza quelcom com això: select_template(['story_%s_detail.html' % story.id, 'story_detail.html']). Que et permetrà utilitzar una plantilla personalitzada per a una història individual, amb una plantilla per defecte per a històries que no tenen plantilles personalitzades.
És possible -- i preferible -- organitzar plantilles en subdirectoris del directori plantilla. La convenció és crear un subdirectori de cada aplicació Django, amb subdirectoris dins aquests subdirectoris que es necessitin.
Fer això per la teva pròpia salut. Guardar totes les plantilles en el nivell arrel d'un únic directori és brut.
Per carregar una plantilla que és dins un subdirectori, només has d'usar una barra, així:
get_template('news/story_detail.html')
Utilitzant el mateix paràmetre TEMPLATE_DIRS anterior, aquest exemple de crida get_template() intentaria carregar les següents plantilles:
Per defecte, Django carrega les plantilles del sistema de fitxers, però Django ve amb uns quants carregadors de plantilles que saben com carregar plantilles d'altres orígens.
Alguns d'aquests altres carregadors estan desactivats per defecte, però pots activar-los editant el teu paràmetre de configuració TEMPLATE_LOADERS. TEMPLATE_LOADERS ha de ser una tupla de cadenes, on cada cadena representa un carregador de plantilles. Els carregadors de plantilles que van amb Django són:
django.template.loaders.filesystem.load_template_source
Carrega les plantilles des del sistema de fitxers, d'acord amb el paràmetre TEMPLATE_DIRS.
Aquest carregador ve activar per defecte.
django.template.loaders.app_directories.load_template_source
Carrega plantilles des de les aplicacions de Django en el sistema de fitxers. Per cada aplicació a INSTALLED_APPS, el carregador busca en el subdirectori templates. Si el directori existeix, Django hi busca les plantilles.
Això vol dir que pots guardar plantilles dins de les aplicacions individual. Això fa que sigui fàcil distribuir aplicacions Django amb plantilles per defecte.
Per exemple, if INSTALLED_APPS conté ('myproject.polls', 'myproject.music'), llavors get_template('foo.html') buscarà les plantilles en aquest ordre:
Fixa't que el carregador processa una optimització quan és el primer importat: Aquest guarda en memòria una llista de quins paquets INTALLED_APPS tenen el subdirectori templates.
Aquest carregador ve activat per defecte.
django.template.loaders.eggs.load_template_source
Com l'anterior app_directories, però aquest carrega plantilles de les ous de Python en comtes de des del sistema de fitxers.
Aquest carregador ve desactivat per defecte; necessitaràs activar si estàs utilitzant ous per distribuir la teva aplicació.
Django utilitza els carregadors de plantilla en l'odre del paràmetre TEMPLATE_LOADERS. Aquest utilitza cada carregador fins que es troba la plantilla.
Encara que el llenguatge de plantilles Django ve amb moltes etiquetes i filtres per defecte, pots voler escriure les teves pròpies, i és fàcil de fer-ho.
Primer, crea un paquet templatetags en el paquet apropiat de l'aplicació Django. Aquest hauria d'estar en el mateix nivell que moldes.py, views.py, etc. Per exemple:
polls/ models.py templatetags/ views.py
Afegeix dos fitxers al paquet templatetags: un fitxer __init__.py (per indicar a Python que aquest és un mòdul que conté codi Python) i un fitxer que contindrà les teves pròpies etiquetes i filtres personalitzats.
El nom de l'últim fitxer és el nom que utilitzarà per carregar després les etiquetes. Per exemple, si les teves etiquetes i filtres personalitzats estan en el fitxer anomenat poll_extras.py, hauràs de fer el següent en la plantilla:
{% load poll_extras %}
L'etiqueta {% load %} mira si el paràmetre INSTALLED_APPS i només permet la càrrega de les llibreries de plantilla dins de les aplicacions instal·lades a Django. Això és una característica de seguretat: Això et permet hostatjar codi Python de moltes llibreries de plantilles en un únic ordinador sense activar l'accés des de totes les instal·lacions Django.
Si escrius una llibreria de plantilles que no està lligada a cap vista o model en particular, és perfectament correcta tenir un paquet d'aplicació Django que només contingui un paquet templatetags.
No hi ha cap límit en quants mòduls pots posar en el paquet templatetags. Només tingues present que una sentència carregarà els filtres i etiquetes del nom del mòdul Python indicat, no del nom de l'aplicació.
Un cop has creat aquest mòdul Python, només hauràs d'escriure un mica de codi Python, depenent de si has escrit filtres o etiquetes.
Per tenir una llibreria d'etiquetes vàlides, el mòdul conté una variable de nivell de mòdul anomenada register que és una instància template.Library, en que les etiquetes i filtres estan registrats. Així, prop de l'inici del mòdul, poseu el següent:
from django import template register = template.Library()
Darrera l'escenari
Per a un munt d'exemples, llegeix el codi font dels filtres i etiquetes per defecte de Django. Són a django/template/defaultfilters.py i django/template/defaulttags.py, respectivament.Les aplicacions de django.contrib també contenen numerosos exemples.
Els filtres personalitzats són només funcions Python que agafen un o dos arguments:
Per exemple, en el filtre {{ var|foo:"bar" }}, el filtre foo li seria passada la variable var i l'argument "bar".
Les funcions filtres sempre haurien de retornar alguna cosa. No haurien de generar excepcions i si passa alguna cosa, haurien de ser silenciones. Si hi ha un error, haurien de retornar l'entrada original o una cadena buida -- o alguna cosa amb més sentit.
Aquí hi ha un exemple de definició de filtre:
def cut(value, arg): "Removes all values of arg from the given string" return value.replace(arg, '')
I aquí hi ha un exemple de com aquest filtre s'ha d'usar:
{{ somevariable|cut:"0" }}
La majoria dels filtres no agafen arguments. En aquest cas, només cal que deixis l'argument fora de la funció:
def lower(value): # Only one argument. "Converts a string into all lowercase" return value.lower()
Quan has escrit la teva funció de filtre, necessites registrar-la amb la teva instància Library, per fer-la accessible al llenguatge de plantilles de Django:
register.filter('cut', cut)
register.filter('lower', lower)
El mètode Library.filter() pren dos arguments:
Si estàs usant Python 2.4 o major, pots usar register.filter() com un decorador:
@register.filter(name='cut') def cut(value, arg): return value.replace(arg, '') @register.filter def lower(value): return value.lower()
Si no poses l'argument name, com en el segon exemple, Django usarà el nom de la funció com a nom de filtre.
Les etiquetes són més complexes que els filtres, perquè les etiquetes poden qualsevol cosa.
Abans, aquest capítol descrivia com les sistema de plantilles funciona en dos passos: compilant i imprimint. Per definir una etiqueta de plantilla personalitzada, necessites dir-li a Django com gestionar ambdós passos quan agafa la teva etiqueta.
Quan Django compila una plantilla, separa el text de la plantilla dins de "nodes". Cada node és una instància de django.template.Node i té un mètode render(). Així, una plantilla compilada és simplement un llistat d'objecte Node
Quan crides render() en una plantilla compilada, la plantilla crida el render() de cada Node del llistat de nodes, que té el context. Els resultats són tots els concatenats junts de al formulari de sortida de la plantilla.
Així, per definir una etiqueta de plantilla personalitzada, especifica com l'etiqueta de la plantilla es converteix dins un Node (la funció de compilació), i el mètode render() què fa.
Per cada etiqueta de plantilla que l'analitzador de plantilla troba, crida una funció Python amb el contingut de l'etiqueta i el propi objecte analitzador. Aquest funció és la responsable de retornar una instància Node basada en els continguts de l'etiqueta.
Per exemple, escriu l'etiqueta {% current_time %}, que mostra la data i hora actual, formatada d'acord a un paràmetres donat per l'etiqueta, amb la sintaxi strftime (mira http://www.python.org/doc/current/lib/module-time.html#l2h-1941). És bona idea decidir la sintaxi de l'etiqueta abans de qualsevol altra cosa. En el nostre cas, diguem que l'etiqueta s'utilitzarà així:
<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
Nota
Si, aquesta etiqueta de plantilla és redundant; l'etiqueta {% now %} per defecte de Django fa la mateixa tasca amb una sintaxi més simple. Només és un exemple.
L'analitzador d'aquesta funció hauria de gravar el paràmetre i crear un objecte 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:
raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents[0])
return CurrentTimeNode(format_string[1:-1])
Realment, aquí hi ha molta cosa:
El segon pas en la creació d'etiquetes personalitzades és definir una subclasse de Node que té un mètode render(). Continuant amb l'exemple anterior, necessitem definir CurrentTimeNode:
import datetime class CurrentTimeNode(template.Node): def __init__(self, format_string): self.format_string = format_string def render(self, context): return datetime.datetime.now().strftime(self.format_string)
Les dues funcions (__init__ i render) equivalen directament als dos passos del processat de plantilla (compilació i impressió). Així, la funció d'inicialització només cal per guardar la cadena de format per usar més tard, i la funció render() fa la feina real.
Com en els filtres de plantilla, aquestes funció d'impressió haurien de fallar de forma silenciosa si hi ha algun error. L'únic moment que es poden generar errors és durant la compilació.
Finalment, necessites registrar l'etiqueta amb la tema instància Library del mode, com s'ha explicat anteriorment a "Escrivint filtres de plantilla":
register.tag('current_time', do_current_time)
El mètode tag() pren dos arguments:
Com amb el registre del filtre, també és possible utilitzar aquesta com un decorador a Python 2.4 en endavant:
@register.tag(name="current_time") def do_current_time(parser, token): # ... @register.tag def shout(parser, token): # ...
Si deixes l'argument name, com en el segon exemple anterior, Django utilitzarà el nom de la funció com a nom de la etiqueta.
Els exemples anteriors simplement treuen un valor. Sovint és útil establir variables de plantilla en comptes de valors de sortida. D'aquesta forma, els autors de les plantilles poden simplement usar els valors que han creat les teves etiquetes de plantilla.
Per establir una variable en el context, només cal que usis l'assignament de diccionari sobre l'objecte del context en el mètode render(). Aquí hi ha una versió actualitzada de CurrentTimeNode que activa una variable de plantilla current_time en comptes de treure el resultat:
class CurrentTimeNode2(template.Node): def __init__(self, format_string): self.format_string = format_string def render(self, context): context['current_time'] = datetime.datetime.now().strftime(self.format_string) return ''
Fixa't que render() retornar una cadena buida; render() sempre ha de retornar una cadena, si totes les etiquetes de plantilla només activen una variable, render() hauria de retornar un cadena buida.
Aquí es veu com s'utilitza la nova versió de l'etiqueta:
{% current_time "%Y-%M-%d %I:%M %p" %}
<p>The time is {{ current_time }}.</p>
Però, hi ha un problema amb CurrentTimeNode2: el nom de variable current_time està codificat en el codi. Això significa que necessites estar segur que la teva plantilla no usa {{ current_time }} enlloc més, perquè el {% current_time %} sobre-escriure el valor de la variable.
Una solució més neta és fer una etiqueta de plantilla específica el nom de la variable de sortida, així:
{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>
Per fer això, necessitaràs recodificar tant la funció de compilació i la classe Node, així:
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):
context[self.var_name] = datetime.datetime.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:
raise template.TemplateSyntaxError("%r tag requires arguments" % token.contents[0])
m = re.search(r'(.*?) as (\w+)', arg)
if m:
format_string, var_name = m.groups()
else:
raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name)
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name)
return CurrentTimeNode3(format_string[1:-1], var_name)
Ara, do_current_time() grava la cadena de format i el nom de la variable, passant ambdós paràmetres a CurrentTimeNode3.
Les etiquetes de plantilla funcionen com blocs que contenen altres etiquetes. Per exemple, l'etiqueta estàndard {% comment %} amaga tot el que troba fins {% endcomment %}.
Per crear una etiqueta de plantilla com aquesta, utilitza parser.parse() en la teva funció de compilació.
Aquí es mostra com està implementada l'etiqueta estàndard {% comment %}:
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() agafa una tupla de noms d'etiquetes de bloc per analitzar-les fins allí. Retorna una instància de django.template.NodeList, que és una llista de tots els objectes Node que l'analitzador ha trobat abans de qualsevol de les etiquetes indicades en la tupla.
Així en l'exemple anterior, nodelist és una llista de tots els nodes entre les etiquetes {% comment %} i {% endcomment %}, sense incloure els propis {% comment %} i {% endcomment %}.
Després que parser.parser() sigui cridat, l'analitzador encara no ha "consumit" l'etiqueta {% endcomment %}, així que s'ha de cridar a parser.delete_first_token() per evitar que aquesta etiqueta es torni a processar.
Després, CommentNode.render() simplement retornar una cadena buida. Qualsevol cosa entre {% comment %} i {% endcomment %} s'ignora.
En l'exemple previ, do_comment() descarta tot el que hi ha entre {% comment %} i {% endcomment %}. En comptes de fer això es possible fer quelcom amb el codi entre les etiquetes.
Per exemple, aquí hi ha una etiqueta de plantilla personalitzada {% upper %}, que passa a majúscules tot el que troba entre ella i {% endupper %}:
{% upper %}
This will appear in uppercase, {{ your_name }}.
{% endupper %}
Com en l'exemple anterior, utilitzarem parser.upper(). Aquesta vegada, passarem el nodelist resultant a el Node:
@register.tag
def do_upper(parser, token):
nodelist = parser.parse(('endupper',))
parser.delete_first_token()
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()
L'únic concepte nou que hi ha és el self.nodelist.render(context) a UpperNode.render().
Per més exemples de l'impressió complexa, mireu el codi font de {% if %}, {% for %}, {% ifequal %} i {% ifchanged %}. Aquestes són a django/template/defaulttags.py.
Moltes etiquetes de plantilla només agafen un únic argument -- una cadena o una referència a una variable de plantilla -- i retornen una cadena després de fer algun processat basat només en l'argument d'entrada i algunes informació externa. Per exemple, l'etiqueta current_time estaria escrita en aquesta varietat: nosaltres li donarem un format de cadena, retornant l'hora en una cadena.
Per facilitar la creació d'aquests tipus d'etiquetes, Django proporciona una funció d'ajuda, simple_tag. Aquesta funció, que és un mètode de django.template.Library, agafa una funció que accepta un argument i l'embolcalla en una funció render i en altres elements necessaris mencionats anteriorment i registrar-lo en el sistema de plantilles.
La nostra anterior funció current_time hauria d'escriure's així:
def current_time(format_string): return datetime.datetime.now().strftime(format_string) register.simple_tag(current_time)
A Python 2.4, la sintaxi decorador també funciona:
@register.simple_tag def current_time(token): ...
Unes quantes coses que cal fixar-se sobre la funció d'ajuda simple_tag:
Un altre tipus d'etiqueta comuna és la que mostra algunes dades imprimint una altra plantilla.
Per exemple, la interfície d'administració Django utilitza etiquetes personalitzades per mostrar els botons al final de les pàgines de formulari "afegit/canviar". Aquestes botons sempre són els mateixos, però els enllaços de destí canvien depenent de l'operació que es realitzi. És un cas perfecte per utilitzar una petita plantilla que s'omplirà amb els detalls del nostre objecte actual.
Aquests conjunt d'etiquetes s'anomenen etiquetes d'inclussió.
Escriure etiquetes d'inclussió és demostra millor amb un exemple. Escriu una etiqueta que tregui una llista de les opcions de un objecte Poll de múltiple selecció. Utilitzarem l'etiqueta així:
{% show_results poll %}
... i la sortida serà quelcom així:
<ul> <li>First choice</li> <li>Second choice</li> <li>Third choice</li> </ul>
Primer, definim la funció que agafarà l'argument i produirà un diccionari de dades com a resultat. Fixa't que només necessitem retornar un diccionari, no té cap més complexitat. Aquest s'utilitzarà com el context per al fragment de plantilla:
def show_results(poll):
choices = poll.choice_set.all()
return {'choices': choices}
Després, crearem la plantilla utilitzada per imprimir la sortida de l'etiqueta. seguint el nostre exemple, la plantilla és molt simple:
<ul>
{% for choice in choices %}
<li> {{ choice }} </li>
{% endfor %}
</ul>
Finalment, crearem i registrarem l'etiqueta d'inclussió cridant el mètode inclusion_tag() de l'objecte Library.
Seguint amb el nostre objecte, si la plantilla anteriors és en un fitxer anomenat polls/result_snippet.html, registrarem l'etiqueta així:
register.inclusion_tag('polls/result_snippet.html')(show_results)
Com sempre, la sintaxi del decorador de Python 2.4 també funciona, podríem fer-ho així:
@register.inclusion_tag('results.html')
def show_results(poll):
...
Algunes vegades, les teves etiquetes incloses necessiten accedir al context de la plantilla pare.
Per solucionar això, Django proporciona una opció takes_context per les etiquetes d'inclusió. si especifiques takes_context en la creació de l'etiqueta de plantilla, l'etiqueta no necessitarà arguments, i la funció de sota de Python tindrà un argument -- el context de la plantilla com quan l'etiqueta s'ha cridat.
Per exemple, diguem que estàs escrivint una etiqueta d'inclusió que sempre s'usarà en un context que conté les variables home_link i home_title que apunten a la pàgina principal. Aquí veiem el que la funció de Python ha de fer:
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
return {
'link': context['home_link'],
'title': context['home_title'],
}
Nota
El primer paràmetre de la funció ha d'anomenar-se context.
La plantilla link.html ha de contindre:
Jump directly to <a href="{{ link }}">{{ title }}</a>.
Llavors, cada cop que tu utilitzis l'etiqueta personalitzada, carregarà aquesta llibreria i la cridarà sense cap argument, així:
{% jump_link %}
Fixa't que quan utilitzes takes_context=True, no cal passar arguments a l'etiqueta de plantilla. Automàticament accedirà al context.
Els carregadors de plantilles dins de Django normalment cobreixen les necessitats de càrrega de plantilles, però és molt fàcil escriure'n el teu propi si necessites una lògica de càrrega especial.
Un carregador de plantilla -- que é, cada entrada en el paràmetre TEMPLATE_LOADERS -- s'espera que sigui un tipus cridable amb aquesta interfície:
load_template_source(template_name, template_dirs=None)
L'argument template_name és el nom de la plantilla a carregar (com passa a loader.get_template() o loader.select_template()), i template_dirs és una llista opcional de directoris per cercar en comptes de TEMPLATE_DIRS.
Si el carregador es capaç de carregar correctament una plantilla, aquest hauria de retornar una tupla: (template_source, template_path). Aquí template_source és la cadena de la plantilla que serà compilada per el motor de la plantilla, i template_path és el camí des d'on s'ha carregat la plantilla. Aquest camí podria ser mostrat a l'usuari per supòsits de depuració, així podries identificar ràpidament d'on s'ha carregat la plantilla.
Si el carregador no es capaç de carregar una plantilla, enviarà una excepció django.template.TemplateDoesNotExists.
Cada funció del carregador ha de tenir un atribut de funció is_usable en l'actual instal·lació Python.
Per exemple, el carregador d'ous (que és capaç de carregar plantilles des dels ous) posa is_usable a False si el mòdul pkg_resources no està instal·lat, perquè pck_resources és necessari per llegir informació des dels ous.
Un exemple podria ajudar a clarificar totes aquestes coses. Aquí hi ha una funció de carregador de plantilla que pot carregar plantilles des d'un fitxer ZIP. Utilitza un paràmetre personalitzat, TEMPLATE_ZIPS_FILES com un cerca de fitxers en comptes de TEMPLATE_DIRS, i espera que cada element de la llista sigui un fitxer ZIP que contingui plantilles:
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.""" # Lookup ZIP file list from settings if it's not already given. if template_zipfiles is None: 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 # 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 a Python standard library function) load_template_source.is_usable = True
L'únic pas que falta si volem utilitzar aquest carregador és afegir-lo al paràmetre TEMPLATE_LOADERS. Si posem aquest codi en un mòdul anomenat myproject.zip_loader, llavors haurem d'afegir myproject.zip_loader.load_template_source a TEMPLATE_LOADERS.
La interfície d'administrador Django inclou una referència completa de totes les etiquetes de plantilla i filtres disponibles per un lloc. Està dissenyat per ser una eina que els programadors Django donen als desenvolupadors de plantilles. Per veure'ls, ves a la teva interfície d'administració i fes clic a l'enllaç de "Documentació" en la cantonada superior dreta de la pàgina.
La referència està dividida en 4 seccions: etiquetes, filtres, models i vistes.
Les seccions etiquetes i filtres descriuen totes les etiquetes internes (de fet, les referències d'etiquetes i filtres de sota venen directament d'aquestes pàgines) igual que qualsevol etiqueta i filtre personalitzat que estigui disponible.
La pàgina views és la més valuosa. Cada URL del teu lloc hi té una entrada a part, i fent clic en la URL et mostra:
Cada pàgina de documentació de vista també té un preferit que pots usar per saltar des de qualsevol pàgina a la pàgina de documentació per aquesta vista.
Ja que els llocs fets amb Django normalment usen objecte de bases de dades, la secció moidels de la pàgina de documentació descriu cada tipus d'objecte del sistema amb totes els camps disponibles d'aquest objecte.
Ajuntant-ho tot, les pàgines de la documentació han de dir-te quines etiquetes, filtres, variables i objectes hi ha disponibles en una plantilla concreta.
Nota
Aquesta secció només és d'interès per gent que vulgui utilitzar el sistema de plantilles com a component de sortida d'una altra aplicació. si estàs usant el sistema de plantilles com a part d'una aplicació Django, res d'això no t'afecta.
Normalment, Django carregarà tota la informació de configuració que necessita des del propi fitxer de configuració, combinat amb els paràmetres del mòdul donats en la variable d'entorn DJANGO_SETTINGS_MODULE. Però si estàs usant el sistema de plantilles independentment de la resta de Django, la variable d'entorn no és massa convenient, perquè probablement voldràs configurar el sistema de plantilla en línia amb la resta de les teves aplicacions en comptes d'utilitzar fitxers de configuració i apuntar-hi via variables d'entorn.
Per solucionar aquest problema, necessites usar el manual de configuració descrit en l'Apèndix XXX.
Simplement importa les peces apropiades del sistema de plantilles i llavors, abans de cridar a les funcions de plantilla, crida a django.conf.settings.configure() amb qualsevol paràmetres que hi vulguis especificar.
Podries voler considerar els paràmetres TEMPLATE_DIRS (si vas a utilitzar els carregadors de plantilles), DEFAULT_CHARSET (encara que el valor per defecte utf-8 et funcioni) i TEMPLATE_DEBUG. Tots els paràmetres disponibles estan descrits en el Capítol XXX, i qualsevol paràmetre que comenci amb TEMPLATE_ segurament et serà d'interès.