Saltar a contenido

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:

  1. Busca dentro de la carpeta /templates de cada aplicación del proyecto.(1)
  2. Busca en otras carpetas definidas explícitamente en la configuración del proyecto.(2)
  1. Siempre y cuando la variable APP_DIRS definida en settings.py esté a True:
    main/settings.py
    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',
                ],
            },
        },
    ]
    
  2. Las «otras» ubicaciones de plantillas se pueden indicar mediante la variable DIRS definida en settings.py.

    main/settings.py
    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',
                ],
            },
        },
    ]
    

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:

posts/templates/posts/post/detail.html
<h2>{{ post.title }}</h2><!--(1)!-->
<p>{{ post.content }}</p>

  1. También podríamos haber usado directamente {{ post }} siempre y cuando se haya implementado convenientemente el método __str__() de la clase Post.

Desde la correspondiente vista, tendremos que renderizar la plantilla anterior mediante el siguiente fragmento de código:

posts/views.py
from django.shortcuts import render


def post_detail(request, post_slug: str):
    # ...
    return render(request, 'posts/post/detail.html', {'post': post})#(1)!

  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:

<h2>post.title.upper</h2>

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:

main/settings.py
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)!
        },
    },
]

  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:

posts/templates/posts/post/list.html
<ul>
    {% for post in posts %}<!--(1)!-->
        <li>{{ post }}</li><!--(2)!-->
    {% endfor %}<!--(3)!-->
</ul>

  1. En esta línea podemos usar directamente las variables sin usar doble .
  2. Aquí si tenemos que acceder a la variable con doble .
  3. 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:

posts/views.py
from django.shortcuts import render


def post_list(request):
    # ...
    return render(request, 'posts/post/list.html', {'posts': posts})#(1)!

  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:

<div class="points">
{% for x, y in points %}
    <p>{{ x }},{{ y }}</p>
{% endfor %}
</div>

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

posts/templates/posts/post/list.html
<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»:

posts/templates/posts/post/list.html
<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»:

posts/templates/posts/post/list.html
<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»:

posts/templates/posts/post/list.html
{% 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 %}

  1. Aplicamos una condición sobre el número de iteración.
  2. No es obligatorio el uso de {% else %}.
  3. 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:

posts/templates/posts/post/list.html
<a href="{% url 'posts:post-list' %}"><!--(1)!-->
    Ver todos los posts
</a>

  1. Al renderizar: <a href="/posts/">Ver todos los posts</a>

posts/templates/posts/post/detail.html
<a href="{% url 'posts:post-detail' post.slug %}"><!--(1)!-->
    {{ post.title }}
</a>

    • Al ser una URL con parámetro, es necesario pasar un argumento post.slug.
    • Al renderizar: <a href="/posts/django-is-awesome/">Django is awesome</a>

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:

shared/templates/base.html
<!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>

    • <title>Technology | Blog</title> si se envía título en el contexto {'title': 'Technology'} de la vista.
    • <title>Blog</title> si no se envía título en el contexto {} de la vista.
  1. 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:

posts/templates/posts/post/list.html
{% 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»:

shared/templates/header.html
<div class="header">
    <h1>The ultimate blog</h1>
    <h2>{{ subtitle }}</h1><!--(1)!-->
</div>

  1. Es perfectamente válido utilizar variables o etiquetas dentro de las plantillas a incluir.

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!'}:

posts/templates/posts/post/list.html
{% include "header.html" %} 
...

Se incluye la plantilla utilizando los argumentos indicados en la propia sentencia:

posts/templates/posts/post/list.html
{% include "header.html" with subtitle="Don't miss the cutting edge info!" %} 
...

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:

{% comment "Comentario opcional" %}
    <p>This won't be rendered</p>
{% endcomment %}

{% debug %} Muestra el contexto actual pasado a la plantilla.

{% debug %}

{% firstof %} Muestra el primer argumento que no evalúe a False:

{% firstof var1 var2 var3 %}<!--(1)!-->

  1. Equivale a:
    {% if var1 %}
        {{ var1 }}
    {% elif var2 %}
        {{ var2 }}
    {% elif var3 %}
        {{ var3 }}
    {% endif %}
    

{% lorem %} Muestra contenido «lorem ipsum» aleatorio. Útil para rellenar datos en plantillas:

{% lorem %}<!--(1)!-->
{% lorem 20 w %}<!--(2)!-->
{% lorem 3 p %}<!--(3)!-->

  1. Muestra el habitual párrafo «lorem ipsum».
  2. Muestra 20 palabras en Latín.
  3. Muestra 3 párrafos en Latín.

{% now %} Muestra la fecha/hora actual (requiere modificadores de formato):

{% now "c" %}<!--(1)!-->
{% now "d-m-Y" %}<!--(2)!-->
{% now "l M/j/y" %}<!--(3)!-->

  1. 2025-10-04T10:56:23.668338
  2. 04-10-2025
  3. Friday Oct/4/25

{% spaceless %} Elimina los espacios en blanco entre etiquetas HTML:

{% spaceless %}<!--(1)!-->
    <p>
        <a href="posts/">Post list</a>
    </p>
{% endspaceless %}

  1. Genera: <p><a href="posts/">Post List</a></p>

{% verbatim %} El contenido incluido dentro de este bloque no será renderizado por Django:

{% verbatim %}
    <p>This won't be {{ var }} rendered</p>
{% endverbatim %}

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:

posts/models.py
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:

posts/templates/posts/includes/list.html
{% for post in posts %}
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
{% endfor %}

A continuación definimos la etiqueta:

posts/templatetags/post_extras.py
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=True haría que dispusiéramos del parámetro context en post_list() para acceder al contexto de la petición.
    • 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.
  1. Consulta de los «posts» que cumplen la condición.
  2. 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:

posts/templates/posts/post/list.html
{% load post_extras %}<!--(1)!-->

<div class="posts">
    {% post_list 5 %}<!--(2)!-->
</div>

  1. Para poder utilizar la etiqueta debemos cargar el módulo en cuestión.
  2. Dado que min_rating tiene 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.

  1. 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:

  1. Aquellos que no admiten argumentos, cuya sintaxis es: {{ value|filter }}
  2. Aquellos que 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 }}
'&lt;h1&gt;Django&lt;/h1&gt;'
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 }}
['&lt;p&gt;', '&lt;h1&gt;']
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:

posts/templatetags/post_extras.py
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 (como str) 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')
  1. 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.
  2. 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:

posts/templates/posts/post/list.html
{% load post_extras %}<!--(1)!-->

<div class="posts">
    {% for post in posts %}
        <div class="post">
            {{ post|post_size }}<!--(2)!-->
        </div>
    {% endfor %}
</div>

  1. Para poder utilizar el filtro debemos cargar el módulo en cuestión.
    • La variable post se 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" }}
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:

  1. 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_html que se encarga de escapar sus argumentos. Evitaríamos por ejemplo ataques XSS.
  2. 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:

posts/templatetags/post_extras.py
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)!

  1. Importamos la función format_html necesaria para «securizar» nuestro código.
  2. Interpolamos los atributos necesarios del «post».
posts/templates/posts/post/list.html
{% load post_extras %}

<div class="posts">
    {% for post in posts %}
        <div class="post">
            {{ post|post_link }}
        </div>
    {% endfor %}
</div>

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 —TEMPLATESOPTIONScontext_processors— y por defecto es una lista que toma los siguientes valores:

main/settings.py
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:

posts/context_processors.py
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_post estará disponible en todas las plantillas de forma automática.
  1. 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:

main/settings.py
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»:

shared/templates/base.html
<header>
    <h1>The ultimate blog</h1>
</header>

<section class="last-post">
    {{ last_post }}
</section>