Template tags y filtros personalizados

Intermedio
Django
Django
Actualizado: 18/04/2026

Módulo templatetags

Para crear tags y filtros personalizados, la aplicación debe contener un paquete llamado templatetags:

catalogo/
    templatetags/
        __init__.py
        catalogo_tags.py   # Nuestros tags y filtros

Diagrama conceptual de Template tags y filtros personalizados

El archivo __init__.py es obligatorio para que Python reconozca el directorio como paquete.

Filtros personalizados

Un filtro transforma el valor de una variable en la plantilla. Se registra con el decorador @register.filter:

# catalogo/templatetags/catalogo_tags.py
from django import template
from django.utils.safestring import mark_safe
import locale

register = template.Library()

@register.filter
def precio_formateado(valor):
    """Formatea un decimal como precio en euros: 1299.99 → '1.299,99 €'"""
    try:
        return f"{float(valor):,.2f} €".replace(',', 'X').replace('.', ',').replace('X', '.')
    except (ValueError, TypeError):
        return '—'

@register.filter(name='truncar_palabras')
def truncar_palabras(texto, num):
    """Trunca el texto a N palabras y añade puntos suspensivos."""
    palabras = str(texto).split()
    if len(palabras) <= num:
        return texto
    return ' '.join(palabras[:num]) + '…'

@register.filter
def es_multiplo(valor, divisor):
    """Devuelve True si valor es múltiplo de divisor."""
    try:
        return int(valor) % int(divisor) == 0
    except (ValueError, TypeError, ZeroDivisionError):
        return False

@register.filter(is_safe=True)
def estrellas(puntuacion, maximo=5):
    """Convierte una puntuación numérica en iconos de estrellas."""
    puntuacion = min(int(puntuacion), maximo)
    llenas = '★' * puntuacion
    vacias = '☆' * (maximo - puntuacion)
    return mark_safe(f'<span class="estrellas">{llenas}{vacias}</span>')

Uso en plantillas:

{% load catalogo_tags %}

<p>{{ producto.precio|precio_formateado }}</p>
<p>{{ producto.descripcion|truncar_palabras:20 }}</p>
<div>{{ resena.puntuacion|estrellas:5 }}</div>

{% for i in lista %}
    {% if forloop.counter|es_multiplo:3 %}
        <div class="nueva-fila"></div>
    {% endif %}
{% endfor %}

Simple tags

Los simple_tags son como filtros pero pueden recibir múltiples argumentos y tienen acceso al contexto:

@register.simple_tag
def url_con_parametros(base_url, **kwargs):
    """Construye una URL con parámetros GET."""
    from urllib.parse import urlencode
    params = urlencode({k: v for k, v in kwargs.items() if v})
    return f"{base_url}?{params}" if params else base_url

@register.simple_tag(takes_context=True)
def url_activa(context, url_name):
    """Devuelve 'active' si la URL actual coincide con url_name."""
    from django.urls import reverse
    try:
        request = context['request']
        return 'active' if request.path == reverse(url_name) else ''
    except Exception:
        return ''

Uso:

{% load catalogo_tags %}

<a href="{% url_con_parametros '/productos/' categoria='libros' orden='precio' %}">
    Libros ordenados por precio
</a>

<a class="{% url_activa 'catalogo:producto-list' %}" href="{% url 'catalogo:producto-list' %}">
    Productos
</a>

Inclusión tags

Los inclusion_tags renderizan una plantilla parcial con su propio contexto, perfectos para componentes reutilizables:

@register.inclusion_tag('catalogo/componentes/breadcrumb.html', takes_context=True)
def breadcrumb(context, *elementos):
    """Renderiza el breadcrumb de navegación."""
    return {
        'elementos': elementos,
        'request': context.get('request'),
    }

@register.inclusion_tag('catalogo/componentes/paginacion.html')
def paginacion(page_obj, parametros_url=''):
    """Renderiza los controles de paginación."""
    return {
        'page_obj': page_obj,
        'parametros_url': parametros_url,
    }
<!-- catalogo/componentes/paginacion.html -->
{% if page_obj.has_other_pages %}
<nav class="paginacion">
    {% if page_obj.has_previous %}
        <a href="?page={{ page_obj.previous_page_number }}&{{ parametros_url }}">‹</a>
    {% endif %}
    <span>{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span>
    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}&{{ parametros_url }}">›</a>
    {% endif %}
</nav>
{% endif %}

Uso:

{% load catalogo_tags %}

{% breadcrumb "Inicio" "Catálogo" "Electrónica" %}

{% paginacion page_obj "categoria=electronica" %}

Cargar tags en todas las plantillas

Para no tener que escribir {% load catalogo_tags %} en cada plantilla, se pueden agregar las librerías en settings.py:

TEMPLATES = [
    {
        'OPTIONS': {
            'builtins': [
                'catalogo.templatetags.catalogo_tags',
            ],
        },
    },
]

Los template tags y filtros personalizados son la herramienta correcta para encapsular lógica de presentación compleja que sería inapropiado incluir directamente en las vistas o en el modelo.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Django

Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Crear el módulo templatetags dentro de una aplicación Django. Registrar un filtro personalizado con @register.filter. Implementar template tags simples con @register.simple_tag. Crear inclusion_tags que renderizan una sub-plantilla con contexto propio. Usar los custom tags y filtros desde las plantillas con {% load %}.