Plantillas¶
Django básico
Las plantillas en Django se utilizan para preparar el contenido final (habitualmente HTML) que se hará llegar al usuario de la aplicación web.
Las plantillas constituyen la capa de presentación del modelo por capas visto en la introducción al desarrollo web.
Nombres de plantillas
Las plantillas son ficheros HTML cuyo nombre se escribe habitualmente en formato kebab-case, es decir, en minúsculas separados por guiones medios. Por ejemplo flight-departures.html
Ubicación¶
Cuando hacemos referencia a una plantilla —habitualmente desde una vista— mediante una ruta (relativa), Django utiliza la siguiente estrategia para localizar la plantilla indicada:
- Busca dentro de la carpeta
/templatesde cada aplicación del proyecto.(1) - Busca en otras carpetas definidas explícitamente en la configuración del proyecto.(2)
- Siempre y cuando la variable
APP_DIRSdefinida ensettings.pyesté aTrue:main/settings.pyTEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] -
Las «otras» ubicaciones de plantillas se pueden indicar mediante la variable
DIRSdefinida ensettings.py.main/settings.pyTEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
Supongamos por ejemplo que estamos en un proyecto de «blog» con las aplicaciones actions, comments, posts. Solicitamos a Django una plantilla con ruta 'posts/index.html':
flowchart TD
t1["¿Existe posts/index.html en actions/templates?"]
t2["¿Existe posts/index.html en comments/templates?"]
t3["¿Existe posts/index.html en posts/templates"]
t1 -->|No| t2
t1 -->|Sí| r1(["Se devuelve actions/templates/posts/index.html"])
t2 -->|No| t3
t2 -->|Sí| r2(["Se devuelve comments/templates/posts/index.html"])
t3 -->|No| s["¿Existe en otros directorios definidos en settings.py?"]
t3 -->|Sí| r3(["Se devuelve posts/templates/posts/index.html"])
s -->|No| err{{TemplateDoesNotExist}}
s -->|Yes| r4(["Se devuelve el fichero correspondiente"])
Los espacios de nombres son muy importantes a la hora de organizar las plantillas de nuestro proyecto Django. Ya lo dice el Zen de Python: «Namespaces are one honking great idea -- let's do more of those!».
TemplateDoesNotExist
Uno de los errores más habituales desarrollando proyectos Django es el de TemplateDoesNotExist, que nos indica que no es posible encontrar la plantilla en la ruta indicada.
Si aparentemente todo está bien y Django no encuentra la plantilla indicada, es posible que se solucione la incidencia reiniciando el servidor de desarrollo.
Una tabla resumen que puede aclarar distintos escenarios:
| Si queremos referenciar... | Tendremos que escribir... |
|---|---|
app1/templates/app1/test.html |
'app1/test.html' |
app2/templates/test.html |
'test.html' |
app3/templates/app3/core/test.html |
'app3/core/test.html' |
app4/templates/base/front/test.html |
'base/front/test.html' |
Variables¶
Para usar variables en una plantilla Django debemos rodear su nombre con dobles llaves {{}}
Por ejemplo si queremos mostrar un determinado «post» de un «blog» en una plantilla, podríamos usar la siguiente plantilla:
<h2>{{ post.title }}</h2><!--(1)!-->
<p>{{ post.content }}</p>
- También podríamos haber usado directamente
{{ post }}siempre y cuando se haya implementado convenientemente el método__str__()de la clasePost.
Desde la correspondiente vista, tendremos que renderizar la plantilla anterior mediante el siguiente fragmento de código:
from django.shortcuts import render
def post_detail(request, post_slug: str):
# ...
return render(request, 'posts/post/detail.html', {'post': post})#(1)!
- En el contexto se fija el «post» que vamos a utilizar en la plantilla.
Sin paréntesis
Si queremos hacer uso de una función/método dentro de una plantilla, no se ponen los paréntesis en la llamada:
De aquí se deriva el hecho de que no se pueden pasar parámetros a funciones/métodos en plantillas. Para eso habría que hacer uso de filtros.
Salida vacía¶
En una plantilla de Django, cuando la variable (o función) a la que estamos accediendo no existe no se produce ningún error. En tal caso, no se muestra nada por pantalla. Hay que tenerlo en cuenta para saber cómo proceder.
Esto se debe a que la opción string_if_invalid de main/settings.py establece lo que se muestra cuando no existe la variable (o función) y por defecto su valor es la cadena vacía ''.
Si queremos modificar ese comportamiento bastaría con modificar la configuración indicada.
Supongamos por ejemplo que ahora queremos mostrar el mensaje Missing variable: <variable> cada vez que una variable (o función) no estuviera definida. Para ello haríamos:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
'string_if_invalid': 'Missing variable: %s',#(1)!
},
},
]
- El modificador
'%s'hace referencia a la variable que estamos intentando renderizar.
Etiquetas¶
Django proporciona una serie de etiquetas para usar en plantillas. Estas etiquetas ofrecen distintas funcionalidades y se caracterizan por usar sintaxis {% tag %}.
A continuación veremos los distintos tipos de etiquetas de plantilla que ofrece Django.
Bucles¶
Para recorrer estructuras de datos iterables en una plantilla se usa la etiqueta {% for %} análoga al bucle for de Python.
Veamos un ejemplo de plantilla en la que recorremos todos los «posts» de un blog:
<ul>
{% for post in posts %}<!--(1)!-->
<li>{{ post }}</li><!--(2)!-->
{% endfor %}<!--(3)!-->
</ul>
- En esta línea podemos usar directamente las variables sin usar doble .
- Aquí si tenemos que acceder a la variable con doble .
- Hay que terminar el bucle con esta sentencia.
__str__()
Recuerda que cuando usamos un objeto de modelo en una plantilla {{ post }} se invoca automáticamente el método __str__ del modelo.
Desde la correspondiente vista, tendremos que renderizar la plantilla anterior mediante el siguiente fragmento de código:
from django.shortcuts import render
def post_list(request):
# ...
return render(request, 'posts/post/list.html', {'posts': posts})#(1)!
- En el contexto se fijan los «posts» que vamos a utilizar en la plantilla.
Desempaquetado¶
Los bucles en plantillas Django admiten también el desempaquetado de secuencias tal y como se hacen en Python «tradicional».
Por ejemplo con tuplas representando puntos en el espacio, tendríamos:
Variables especiales¶
Cuando usamos un bucle en una plantilla Django tenemos acceso a ciertas variables especiales que nos pueden facilitar la lógica a implementar.
| Variable | Descripción |
|---|---|
{{ forloop.counter }} |
Iteración actual del bucle (índice ) |
{{ forloop.counter0 }} |
Iteración actual del bucle (índice ) |
{{ forloop.revcounter }} |
Número de iteraciones desde el final del bucle (índice ) |
{{ forloop.revcounter0 }} |
Número de iteraciones desde el final del bucle (índice ) |
{{ forloop.first }} |
True si es la primera iteración del bucle. |
{{ forloop.last }} |
True si es la última iteración del bucle. |
{{ forloop.parentloop }} |
Para bucles anidados, permite el acceso al bucle que engloba al bucle actual. |
Podríamos por ejemplo numerar los «posts» de nuestro «blog»:
<ul>
{% for post in posts %}
<li>{{ forloop.counter }}. {{ post }}</li>
{% endfor %}
</ul>
Vacío¶
El bucle {% for %} admite la cláusula {% empty %} que se ejecuta cuando el iterable a recorrer está vacío o no existe.
En el ejemplo de los «posts» de un «blog», podríamos mostrar un mensaje en el caso de que no exisitera ningún «post»:
<ul>
{% for post in posts %}
<li>{{ post }}</li>
{% empty %}
<p>No posts so far!</p>
{% endfor %}
</ul>
Ciclo¶
La etiqueta {% cycle %} puede ser útil dentro de un bucle ya que nos permite ir «alternando» entre distintos valores.
Por ejemplo imaginemos que queremos alternar el color de fondo de los distintos «posts» del «blog»:
<ul>
{% for post in posts %}
<li style="background-color: {% cycle 'LightBlue' 'LightPink' %}">
{{ post }}
</li>
{% endfor %}
</ul>
Django ofrece la etiqueta {% resetcycle %} para reiniciar un ciclo {% cycle %} y que vuelva a empezar por su primer valor.
Condicionales¶
Django proporciona la etiqueta {% if %} para llevar a cabo comprobaciones en el código de una plantilla. Funciona de manera análoga a la sentencia if de Python.
En el siguiente ejemplo se muestra un mensaje diferente para el primer «post» del «blog»:
{% for post in posts %}
<li>
{% if forloop.first %}<!--(1)!-->
<em>{{ post }}</em>
<strong>(Post más antiguo)</strong>
{% else %}<!--(2)!-->
{{ post }}
{% endif %}<!--(3)!-->
</li>
{% endfor %}
- Aplicamos una condición sobre el número de iteración.
- No es obligatorio el uso de
{% else %}. - Hay que terminar la condición con esta sentencia.
Operadores¶
Para construir condiciones más complejas podemos hacer uso de los mismos operadores lógicos que en Python «tradicional»: and, or y not.
Además tenemos disponibles otros operadores habituales:
| Operador | Significado |
|---|---|
== |
Igualdad. |
!= |
Desigualdad. |
< |
Menor que. |
> |
Mayor que. |
<= |
Menor o igual que. |
>= |
Mayor o igual que. |
in |
En una serie de valores. |
not in |
Fuera de una serie de valores. |
Estilo de programación
El código que insertamos en las plantillas también debemos cuidarlo y aplicarle las mismas reglas de estilo que si estuviéramos escribiendo un fichero puro de Python.
URL¶
Ya hemos visto que las URLs definidas en urls.py pueden deben disponer de un nombre que las identifique.
En una plantilla usaremos la etiqueta {% url %} para renderizar la URL de un determinado recurso y no tener que escribirla directamente.
Como ejemplo vamos a mostrar la manera de ingresar la URL de acceso a todos los «posts» del blog o a uno en concreto. Se diferencian —por tanto— estos dos casos:
Herencia¶
Cuando diseñamos una página web, hay ciertos componentes que son comunes a todas las «pantallas». Véase la cabecera («header»), el pie («footer») o las distintas barras de navegación («sidebar»). No parece muy razonable, reescribir estos componentes en todas las plantillas que hagamos. Siguiendo la filosofía DRY deberíamos poder «refactorizar» estas secciones y sólo añadir el contenido propio de cada página.
La herencia de plantillas se basa en definir una plantilla base desde la que derivamos otras plantillas. En la plantilla base se definen ciertos bloques que serán sobreescritos por las plantillas derivadas.
flowchart TB
base[Plantilla base]
base e1@--> p1[Plantilla derivada 1]
base e2@--> p2[Plantilla derivada 2]
base e3@--> p3[Plantilla derivada 3]
base e4@--> p4[Plantilla derivada 4]
e1@{ animate: true }
e2@{ animate: true }
e3@{ animate: true }
e4@{ animate: true }
El elemento fundamental sobre el que trabaja la herencia es la etiqueta {% block %}. Nos permite definir un bloque (con nombre) para luego reutilizarlo en la jerarquía de plantillas.
Supongamos un ejemplo en el que estamos definiendo plantillas para un «blog». Lo primero será establecer una plantilla base:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% if title %}{{ title }} | {% endif %}Blog</title><!--(1)!-->
</head>
<body>
<div class="container">
{% block content %}{% endblock %}<!--(2)!-->
</div>
</body>
</html>
-
Se define un bloque para el contenido propio de la página.
Ubicación de la plantilla base
La plantilla base (raíz del proyecto) se entiende que será compartida por todas las aplicaciones del proyecto. Por ello, puede ser interesante crear una aplicación shared que contenga la mencionada plantilla base.html.
Ahora vamos a crear una plantilla derivada (desde esta plantilla base) que se encargue de mostrar todos los «posts» del blog:
{% extends "base.html" %}<!--(1)!-->
{% block content %}<!--(2)!-->
{% for post in posts %}
<h3>{{ post }}</h3>
<p>Read more <a href="{% url 'posts:post-detail' post.slug %}">here</a></p>
{% endfor %}
{% endblock %}
-
- Extendemos de la plantilla base.
- La sentencia
{% extends %}debe ser la primera línea de la plantilla. En caso contrario se lanzará el siguiente error:<ExtendsNode: extends "base.html"> must be the first tag in the template.
-
- Sobreescribimos el bloque de contenido correspondiente a esta página.
- Usar bloques sólo tiene sentido cuando estamos extendiendo de alguna plantilla base.
Contenido
Cuando estamos «heredando» de otra plantilla, todo el contenido que pongamos en la plantilla derivada debe ir dentro de algún bloque extendido. En otro caso, el contenido que quede fuera no se renderizará.
Etiquetas y herencia
Las etiquetas de «carga» de módulos (por ejemplo {% load static %}) no se heredan, por lo que deben escribirse también en las plantillas derivadas.
Inclusión¶
Django nos permite externalizar partes de una plantilla a un fichero, para luego incluirlo desde la propia plantilla. Para ello se utiliza la etiqueta {% include %}.
Supongamos por ejemplo que disponemos de la siguiente plantilla para mostrar la cabecera («header») de un «blog»:
<div class="header">
<h1>The ultimate blog</h1>
<h2>{{ subtitle }}</h1><!--(1)!-->
</div>
Django nos ofrece dos modos de incluir la plantilla anterior:
Se incluye la plantilla utilizando el contexto que viene desde la vista en el que tendremos algo como: {'subtitle': 'Check out our last posts!'}:
Otras etiquetas¶
A continuación se muestran otras etiquetas de plantilla disponibles en Django:
{% comment %} Ignora todo lo que hay dentro del bloque de comentario y permite añadir un comentario opcional:
{% debug %} Muestra el contexto actual pasado a la plantilla.
{% firstof %} Muestra el primer argumento que no evalúe a False:
- Equivale a:
{% lorem %} Muestra contenido «lorem ipsum» aleatorio. Útil para rellenar datos en plantillas:
- Muestra el habitual párrafo «lorem ipsum».
- Muestra 20 palabras en Latín.
- Muestra 3 párrafos en Latín.
{% now %} Muestra la fecha/hora actual (requiere modificadores de formato):
2025-10-04T10:56:23.66833804-10-2025Friday Oct/4/25
{% spaceless %} Elimina los espacios en blanco entre etiquetas HTML:
- Genera:
<p><a href="posts/">Post List</a></p>
{% verbatim %} El contenido incluido dentro de este bloque no será renderizado por Django:
Etiquetas personalizadas¶
Django avanzado
Django permite crear etiquetas personalizadas más allá de las predefinidas («built-in»).
Aunque hay otros tipos, el caso de uso más habitual de una etiqueta personalizada es la de incluir una plantilla que requiere de cierto procesamiento previo. Es lo que Django denomina «inclusion tags» (etiquetas de inclusión).
Supongamos un ejemplo en el que queremos crear una etiqueta personalizada para listar todos los «posts» de nuestro «blog» que superen una determinada valoración.
El modelo del que partimos es el siguiente:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=256)
slug = models.SlugField(max_length=256, unique=True)
content = models.TextField()
rating = models.FloatField(default=0)
def __str__(self):
return self.title
Empezaremos por crear la plantilla que vamos a renderizar:
{% for post in posts %}
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
{% endfor %}
A continuación definimos la etiqueta:
from django import template
from posts.models import Post
register = template.Library()
@register.inclusion_tag('posts/includes/list.html')#(1)!
def post_list(min_rating: int = 0):#(2)!
posts = Post.objects.filter(rating__gte=min_rating)#(3)!
return {'posts': posts}#(4)!
-
- Es necesario registrar la etiqueta usando el decorador
@register. - En este tipo de etiquetas se define la plantilla que se va a renderizar.
- El argumento
takes_context=Trueharía que dispusiéramos del parámetrocontextenpost_list()para acceder al contexto de la petición.
- Es necesario registrar la etiqueta usando el decorador
-
- Esta etiqueta recibe un único parámetro que indica el mínimo «rating» a filtrar.
- Las etiquetas personalizadas admiten cualquier número de parámetros.
- Consulta de los «posts» que cumplen la condición.
- Debemos retornar un diccionario que se convertirá en el contexto para renderizar la plantilla.
Ubicación de las etiquetas
Las etiquetas personalizadas deben ubicarse en una carpeta templatetags dentro de la aplicación correspondiente.
Es «habitual» que si la aplicación se llama foos (por ejemplo) las etiquetas personalizadas estén en foos/templatetags/foo_extras.py (aunque el nombre del módulo es arbritrario).
Lo que nos quedaría es utilizar la etiqueta creada en alguna plantilla:
{% load post_extras %}<!--(1)!-->
<div class="posts">
{% post_list 5 %}<!--(2)!-->
</div>
- Para poder utilizar la etiqueta debemos cargar el módulo en cuestión.
- Dado que
min_ratingtiene un valor por defecto, podríamos usar la etiqueta sin argumentos:{% post_list %}(si queremos todos los «posts»).
Argumentos nominales
Nada impide que pasemos argumentos nominales(1) a nuestra etiqueta personalizada. Eso sí, igual que en el resto de funciones Python, los argumentos nominales deben proporcionarse después de los argumentos posicionales.
- Por ejemplo
{% post_list min_rating=5 %}
Reiniciar servidor de desarrollo
Si ves que no te reconoce la etiqueta personalizada que acabas de implementar, reinicia el servidor de desarrollo.
Filtros¶
Django nos proporciona una enorme cantidad de filtros para utilizar en plantillas. Estos filtros ofrecen funcionalidades muy interesantes dependiendo del contexto que queramos abordar.
Hay dos tipos de filtros:
- Aquellos que no admiten argumentos, cuya sintaxis es:
{{ value|filter }} - Aquellos que sí admiten argumentos, cuya sintaxis es:
{{ value|filter:"argument" }}
En la siguiente tabla se muestran todos los filtros de plantilla que ofrece Django clasificados por el tipo de dato que manejan:
| Filtro | Ejemplo | Descripción |
|---|---|---|
addslashes |
value = "I'm Guido" {{ value|addslashes }}"I\'m Guido" |
Añade barra invertida antes de las comillas. |
capfirst |
value = 'django' {{ value|capfirst }}'Django' |
Pasa a mayúsculas el primer caracter del valor. |
center |
value = 'Django' {{ value|center:"15" }}' Django ' |
Centra el valor indicado en el número de caracteres dado, añadiendo espacios antes y después. |
cut |
value = 'Django-jango' {{ value|cut:"j" }}'Dango-ango' |
Elimina el carácter indicado. |
escape |
value = '<h1>Django</h1>' {{ value|escape }}'<h1>Django</h1>' |
Escapa el contenido HTML. Variantes: escapejs y force_escape. |
iriencode |
value = '?test=I ♥ Django' {{ value|iriencode }}'?test=I%20%E2%99%A5%20Django' |
Convierte una IRI en una cadena de texto lista para ser incluida en una URL. |
json_script |
value = {'hello': 'django'} {{ value|json_script:"greet" }}'<script id="greet" type="application/json">{"hello": "django"}</script>' |
Convierte un objeto Python a un JSON dentro de una etiqueta <script>. |
linebreaks |
value = 'Django is\nawesome' {{ value|linebreaks }}'<p>Django is<br>awesome</p>' |
Reemplaza los saltos de línea por el correspondiente HTML. |
linebreaksbr |
value = 'Django is\nawesome {{ value|linebreaksbr }}'Django is<br>awesome' |
Reemplaza los saltos de línea por HTML <br>. |
linenumbers |
value = 'Django\nis\nawesome {{ value|linenumbers }}'1. Django\n2. is\n3. awesome' |
Añade números de línea. |
ljust |
value = 'Django' {{ value|ljust:"10" }}'Django ' |
Justifica a la izquierda rellenando con espacios a la derecha la cantidad indicada. |
lower |
value = 'DJANGO IS AWESOME' {{ value|lower }}'django is awesome' |
Pasa el valor a minúsculas. |
make_list |
value = 'django' {{ value|make_list }}['d', 'j', 'a', 'n', 'g', 'o'] |
Convierte el argumento a una lista. |
phone2numeric |
value = '800-COLLECT' {{ value|phone2numeric }}'800-2655328' |
Convierte un número de teléfono (posiblemente con letras) a su equivalente numérico. |
pluralize |
value = 2 message{{ value|pluralize }}'messages' |
Devuelve un sufijo plural cuando el valor es mayor que 1. Se puede especificar el sufijo como argumento. |
pprint |
value = 'something to debug' message{{ value|pprint }}'something nice to debug' |
Hace una llamada a pprint.pprint. Principalmente para depuración. |
rjust |
value = 'Django' {{ value|rjust:"10" }}' Django' |
Justifica a la derecha rellenando con espacios a la izquierda la cantidad indicada. |
safe |
value = '<h1>Django</h1>' {{ value|safe }}'<h1>Django</h1>' |
Marca una cadena de texto como HTML seguro listo para mostrar en la página. |
slugify |
value = 'Become a slug!' {{ value|slugify }}'become-a-slug' |
Devuelve el valor convertido a un «slug». |
striptags |
value = '<h1>Django</h1>' {{ value|striptags }}'Django' |
Elimina las etiquetas HTML encontradas. |
title |
value = 'django is awesome' {{ value|title }}'Django Is Awesome' |
Pasa el valor a título. |
truncatechars |
value = 'Welcome to our flight to Python World' {{ value|truncatechars:7 }}'Welcome...' |
Trunca el valor al número de caracteres indicados como argumento. |
truncatechars_html |
value = '<p>Welcome to our flight to Python World</p>' {{ value|truncatechars_html:7 }}'<p>Welcome...</p>' |
Trunca el valor al número de caracteres indicados como argumento, respetando las etiquetas HTML. |
truncatewords |
value = 'Welcome to our flight to Python World' {{ value|truncatewords:4 }}'Welcome to our flight...' |
Trunca el valor al número de palabras indicadas como argumento. |
truncatewords_html |
value = '<p>Welcome to our flight to Python World</p>' {{ value|truncatewords_html:4 }}'<p>Welcome to our flight...</p>' |
Trunca el valor al número de palabras indicadas como argumento, respetando las etiquetas HTML. |
upper |
value = 'django is awesome' {{ value|upper }}'DJANGO IS AWESOME' |
Pasa el valor a mayúsculas. |
urlencode |
value = 'https://django.com/query?a=b&c=d' {{ value|urlencode }}'https://django.com/query%3Fa%3Db%26c%3Dd' |
Escapa un valor para usarlo en una URL. |
urlize |
value = 'Check out https://python.org' {{ value|urlize }}'Check out <a href="https://python.org">python.org</a>' |
Convierte el argumento a un enlace HTML. |
urlizetrunc |
value = 'Check out https://python.org' {{ value|urlizetrunc:2 }}'Check out <a href="https://python.org">py...</a>' |
Convierte el argumento a un enlace HTML (truncando la longitud indicada). |
wordcount |
value = 'Django is awesome!' {{ value|wordcount }}3 |
Devuelve el número de palabras del valor. |
wordwrap |
value = 'Django is awesome' {{ value|wordwrap:6 }}Django\nis\nawesome |
Incluye saltos de línea con palabras del tamaño indicado. |
| Filtro | Ejemplo | Descripción |
|---|---|---|
escapeseq |
value = ['<p>', '<h1>'] {{ value|escapeseq }}['<p>', '<h1>'] |
Aplica el filtro escape a cada elemento de la lista. |
first |
value = [6, 4, 8] {{ value|first }}6 |
Devuelve el primer elemento de una lista. |
join |
value = ['x', 'y', 'z'] {{ value|join:"|" }}'x|y|z' |
Une la lista utilizando el argumento dado. |
last |
value = [6, 4, 8] {{ value|last }}8 |
Devuelve el último elemento de una lista. |
length |
value = ['a', 'b', 'c'] {{ value|length }}3 |
Devuelve la longitud del valor (list o str). |
random |
value = [6, 4, 8] {{ value|random }}4 |
Devuelve un elemento aleatoria de la lista dada. |
slice |
value = [9, 3, 7, 2] {{ value|slice:":2" }}[9, 3] |
Devuelve un troceado de la lista en índice . |
safeseq |
value = ['<p>', '<h1>'] {{ value|escapeseq }}['<p>', '<h1>'] |
Aplica el filtro safe a cada elemento de la lista. |
unordered_list |
value = ['A', ['B', 'C']] {{ value|unordered_list }}'<li>A<ul><li>B</li><li>C</li></ul></li>' |
Convierte una lista anidada en una lista no ordenada HTML. |
| Filtro | Ejemplo | Descripción |
|---|---|---|
add |
value = 5 {{ value|add:"2" }}7 |
Suma el argumento al valor. |
divisibleby |
value = 15 {{ value|divisibleby:"5" }}True |
Devuelve True si el valor es divisible por el argumento o False en otro caso. |
filesizeformat |
value = 123456789 {{ value|filesizeformat }}'117.7 MB' |
Formatea el valor como un tamaño de fichero legible por un humano. |
get_digit |
value = 123456789 {{ value|get_digit:"2" }}8 |
Devuelve el dígito que ocupa la posición del argumento indicado (empezando por la derecha) en índice . |
stringformat |
value = '3.141516' {{ value|stringformat:".3f" }}'3.142' |
Formatea el valor de acuerdo a la especificación del argumento. |
| Filtro | Ejemplo | Descripción |
|---|---|---|
date |
value = datetime.date(2024, 10, 17) {{ value|date:"d/m/Y" }}'17/10/2024' |
Formatea un objeto de tipo fecha. |
time |
value = datetime.now() {{ value|time:"H:i" }}'04:32' |
Formatea un objeto de tipo hora. |
timesince |
value = datetime.datetime() {{ value|timesince }}'4 days, 6 hours' |
Indica en formato «humano» el tiempo que ha pasado desde el valor indicado. Se puede especificar como argumento otro momento de comparación distinto a ahora. |
timeuntil |
value = datetime.datetime() {{ value|timeuntil }}'6 days, 4 hours' |
Indica en formato «humano» el tiempo que falta hasta el valor indicado. Se puede especificar como argumento otro momento de comparación distinto a ahora. |
| Filtro | Ejemplo | Descripción |
|---|---|---|
default |
value = None {{ value|default:"empty" }}'empty' |
Si el valor evalúa a False muestra el argumento indicado. |
default_if_none |
value = None {{ value|default_if_none:"nothing" }}'nothing' |
Si (y solo si) el valor es None muestra el argumento indicado. |
yesno |
value = 1 {{ value|yesno:"good,bad,regular"' }}'good' |
Si el valor es True se devuelve el primer argumento.Si el valor es False se devuelve el segundo argumento.Si el valor es None se devuelve el tercer argumento (opcional). |
| Filtro | Ejemplo | Descripción |
|---|---|---|
dictsort |
value = [{'name': 'Carry', 'age': 32}, {'name': 'Mike', 'age': 21}] {{ value|dictsort:"age" }}[{'name': 'Mike', 'age': 21}, {'name: 'Carry', age: 32}] |
Ordena una lista de diccionarios por la clave indicada en el argumento. |
dictsortreversed |
value = [{'name': 'Carry', 'age': 32}, {'name': 'Mike', 'age': 21}] {{ value|dictsortreversed:"age" }}[{'name: 'Carry', age: 32}, {'name': 'Mike', 'age': 21}] |
Ordena una lista de diccionarios (de forma inversa/descendente) por la clave indicada en el argumento. |
Filtros personalizados¶
Django avanzado
Django permite crear filtros personalizados más allá de los predefinidos («built-in»).
A diferencia de las etiquetas los filtros deben recibir un argumento (y eventualmente otro). El primer argumento es el valor de la variable a la que aplicamos el filtro y el segundo argumento es opcional y permite modificar el comportamiento predefinido.
A continuación planteamos un ejemplo en el que se crea un filtro personalizado para calcular el «tamaño» de un «post» en función de varias métricas:
from django import template
from posts.models import Post
register = template.Library()
@register.filter#(1)!
def post_size(post: Post, metric: str = 'by-words') -> int:#(2)!
match metric:
case 'by-words':
size = len(post.content.split())
case 'by-chars':
size = len(post.content)
case _:
size = 0
return size#(3)!
-
- Es necesario registrar el filtro usando el decorador
@register. - Es posible pasar un parámetro
name(comostr) al decorador para indicar el nombre del filtro. Si no se pasa, el nombre del filtro será el nombre de la función. Por ejemplo@register.filter(name='psize')
- Es necesario registrar el filtro usando el decorador
- Los parámetros, en este caso son:
- «Post» sobre el que vamos a calcular el tamaño.
- Tipo de métrica:
'by-words'para contar el número de palabras.'by-chars'para contar el número de caracteres.
- La función (filtro) devuelve un número entero.
- Retornamos el tamaño calculado según la lógica de negocio correspondiente.
Ubicación de los filtros
Los filtros personalizadas deben ubicarse en una carpeta templatetags dentro de la aplicación correspondiente.
Es «habitual» que si la aplicación se llama foos (por ejemplo) los filtros personalizados estén en foos/templatetags/foo_extras.py (aunque el nombre del módulo es arbritrario).
Lo que nos quedaría es utilizar el filtro creado en alguna plantilla:
{% load post_extras %}<!--(1)!-->
<div class="posts">
{% for post in posts %}
<div class="post">
{{ post|post_size }}<!--(2)!-->
</div>
{% endfor %}
</div>
- Para poder utilizar el filtro debemos cargar el módulo en cuestión.
-
- La variable
postse pasa como primer argumento. En este caso no hay segundo argumento. - Si quisiéramos una métrica por caracteres, podríamos haber escrito:
{{ post|post_size:"by-chars" }}
- La variable
Múltiples argumentos
Si se diera el caso de necesitar desarrollar alguna funcionalidad en plantilla con más de dos argumentos y que su comportamiento fuera «similar» al de un filtro personalizado, Django ofrece la posibilidad de implementar etiquetas personalizadas simples.
Devolviendo HTML¶
Hay ocasiones en las que nos interesa implementar un filtro que devuelva código HTML. En principio lo haríamos de la misma forma que se ha visto anteriormente devolviendo una cadena de texto con el código HTML correspondiente.
Pero hay que tener en cuenta ciertos aspectos de seguridad:
- Si el código HTML que vamos a devolver desde el filtro contiene potencial información proveniente del usuario (vía formulario por ejemplo), es altamente recomendable utilizar la función
format_htmlque se encarga de escapar sus argumentos. Evitaríamos por ejemplo ataques XSS. - Si el código HTML que vamos a devolver contiene información confiable, necesitamos «marcarlo como seguro» para que Django realmente lo renderice en la plantilla final. Para ello haríamos uso de la función
mark_safe.
Un ejemplo podría ser mostrar un determinado «post» con un formato HTML destacado:
from django import template
from django.utils.html import format_html#(1)!
from posts.models import Post
register = template.Library()
@register.filter
def post_link(post: Post) -> str:
return format_html(
'<a href="{}">{}</a>',
post.get_absolute_url(),
post.title,
)#(2)!
- Importamos la función
format_htmlnecesaria para «securizar» nuestro código. - Interpolamos los atributos necesarios del «post».
Reiniciar servidor de desarrollo
Si ves que no te reconoce el filtro personalizado que acabas de implementar, reinicia el servidor de desarrollo.
Procesadores de contexto¶
Django avanzado
La explicación de que en las plantillas tengamos acceso a los datos de depuración, a la petición HTTP, a la autenticación o a los mensajes, es que existen unos artefactos llamados procesadores de contexto que se encargan de inyectar cierta información en el contexto de la plantilla.
Estos procesadores de contexto se especifican en el fichero de configuración del proyecto —TEMPLATES → OPTIONS → context_processors— y por defecto es una lista que toma los siguientes valores:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Procesadores de contexto personalizados¶
Django nos permite implementar nuestros propios procesadores de contexto con el objetivo de inyectar en «todas» las plantillas ciertos datos comunes.
Planteamos un ejemplo en el que estamos diseñando un «blog» y queremos tener acceso en todo momento al último «post» que se ha publicado.
Un procesador de contexto no es más que una función que vive en algún lugar (aplicación) de nuestro proyecto:
from posts.models import Post
def last_post(request) -> dict:#(1)!
if post := Post.objects.last():#(2)!
return {'last_post': post}#(3)!
return {}#(4)!
-
- Función que actúa como procesador de contexto.
- Siempre recibe
request(petición HTTP). - Siempre devuelve un diccionario.
-
- Lógica del procesador de contexto.
- En este caso se busca el último «post» de la base de datos.
-
- Se devuelve un diccionario.
- El objeto
last_postestará disponible en todas las plantillas de forma automática.
- En caso de errores se devuelve el diccionario vacío.
Para que Django «conozca» la existencia de este procesador de contexto debemos indicar su ruta completa en el fichero settings.py del proyecto:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'posts.context_processors.last_post',
],
},
},
]
De esta forma, en todas las plantillas del proyecto tendremos disponible el objeto last_post con el último «post» publicado en el «blog»:
<header>
<h1>The ultimate blog</h1>
</header>
<section class="last-post">
{{ last_post }}
</section>