Módulo 4 · Lección 17 de 45
El lenguaje de plantillas
Un lenguaje a propósito pequeño, el punto que sirve para cuatro cosas, y el silencio con el que se traga tus erratas.
Una plantilla de Django es un fichero de texto con huecos. No tiene por qué ser HTML: sirve igual para un correo, un CSV o un fichero de configuración. Lo que la distingue de una f-string es que está pensada para que la escriba alguien que no es quien programa, y de esa decisión salen todas sus rarezas.
Porque el lenguaje es pequeño a propósito. No se puede sumar, no se pueden llamar funciones con argumentos, no se puede importar nada y no hay variables sueltas. Eso frustra al principio y tiene un motivo que conviene aceptar antes de pelearse con él: si en la plantilla se puede hacer poco, la lógica no acaba en la plantilla.
Tres sintaxis y nada más#
{{ variable }} Escribe algo
{% etiqueta %} Hace algo
{# comentario #} No sale en la salida{{ }} evalúa y escribe. {% %} ejecuta lógica: bucles, condicionales, herencia. Y {# #} es un comentario que desaparece de la salida, al contrario que <!-- -->, que llega al navegador y cualquiera puede leerlo con "ver código fuente".
El punto hace cuatro cosas#
{{ contacto.nombre }} un atributo
{{ datos.clave }} una clave de diccionario
{{ lista.0 }} un índice
{{ contacto.iniciales }} un método o propiedad, sin paréntesisLos cuatro con el mismo punto. Django los prueba en este orden: primero clave de diccionario, después atributo o método, y por último índice numérico. El primero que funciona gana.
{{ c.iniciales }} / {{ c.nombre|upper }} -> AR / ANA RUIZ
{{ d.clave }} -> valor
{{ l.0 }}{{ l.1 }} -> abFíjate en que iniciales y nombre se escriben igual aunque uno sea una propiedad y el otro un campo. Y en que un método se llama sin paréntesis: {{ contacto.get_grupo_display }} ejecuta el método. Si pusieras los paréntesis, sería un error de sintaxis.
Ese orden de búsqueda tiene una consecuencia práctica: un diccionario con una clave llamada items, values o keys da problemas, porque Django encontrará antes el método del diccionario que tu clave. Es raro y cuando pasa cuesta verlo.
Lo que no se puede hacer#
Esta es la lista, y conviene leerla entera una vez para no perder el tiempo intentándolo:
{{ 2 + 2 }} TemplateSyntaxError
{{ contacto.busca("ana") }} No se pueden pasar argumentos
{% x = 5 %} No hay asignación
{% import datetime %} No se importa nada
{{ contactos[0] }} No hay corchetes; es contactos.0TemplateSyntaxError: Could not parse the remainder: ' + 2' from '2 + 2'Para lo poco que hace falta hay filtros:
{{ 2|add:2 }} -> 4Y para todo lo demás, la respuesta correcta no es buscar el truco, es calcularlo en la vista o en el modelo y pasarlo hecho. Cuando te veas peleando con la plantilla para conseguir un número, casi siempre es la señal de que ese número es una propiedad del modelo.
Filtros#
Un filtro transforma un valor, se aplica con | y se puede encadenar:
{{ s|lower|title|truncatewords:2 }} -> Hola Que …Los argumentos van detrás de dos puntos, sin espacios. Los que se usan de verdad:
| Filtro | Qué hace |
|---|---|
default:"x" | Usa x si el valor es falso: vacío, cero, None |
default_if_none:"x" | Usa x solo si es None |
length | Cuántos elementos tiene |
upper, lower, title, capfirst | Mayúsculas y minúsculas |
truncatewords:n, truncatechars:n | Cortar |
date:"formato" | Dar formato a una fecha |
join:", " | Unir una lista |
pluralize | La s del plural |
yesno:"sí,no" | Convertir un booleano en texto |
linebreaks, linebreaksbr | Saltos de línea en <p> o en <br> |
safe | No escapar este valor |
floatformat:2 | Decimales |
La diferencia entre default y default_if_none se ve mejor con las dos juntas:
[{{ n|default_if_none:"nada" }}][{{ v|default:"vacio" }}] -> [nada][vacio]default salta con cualquier cosa falsa, y eso incluye el cero. Para un contador, {{ total|default:"ninguno" }} enseña ninguno cuando el total es cero, que casi nunca es lo que se quería. Con números, default_if_none.
pluralize es más útil de lo que parece en español:
{{ n }} contacto{{ n|pluralize }} -> 2 contactos
{{ n }} direcci{{ n|pluralize:"ón,ones" }} -> 2 direccionesSin argumento añade una s; con dos valores separados por coma, elige entre singular y plural. La segunda forma es la que resuelve el español.
Fechas#
date usa un formato propio que no es el de strftime:
{{ c.creado|date:"d/m/Y H:i" }} -> 22/09/2026 18:29
{{ c.creado|date:"j \d\e F \d\e Y" }} -> 22 de septiembre de 2026El segundo sale en español por el LANGUAGE_CODE de la lección 5, sin instalar nada. Las letras sueltas hay que escaparlas con barras, porque casi todas significan algo.
Hay tres formatos con nombre que respetan el idioma sin tener que escribir nada: {{ fecha|date:"SHORT_DATE_FORMAT" }}, "DATE_FORMAT" y "DATETIME_FORMAT". Son los que conviene usar cuando el formato exacto da igual.
Etiquetas#
for#
{% for contacto in contactos %}
<li>{{ contacto.nombre }}</li>
{% empty %}
<li>No hay contactos.</li>
{% endfor %}{% empty %} es el bloque que sale cuando la lista está vacía, y ahorra el {% if %} de alrededor.
Dentro del bucle hay una variable forloop:
forloop.* | Qué es |
|---|---|
counter | El número, empezando en 1 |
counter0 | El número, empezando en 0 |
first, last | Si es el primero o el último |
revcounter | Los que quedan |
parentloop | El forloop del bucle de fuera |
{% for x in l %}{{ forloop.counter }}:{{ x }}{% if not forloop.last %}, {% endif %}{% endfor %}
1:a, 2:b, 3:cPara recorrer un diccionario se usa items, como en Python pero sin paréntesis:
{% for clave, valor in datos.items %}{{ clave }}={{ valor }}{% endfor %}if#
{% if contacto.telefono %}
<a href="tel:{{ contacto.telefono }}">Llamar</a>
{% elif contacto.correo %}
<a href="mailto:{{ contacto.correo }}">Escribir</a>
{% else %}
Sin forma de contacto
{% endif %}Admite ==, !=, <, >, <=, >=, in, not in, is, and, or y not. Es casi Python, con la diferencia de que no hay paréntesis para agrupar: cuando la condición necesita paréntesis, es que le toca a la vista.
with#
Guarda un valor calculado para no repetirlo:
{% with total=contactos.count %}
Hay {{ total }} contacto{{ total|pluralize }}.
{% endwith %}Esto no es solo comodidad. contactos.count es una consulta a la base de datos, y escribirlo tres veces en la plantilla son tres consultas. with la hace una vez.
El escapado#
Ya se vio en la lección anterior: todo lo que se inserta se escapa.
{{ h }} || {{ h|safe }} -> <b>x</b> || <b>x</b>safe desactiva el escapado de ese valor, y {% autoescape off %} lo desactiva para un bloque entero.