Plantillas – Templates

No es una buena idea codificar el HTML directamente en las vistas.

Es mucho más limpio y más fácil de mantener separar el diseño de la página del código Python. Podemos hacer esto con el sistema de plantillas de Django.

Base del Sistema de Plantillas


Una plantilla de Django es una cadena de texto para separar la presentación de un documento de sus datos. Una plantilla define contenedores y varios bits de lógica básica (etiquetas) que regulan la forma en que el documento debe ser mostrado. Por lo general las plantillas se utilizan para producir HTML, pero las plantillas Django plantillas son igualmente capaces de generar cualquier otro formato basado en texto.

Comencemos con una plantilla sencilla de ejemplo. Esta plantilla Django describe una página HTML que, da las gracias a una persona por hacer un pedido a la empresa.

<html>

<head><title>Ordering notice</title></head>

<body>

<h1>Ordering notice</h1>

<p>Dear {{ person_name }},</p>

Thanks for placing an order from {{ company }}. It’s scheduled to
ship on {{ ship_date|date:”F j, Y” }}.

Here are the items you’ve ordered:
<ul>
{% for item in item_list %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% if ordered_warranty %}

Your warranty information will be included in the packaging.

{% else %}

You didn’t order a warranty, so you’re on your own when
the products inevitably stop working.

{% endif %}

Sincerely,
{{ company }}

Esta plantilla es HTML básico con algunas variables y etiquetas de plantilla dentro de ella. Cualquier texto rodeado por un par de llaves (por ejemplo, {{ person_name }}) es una variable. Esto significa “insertar el valor de la variable con el nombre dado.” ¿Cómo podemos especificar los valores de las variables?Llegaremos a eso después.

Cualquier texto que está rodeado de llaves y porcentaje (por ejemplo, {% if ordered_warranted %}) es una etiqueta de plantilla. La definición de una etiqueta es bastante amplia: una etiqueta sólo le dice al sistema de plantillas “haz algo”.

Por último, el segundo párrafo de esta plantilla contiene un ejemplo de filtro, que es la forma más conveniente de modificar el formato de una variable. En este ejemplo, {{ ship_date | date: “F j, Y” }}, estamos pasandole a la variable ship_date el filtro date, dándole al filtro date el argumento “F j, Y”. El formato de filtro date formatea las fechas a un formato dado, como se especifica en el argumento. Los filtros son adjuntados con un carácter de canalización (|).

Cada plantilla Django tiene acceso a varias etiquetas y filtros incorporados, muchos de los cuales serán discutidos en las siguientes secciones.

Usando el sistema de plantillas

Entremos en el sistema de plantillas de Django para que cómo funciona, pero no estamos todavía para integrarlo con las vistas que hemos creado en el capítulo anterior. Nuestro objetivo aquí es mostrar cómo funciona el sistema independiente del resto de Django. (Por lo general vamos a usar el sistema de plantillas dentro de una vista de Django, pero hay que dejar claro que el sistema de plantillas es sólo una librería de Python que se puede utilizar en cualquier lugar, no sólo en las vistas de Django.)

Esta es la forma más básica en que se puede utilizar el sistema de plantillas de Django:

  1. Crear un objeto Template proporcionando el código de la plantilla raw como una cadena.
  2. Llamar al método render () del objeto Template con un conjunto de variables (el contexto). Esto devuelve una plantilla renderizada completamente como una cadena, con todas las variables y etiquetas de plantilla evaluadas de acuerdo al contexto.

>>> from django import template
>>> t = template.Template(‘My name is {{ name }}.’)
>>> c = template.Context({‘name’: ‘Adrian’})
>>> print t.render(c)
My name is Adrian.

>>> c = template.Context({‘name’: ‘Fred’})
>>> print t.render(c)
My name is Fred.

Crear objetos Template

La forma más fácil de crear un objeto Template es crear una instancia directamente. La clase Template reside en el módulo django.template, y el constructor toma un argumento, el código de plantilla raw.

Veamos algunos aspectos básicos del sistema de plantillas:

>>> from django.template import Template
>>> t = Template(‘My name is {{ name }}.’)
>>> print t

Si lo estamos haciendo de forma interactiva, veremos algo como esto:

<django.template.Template object at 0xb7d5f24c>

Cuando se crea un objeto Template, el sistema de plantillas compila el código de la plantilla raw internamente de forma optimizada, preparándolo para el renderizado. Pero si el código de la plantilla incluye cualquier error de sintaxis, la llamada a Template() producirá una excepción TemplateSyntaxError.

Renderizar un Template

Una vez que tiene un objeto Template, puede pasarle datos, dándole un contexto. Un contexto es simplemente un conjunto de nombres de variables de plantilla y sus valores asociados. Una plantilla usa un contexto para rellenar sus variables y evaluar sus etiquetas.

Un contexto es representado en Django por la clase Context, que reside en el módulo django.template. Su constructor toma un argumento opcional: un diccionario que mapea los nombres de variables a los valores de las variables. Llamar al método render() del objeto Template con el contexto para rellenar la plantilla:

>>> from django.template import Context, Template
>>> t = Template(‘My name is {{ name }}.’)
>>> c = Context({‘name’: ‘Stephane’})
>>> t.render(c)
u’My name is Stephane.’

Debemos señalar aquí que el valor de retorno de t.render(c) es un objeto Unicode – no una cadena Python normal.  Django utiliza objetos Unicode en lugar de cadenas normales en todo el framework.

He aquí un ejemplo de compilar y renderizar una plantilla, usando una plantilla similar a la ejemplo del principio de este capítulo:

>>> from django.template import Template, Context
>>> raw_template = “””

Dear {{ person_name }},


… <p>Thanks for placing an order 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>

… {% else %}
… <p>You didn’t order a warranty, so you’re on your own when
… the products inevitably stop working.</p>

… {% endif %}

… <p>Sincerely, <br/>{{ company }} <p> “””
>>> t = Template(raw_template)
>>> import datetime
>>> c = Context({‘person_name’: ‘John Smith’,
… ‘company’: ‘Outdoor Equipment’,
… ‘ship_date’: datetime.date(2009, 4, 2),
… ‘ordered_warranty’: False})
>>> t.render(c)
u” <p>Dear John Smith,</p>nn<p>Thanks for placing an order from Outdoor Equipment. It’s scheduled tonship on April 2, 2009.</p>nnn<p>You didn’t order a warranty, so you’re on your own whennthe products inevitably stop working.</p>nnn<p>Sincerely,Outdoor Equipment</p>”

  1. Primero importamos las clases Template y Context, ambas residen en el módulo django.template.
  2. Guardamos el texto raw de nuestra plantilla en la variable raw_template. Tenga en cuenta que usamos comillas triples para designar a la cadena, ya que se extiende por varias líneas, por contra, las cadenas entre comillas sencillas no pueden ser envueltas en varias líneas.
  3. A continuación creamos un objeto plantilla, t, pasando raw_template al constructor de la clase Template.
  4. Importamos el módulo de fecha y hora de la librería estándar de Python, porque la necesitaremos en la siguiente declaración.
  5. Creamos un objeto Context, c. El constructor de Context, toma un diccionario Python, que mapea los nombres de variable a los valores. Aquí, por ejemplo, se especifica que persona_name es ‘John Smith’, company es ‘Outdoor Equipment “, y así sucesivamente.
  6. Por último, llamamos al método render () de nuestro objeto plantilla, pasándole el contexto. Esto devuelve la plantilla renderizada, es decir, que reemplaza las variables de la plantilla con los valores actuales de las variables, y ejecuta cualquier etiqueta de la plantilla.

Esos son los fundamentos del uso del sistema de plantillas de Django: simplemente escribir una cadena de plantilla, crear un objeto Template, crear un Context, y llamar al método render().

Múltiples contextos, misma plantilla

Una vez que tiene un objeto Template, usted puede renderizar múltiples contextos a través de él. Considere este ejemplo:

>>> 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

Cada vez que usted está utilizando la misma plantilla para renderizar múltiples contextos, es más eficiente crear el objeto plantilla una vez, y luego llamar a render() sobre él varias veces:

# 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}))

Búsqueda de variable de Contexto

En los ejemplos hasta ahora, hemos pasado valores simples a los contextos – en su mayoría cadenas, además de un datetime.date. Sin embargo, el sistema de plantillas maneja estructuras de datos complejas, tales como listas, diccionarios, y objetos personalizados.

La clave para atravesar estructuras complejas de datos en las plantillas Django es el carácter de punto (.). Utilice un punto para acceder a las claves del diccionario, atributos, métodos, o índices de un objeto.

Por ejemplo, supongamos que usted está pasando un diccionario Python a una plantilla. Para acceder a los valores de ese diccionario por clave de diccionario, use el punto:

>>> 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)
u’Sally is 43 years old.’

Del mismo modo, los puntos también permiten el acceso a los atributos de los objetos. Por ejemplo, un objeto datetime.date de Python tiene atributos año, mes, día, y puede utilizar un punto para acceder a esos atributos en una plantilla Django:

>>> 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)
u’The month is 5 and the year is 1993.’

Este ejemplo utiliza una clase personalizada, lo que demuestra que los puntos permiten también el acceso a atributos de objetos arbitrarios:

>>> 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)
u’Hello, John Smith.’

Los puntos también puede hacer referencia a métodos de objetos. Por ejemplo, cada cadena Python tiene los métodos upper() y isdigit(), y usted puede llamarlos en las plantillas Django, usando la misma sintaxis del punto:

>>> from django.template import Template, Context
>>> t = Template(‘{{ var }}—{{ var.upper }}—{{ var.isdigit }}’)
>>> t.render(Context({‘var’: ‘hello’}))
u’hello—HELLO—False’
>>> t.render(Context({‘var’: ‘123’}))
u’123—123—True’

Tenga en cuenta que no se incluyen los paréntesis en las llamadas a métodos. Además, no es posible pasar argumentos a los métodos, sólo puede llamar a métodos que no requieren argumentos.

Por último, los puntos también se utilizan para acceder a los índices de lista, como en este ejemplo:

>>> from django.template import Template, Context
>>> t = Template(‘Item 2 is {{ items.2 }}.’)
>>> c = Context({‘items’: [‘apples’, ‘bananas’, ‘carrots’]})
>>> t.render(c)
u’Item 2 is carrots.’

Las búsquedas de puntos se pueden resumir así: cuando la plantilla se encuentra con un punto en un nombre de variable, trata las búsquedas siguientes, en este orden:

  • Diccionario (por ejemplo, foo[“bar”])
  • Atributo (por ejemplo, foo.bar)
  • Llamada a método (por ejemplo, foo.bar())
  • Índice de Lista (por ejemplo, foo[2])

El sistema utiliza el tipo de búsqueda que funciona primero. Es una falta de lógica del circuito.
Las búsquedas del punto se pueden anidar varios niveles de profundidad. Por ejemplo, el ejemplo siguiente utiliza {{ person.name.upper() }}, que se traduce en una búsqueda de diccionario (person[‘name’]) y luego
una llamada al método (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)
u’SALLY is 43 years old.’

Comportamiento de llamada a método

Las llamadas a los métodos son un poco más complejas que el de otros tipos de búsqueda. Aquí hay algunas cosas a tener en cuenta.

Si, durante la búsqueda de métodos, un método produce una excepción, la excepción se propagará a menos que la excepción tenga un atributo silent_variable_failure a True. Si la excepción tiene un atributo silent_variable_failure, la variable se renderizará como una cadena vacía, como en este ejemplo:

>>> 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}))
u’My name is .’

Una llamada al método sólo funcionará si el método no requiere argumentos. De lo contrario, el el sistema se moverá al siguiente tipo de búsqueda (índice de lista).
Obviamente, algunos métodos tienen efectos colaterales y sería absurdo, y, posiblemente, incluso un agujero de seguridad, permitir al sistema de plantillas acceder a ellos. Digamos, por ejemplo, que tiene un objeto BackAccount que tiene un método delete(). Si una plantilla incluye algo como {{ account.delete() }}, donde account es un objeto BankAccount, el objeto sería eliminado al procesar la plantilla. Para evitar esto, establezca el atributo de función alters_data en el método:

def delete(self):

# Delete the account

delete.alters_data = True

El sistema de plantillas no ejecutará cualquier método marcado de esta manera. Continuando con el ejemplo actual, si una plantilla incluye {{ account.delete }} y el método delete() tienen alters_data = True, entonces el método delete() no se ejecutará cuando la plantilla se renderice. En su lugar, se producirá un error silencioso.

¿Cómo se gestionan las variables inválidas?

Por defecto, si una variable no existe el sistema de plantillas la renderiza como una cadena vacía, error silencioso. Considere este ejemplo:

¡

>>> from django.template import Template, Context
>>> t = Template(‘Your name is {{ name }}.’)
>>> t.render(Context())
u’Your name is .’
>>> t.render(Context({‘var’: ‘hello’}))
u’Your name is .’
>>> t.render(Context({‘NAME’: ‘hello’}))
u’Your name is .’
>>> t.render(Context({‘Name’: ‘hello’}))
u’Your name is .’

Jugar con objetos Context

La mayoría de las veces, usted instanciará objetos Context pasando un diccionario totalmente lleno a Context(). Pero usted también puede añadir y eliminar elementos de un objeto Context, una vez instanciado,usando la sintaxis estándar de Python diccionario:

>>> from django.template import Context
>>> c = Context({“foo”: “bar”})
>>> c[‘foo’]
‘bar’
>>> del c[‘foo’]
>>> c[‘foo’]
Traceback (most recent call last):

KeyError: ‘foo’
>>> c[‘newvariable’] = ‘hello’
>>> c[‘newvariable’]
‘hello’

Etiquetas y Filtros de Plantilla Básica

Etiquetas

if / else

La etiqueta {% if %} evalúa una variable, y si esa variable es True (es decir, que existe, no está vacío, y no es un valor boolean FALSE), el sistema mostrará todo entre {% if %} y {% endif %}, como en este ejemplo:

{% if today_is_weekend %}

Welcome to the weekend!

{% endif %}

Una etiqueta {% else %} es opcional:

{% if today_is_weekend %}

Welcome to the weekend!

{% else %}

Get back to work.

{% endif %}

for

La etiqueta {% for %} permite un bucle sobre cada elemento de una secuencia. La plantilla renderizará todo entre {% for %} y {% endfor %}.

Por ejemplo, podría utilizar el siguiente ejemplo para mostrar una lista de atletas dada una variable athlete_list:

<ul> {% for athlete in athlete_list %}

<li>{{ athlete.name }}</li>

{% endfor %}
</ul>

y al revés:

{% for athlete in athlete_list reversed %}

{% endfor %}

Es posible anidar etiquetas {% for %}.

La etiqueta for soporta una clausula opcional {% empty %} que le permite definir que salida si la lista está vacía.

{% for athlete in athlete_list %}

{{ athlete.name }}

{% empty %}

There are no athletes. Only computer programmers.

{% endfor %}

No hay soporte para opciones del tipo break y continue.

Dentro de cada bucle {% for %} usted puede acceder a una variable llamada forloop. Esta variable tiene algunas características que le dan información sobre el progreso del bucle:

  • forloop.counter siempre se establece en un número entero que representa el número de veces que se ha entrado en el bucle.
  • forloop.counter0 es como forloop.counter, excepto que es indexado en cero. Su valor será fijado a 0 la primera vez que se entre en el bucle.
  • forloop.revcounter siempre se establece en un número entero que representa el número de elementos restantes del bucle.
  • forloop.revcounter0 es como forloop.revcounter, excepto que es indexado en cero.
  • forloop.first es un valor booleano que se establece a True si esta es la primera iteración del bucle.
  • forloop.last es un valor booleano que se establece a True si esta es la última iteración del bucle.
  • forloop.parentloop es una referencia al objeto forloop del bucle padre, en caso de bucles anidados.

ifequal / ifnotequal

La etiqueta {% ifequal %} compara dos valores y muestra todo entre {% ifequal %} y {% endifequal %} si los valores son iguales. Este ejemplo compara la plantilla de las variables user y currentuser:

{% ifequal user currentuser %}

<h1>Welcome!</h1>

{% endifequal %}

Al igual que {% if %} la etiqueta {% ifequal %} soporta un {% else %} opcional:

{% ifequal section ‘sitenews’ %}

<h1>Site News</h1>

{% else %}

<h1>No News Here</h1>

{% endifequal %}

Sólo las variables de plantilla, cadenas, enteros y números decimales se permiten como argumentos para {% ifequal %}. Estos son ejemplos válidos:

{% ifequal variable 1 %}
{% ifequal variable 1.23 %}
{% ifequal variable ‘foo’ %}
{% ifequal variable “foo” %}

Cualquier otro tipo de variables, tales como diccionarios, listas o booleanos no pueden codificarse en la etiqueta {% ifequal %}. Estos son ejemplos inválidos:

{% ifequal variable True %}
{% ifequal variable [1, 2, 3] %}
{% ifequal variable {‘key’: ‘value’} %}

Si necesita testear si algo es verdadero o falso, use {% if %} en lugar de {% ifequal %}.

Comentarios

Para designar un comentario usar {# #}.

No pueden usar esta sintaxis para varias líneas. Para ello usar la etiqueta {% comment %}, como esto:

{% comment %}
This is a
multiline comment.
{% endcomment %}

Filtros

Los filtros de plantilla son formas sencillas de alterar el valor de las variables antes de renderizarlas. Los filtros utilizan un carácter de canalización (pipe), como esto:

{{ name|lower }}

Los filtros se pueden encadenar, es decir, que pueden ser usados en conjunto de manera que la salida de un filtro se aplica al siguiente. He aquí un ejemplo que convierte el primer elemento de una lista a mayúsculas:

{{ my_list|first|upper }}

Algunos filtros toman argumentos que vienen después de dos puntos y siempre entre comillas dobles. He aquí un ejemplo:

{{ bio|truncatewords:”30″ }}

Esto muestra las primeras 30 palabras de la variable bio.

Los siguientes son algunos de los filtros más importantes:

  • addslashes: Agrega una barra invertida antes de que cualquier barra invertida, comillas simples o comillas dobles. Esto es útil si el texto producido se incluye en una cadena JavaScript.
  • date: Formatea una cadena de fecha o fecha/hora de acuerdo a un formato dado como parámetro.
  • length: Devuelve la longitud del valor. Para una lista, devuelve el número de elementos. Para una cadena, devuelve el número de caracteres. Funciona en cualquier objeto Python que sabe cómo determinar su propia longitud, es decir, cualquier objeto que tiene un método __len__()

Filosofías y limitaciones

Ahora que usted tiene una idea del sistema de plantillas de Django, hay que señalar alguna de sus limitaciones intencionales, junto con algo de filosofía de por qué funciona de la forma en que funciona.

Más que cualquier otro componente de las aplicaciones web, la sintaxis de las plantillas es muy subjetiva, y las opiniones de los programadores varían significativamente.

Con esto en mente, es posible que le interese saber que Django no requiere que usted use su lenguaje de plantillas. Debido a que Django está destinado a ser un completo entorno web que proporcione todas las piezas necesarias para los desarrolladores web para ser productivo, muchas veces es más conveniente el uso del sistema de plantillas de Django que otras librerías de plantillas Python, pero no es un requisito estricto en ningún sentido.

Sin embargo, está claro que tenemos una fuerte preferencia por el lenguaje de plantillas de Django. El sistema de plantillas tiene sus raíces en la forma en que el desarrollo web se hace en el mundo en línea combinado con la experiencia de los creadores de Django. Aquí están algunas de nuestras filosofías:

  • La lógica de negocio deben estar separada de la lógica de presentación. Los desarrolladores de Django ven el sistema de plantillas como una herramienta que controla la presentación y la lógica relacionada con la presentación, y eso es todo.
  • La sintaxis debe estar desacoplada del HTML/XML. Aunque el sistema de plantillas de Django se utiliza principalmente para producir HTML, su intención es ser tan útil para los formatos no HTML, tales como el texto sin formato.
  • Los diseñadores se supone que se sienten cómodos con el código HTML. El sistema de plantillas no está diseñado de manera que las plantillas necesariamente se muestran muy bien en los editores WYSIWYG como Dreamweaver. Django espera que los autores de plantillas estén cómodos editando el HTML directamente.
  • Se asume que los diseñadores no son programadores de Python. Los autores del sistema de plantillas reconocen que a menudo las plantillas de las páginas web son escritas por los diseñadores, no por programadores, y por lo tanto no se debe asumir el conocimiento de Python.
  • El objetivo no es inventar un lenguaje de programación. El objetivo es ofrecer como mucho la funcionalidad del esquema de programación, tal como la ramificación y la iteración, que es esencial para la toma de decisiones relacionadas con la presentación.

Usando plantillas en vistas

Recordemos la vista current_datetime en mysite.views. Esto es:

from django.http import HttpResponse
import datetime

def current_datetime(request):

now = datetime.datetime.now()

html = “It is now %s.” % now

return HttpResponse(html)

Vamos a cambiar esta vista para utilizar el sistema de plantillas de Django. Al principio se podría pensar en hacer algo como esto:

from django.template import Template, Context
from django.http import HttpResponse
import datetime

def current_datetime(request):

now = datetime.datetime.now()

t = Template(“It is now {{ current_date }}.”)

html = t.render(Context({‘current_date’: now}))

return HttpResponse(html)

Está claro que utiliza el sistema de plantillas, pero no resuelve los problemas que hemos señalado. A saber, la plantilla está incrustada en el código Python, por lo que no se logra una verdadera separación de los datos y la presentación. Vamos a arreglar esto poniendo la plantilla en un archivo separado, que cargará esta vista.

En primer lugar, podría considerar la posibilidad de guardar la plantilla en algún lugar de su sistema de ficheros y usar Python para leer el contenido de la plantilla. Esto es lo que podría parecerse, suponiendo que la plantilla se ha guardado en el archivo /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 is BAD because it 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)

Este enfoque, sin embargo, es poco elegante, por estas razones:

  • No maneja el caso de que el archivo falle, como se señala en el código. Si el archivo mytemplate.html no existe o no es legible, la llamada open() lanzará una excepción IOError.
  • Codifica a pelo la ubicación de la plantilla. Si usted fuera a utilizar esta técnica para cada función de vista, estaría duplicando las localizaciones de la plantilla – por no mencionar que se trata de escribir mucho.
  • Incluye una gran cantidad de código repetitivo aburrido. Tienes cosas mejores que hacer que escribir las llamadas a open(), fp.read(), y fp.close() cada vez que se carga una plantilla.

Para resolver estos problemas, vamos a utilizar la carga de plantillas y la herencia de plantillas.

Carga de Plantillas

Django proporciona un API cómodo y eficaz para la carga de plantillas del sistema de archivos, con el objetivo de eliminar la redundancia, tanto en las llamadas de carga de plantillas como en las plantillas en sí mismas.

Para usar esta API de carga de plantillas, primero tendrá que decirle al marco donde se almacenan las plantillas. El lugar para hacerlo es su archivo de configuración settings.py que hemos mencionado en el capítulo anterior, cuando se introdujo la propiedad ROOT_URLCONF.

Abra settings.py y encuentre la propiedad TEMPLATE_DIRS. De forma predeterminada, es una tupla vacía, y es probable que contenga algunos comentarios autogenerados:

TEMPLATE_DIRS = (

# Put strings here, like “/home/html/django_templates”

# or “C:/www/django/templates”.

# Always use forward slashes, even on Windows.

# Don’t forget to use absolute paths, not relative paths.

)

Este ajuste le indica al mecanismo de carga de plantillas de Django donde buscar las plantillas. Elija un directorio donde desea almacenar sus plantillas y añadalo a TEMPLATE_DIRS, así:

TEMPLATE_DIRS = (

‘/home/django/mysite/templates’,

)

Hay algunas cosas que debe recordar:

  • Usted puede especificar cualquier directorio que desee, siempre y cuando el directorio y las plantillas dentro de ese directorio sean legibles por la cuenta de usuario en las que el servidor Web se ejecuta. Se recomienda la creación de un directorio de plantillas dentro de su proyecto (es decir, dentro del directorio que ha creado mysite)
  • Si su TEMPLATE_DIRS sólo contiene un directorio, no se olvide de la coma al final de la cadena del directorio. Python requiere comas dentro de tuplas de un solo elemento para eliminar la ambigüedad de la tupla de una expresión en paréntesis.
  • Si está en Windows, incluya la letra de la unidad y utilice las barra inclinadas al estilo Unix en lugar de las barras invertidas.

Lo más simple es utilizar rutas absolutas (es decir, rutas de directorios que comienzan en la raíz del sistema de archivos). Si quiere ser un poco más flexible, sin embargo, usted puede construir TEMPLATE_DIRS dinámicamente, como en este ejemplo:

import os.path

TEMPLATE_DIRS = (

os.path.join(os.path.dirname(__file__), ‘templates’).replace(‘\’,’/’),

)

Este ejemplo utiliza la variable “mágica” de Python __file__, que se ajusta automáticamente al nombre de archivo del módulo de Python en que reside el código. Se pone el nombre del directorio que contiene a settings.py (os.path.dirname), y se une con las plantillas de una manera (os.path.join), y entonces asegura que todo lo que se utiliza sean barras inclinadas en lugar de barras invertidas (en el caso de Windows).

Con TEMPLATE_DIRS activo, el siguiente paso es cambiar el código para el uso de la funcionalidad de la carga de plantillas de Django en lugar de codificar a pelo las rutas de las plantillas. Volviendo a nuestra vista current_datetime, vamos a cambiarla de esta manera:

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)

La función get_template() toma un nombre de plantilla como argumento, busca dónde la plantilla residee en el sistema de archivos, abre ese archivo, y devuelve un objeto Template compilado.

Si get_template() no puede encontrar la plantilla con el nombre que se le da, lanza una excepción TemplateDoesNotExist.

Ahora, crear el archivo current_datetime.html dentro de su directorio de plantillas mediante el siguiente código de plantilla:

It is now {{ current_date }}.

Actualice la página en el explorador Web, y usted debería ver la página completamente renderizada.

render_to_response()

Hemos mostrado cómo cargar una plantilla, rellenar un contexto, y devolver un objeto HttpResponse con el resultado de la plantilla renderizada. Lo hemos optimizado mediante el uso de get_template() en lugar de la codificación a pelo de las plantillas y las rutas de plantillas. Sin embargo, todavía se requiere una buena cantidad de código para escribir todas esas cosas. Debido a que estos pasos son iguales, Django proporciona una abreviatura que le permite cargar una plantilla, renderizarla, y devolver un HttpResponse, todo en una sola línea de código.

Esta abreviatura es una función llamada render_to_response(), que reside en el módulo django.shortcuts.
Aquí está el ejemplo current_datetime reescrito para utilizar 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})

El primer argumento de render_to_response() es el nombre de la plantilla a usar. El segundo argumento, si lo hay, debe ser un diccionario para usar en la creación de un contexto para la plantilla. Si usted no proporciona un segundo argumento, render_to_response() utiliza un diccionario vacío.

locals()

Muchas veces, usted se encontrará que usted mismo cálcula algunos valores, los almacena en variables (por ejemplo, now en el código anterior), y envía esas variables a la plantilla. Esto es un poco redundante y también significa escribir más.

Usted puede usar la función Python llamada locals(). Esta devuelve un diccionario que asigna todos los nombres de variables locales a sus valores, donde local significa todas las variables que han sido definidas en el ámbito local. Así, la vista anterior podría reescribirse así:

def current_datetime(request):

current_date = datetime.datetime.now()

return render_to_response(‘current_datetime.html’, locals())

Hemos cambiado el nombre de la variable a current_date ahora, ya que ese es el nombre de variable que la plantilla espera.

Subdirectorios en get_template()

Puede ser difícil de manejar el almacenar todas sus plantillas en un solo directorio. Usted podría almacenar plantillas en subdirectorios de su directorio de plantillas, y eso está bien. De hecho, le recomendamos hacerlo; algunas características más avanzadas de Django (como el sistema de vistas genéricas) esperan esta disposición de plantillas por defecto.

t = get_template(‘dateapp/current_datetime.html’)

Ya que render_to_response() es una pequeña envoltura alrededor de get_template(), usted puede hacer lo mismo con el primer argumento de render_to_response(), así:

return render_to_response(‘dateapp/current_datetime.html’, {‘current_date’: now})

La etiqueta de plantilla include

Podemos introducir una etiqueta de plantilla integrada: {% include%}. Esta etiqueta permite incluir el contenido de otra plantilla. El argumento de la etiqueta debe ser el nombre de la plantilla a incluir, y el nombre de la plantilla puede ser una variable o una cadena entre comillas simples o dobles. Cada vez que tenga el mismo código en varias plantillas, considere el uso de {% include %} para eliminar la redundancia.

Estos dos ejemplos incluyen el contenido de la plantilla nav.html. Los ejemplos son equivalentes e ilustran bien el uso de las comillas simples o dobles:

{% include ‘nav.html’ %}
{% include “nav.html” %}

El siguiente ejemplo incluye el contenido de la plantilla, cuyo nombre figura en la variable template_name:

{% include template_name %}

Al igual que en get_template(), el nombre del fichero de la plantilla se determina mediante la adición al directorio de plantillas de TEMPLATE_DIRS para el nombre de la plantilla.

La plantillas incluidas son evaluadas dentro del contexto de la plantilla que las incluye. Por ejemplo, considere estas dos plantillas:

# mypage.html

{% include “includes/nav.html” %}

<h1>{{ title }}</h1>

# includes/nav.html
<div id=”nav”>You are in: {{ current_section }}</div>

Si renderiza mypage.html con un contexto que contiene current_section, entonces la variable estará disponible en la plantilla incluida, como era de esperar.

Si, en una etiqueta {% include %}, no se encuentra una plantilla con el nombre dado, Django hará una de estas 2 cosas:

  • Si DEBUG es True, verá una excepción TemplateDoesNotExist en una página de error de Django.
  • Si DEBUG es False, la etiqueta fallará de forma silenciosa, no visualizando nada en el lugar de la etiqueta.

Herencia de plantillas

Nuestros ejemplos de plantillas hasta ahora han sido pequeños fragmentos de código HTML, pero en el mundo real usted utilizará el sistema de plantillas de Django para crear páginas enteras de HTML. Esto lleva a un problema de desarrollo web común: a través de un sitio Web, ¿cómo se puede reducir la duplicación y la redundancia de áreas de página comunes, tales como la navegación de todo el sitio?

Una forma clásica de resolver este problema es usar includes de lado servidor, las directivas que usted puede incrustar dentro de sus páginas HTML para incluir una página web dentro de otra. De hecho, Django soporta esta aproximación con la etiqueta {% include %} que acabamos de describir. Pero la forma preferida de resolver este problema con Django es utilizar una estrategia más elegante llamado herencia de plantillas.

En esencia, la herencia de plantillas le permite construir una plantilla “esqueleto” base que contiene todas las partes comunes de su sitio y define “bloques” que las plantillas hijas puede rellenar.

Veamos un ejemplo de esto creando una plantilla más completa para nuestra vista current_datetime, editando el archivo 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>

It is now {{ current_date }}.

<hr>Thanks for visiting my site.

</body>
</html>

Eso se ve muy bien, pero ¿qué sucede cuando queremos crear una plantilla para otra vista – por ejemplo, la vista hours_ahead? Si queremos volver a hacer otra plantilla HTML agradable, válida, completa, nos quedará algo como esto:

<!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>

In {{ hour_offset }} hour(s), it will be {{ next_time }}.

<hr>Thanks for visiting my site.

</body>
</html>

Es evidente que hay mucho HTML duplicado. Imagínese si tuviéramos un sitio más típico, incluyendo una barra de navegación, una hojas de estilo, tal vez algo de JavaScript – empezaremos metiendo todo ese HTML redundante en cada plantilla.

La solución de include de lado servidor a este problema es factorizar las partes comunes de ambas plantillas y guardarlos en distintos fragmentos de plantilla, que luego son incluidos en cada plantilla. Tal vez desee guardar la parte superior de la plantilla en un archivo llamado header.html:

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01//EN”>
<html lang=”en”>
<head>

y quizá almacenar la parte inferior en un fichero llamado footer.html:

<hr>

<p>Thanks for visiting my site.</p>

</body>
</html>

Con una estrategia basada en include, los encabezados y los pies de página son fáciles. Es el punto medio el desordenado. En este ejemplo, ambas páginas tienen un título -<h1>My helpful timestamp site </h1> pero ese título no puede encajar en header.html porque el <title> en ambas páginas es diferente. Si incluimos el <h1> en la cabecera, tendríamos que incluir el <title>, que no nos permitiría personalizarlo por página.

El sistema de herencia de plantillas de Django soluciona estos problemas. Puede pensar en ello como una versión dentro-fuera de los includes de lado servidor. En lugar de definir los fragmentos de código que son comunes, se definen los fragmentos de código que son diferentes.

El primer paso es definir una plantilla base, un esqueleto de la página que las plantillas hijas rellenarán después. Aquí hay una plantilla base para nuestro ejemplo en curso:

<!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>

Esta plantilla, que llamaremos base.html, define un documento de esqueleto HTML sencillo que se utilizará para todas las páginas del sitio. Es el trabajo de las plantillas hijas reemplazar, añadir o dejar vacío el contenido de los bloques.

Estamos utilizando una etiqueta de plantilla que no hemos visto antes: la etiqueta {% block %}. Todo lo que hace una etiqueta {% block %} es decirle al motor de plantillas que una plantilla hija puede sustituir aquellas partes de la plantilla.

Ahora que tenemos esta plantilla base, podemos modificar nuestra plantilla current_datetime.html existente:

{% extends “base.html” %}

{% block title %}The current time{% endblock %}

{% block content %}

<p>It is now {{ current_date }}.</p>

{% endblock %}

Vamos a crear una plantilla para la vista hours_ahead del Capítulo 3. Se podría parecer a esto:

{% extends “base.html” %}

{% block title %}Future time{% endblock %}

{% block content %}

<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>

{% endblock %}

¿No es esto bonito? Cada plantilla contiene sólo el código que es único para esa plantilla. No hay redundancia. Si usted necesita hacer un cambio de diseño de todo el sitio, sólo haga el cambio a base.html, y todas las otras plantillas inmediatamente reflejarán el cambio.

He aquí cómo funciona. Cuando se carga la plantilla current_datetime.html, el motor de plantillas ve la etiqueta {% extends %}, notando que esa plantilla es una plantilla hija. El motor carga inmediatamente la plantilla padre – en este caso, base.html.

En ese momento, el motor de plantillas nota las tres etiquetas {% block %} de base.html y sustituye esos bloques con el contenido de la plantilla hija.

La herencia no afecta al contexto de la plantilla. En otras palabras, cualquier plantilla en el árbol de herencia tendrá acceso a cada una de sus variables de plantilla en el contexto.

Puede utilizar tantos niveles de herencia, según sea necesario. Una forma común de utilizar la herencia es el siguiente enfoque de tres niveles:

  1. Crear una plantilla base.html que contenga el diseño principal de su sitio. Esta contiene las cosas que rara vez o nunca se cambian.
  2. Crear una plantilla base_SECTION.html por cada sección del sitio (por ejemplo, base_photos.html y base_forum.html). Estas plantillas extienden a base.html e incluyen estilos y diseño específicos de la sección.
  3. Crear plantillas individuales para cada tipo de página, como una página del foro o una galería de fotos. Estas plantillas extienden a la plantilla de la sección correspondiente.

Este método maximiza la reutilización de código y facilita el añadir elementos a las zonas comunes, como la sección de navegación.

Aquí hay algunas pautas para trabajar con herencia de plantillas:

  • Si usted usa {% extends %} en una plantilla, debe ser la primera etiqueta de esa plantilla.
  • Generalmente, cuanto más etiquetas {% block %} en sus plantillas base, mejor. Recordar que las plantillas hijas no tienen que definir todos los bloques de los padres, así que usted puede definir en las plantillas hijas sólo los que necesita.
  • Si usted encuentra duplicación de código en varias plantillas, probablemente significa que usted debe mover el código a una etiqueta {% block %} de una plantilla padre.
  • Si usted necesita obtener el contenido del bloque de la plantilla padre, use {{ block.super }}, que es una variable “mágica” que proporciona el texto renderizado de la plantilla padre. Esto es útil si desea añadir el contenido de un bloque padre en lugar de sobreescribirlo completamente.
  • Usted no puede definir múltiples etiquetas {% block %} con el mismo nombre en la misma plantilla.
    Esta limitación existe porque una etiqueta de bloque funciona en ambas direcciones. Es decir, un etiqueta de bloque no sólo proporcionan un contenedor para rellenar, sino que también define el contenido que rellena el contenedor en el padre. Si hubiese dos etiquetas {% block %} llamadas iguales en una plantilla, el padre no sabría cual de los contenidos de bloque utilizar.
  • El nombre de plantilla que se pasa a {% extends %} se carga utilizando el mismo método que usa get_template(). Es decir, el nombre de la plantilla se añade a la propiedad TEMPLATE_DIRS.
  • En la mayoría de los casos, el argumento de {% extends %} será una cadena, pero puede ser una variable, si usted no sabe el nombre de la plantilla padre hasta tiempo de ejecución. Esto le permite hacer algunas cosas de forma dinámica.

Enlaces Relacionados: