Saltar a contenido

Middleware

Django intermedio

Un «middleware» —en el contexto de Django— es un artefacto de software que se sitúa entre la petición y la respuesta HTTP y permite alterar la entrada o la salida a conveniencia de manera global.

graph LR
  req[HttpRequest] --> middleware[Middleware]
  middleware --> resp[HttpResponse]

Middleware disponible

Django proporciona una serie de «middleware» predefinido que podemos activar bajo demanda.

Si nos fijamos en un proyecto nuevo («fresh») de Django podremos observar que muchos de estos «middleware» ya se encuentran activados por defecto:

main/settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Cada «middleware» define una o varias clases que aportan distintas funcionalidades:

Middleware Ruta Descripción ¿Por defecto?
Cache 'django.middleware.cache.UpdateCacheMiddleware'
'django.middleware.cache.FetchFromCacheMiddleware'
Habilita la caché de Django
Common 'django.middleware.common.CommonMiddleware' Realiza distintas acciones comunes
GZip 'django.middleware.gzip.GZipMiddleware' Comprime contenido en formato GZip
Conditional GET 'django.middleware.http.ConditionalGetMiddleware' Maneja operaciones GET condicionales
Locale 'django.middleware.locale.LocaleMiddleware' Habilita la selección de idioma sobre los datos de la petición
Message 'django.contrib.messages.middleware.MessageMiddleware' Habilita el soporte para mensajes
Security 'django.middleware.security.SecurityMiddleware' Proporciona mejoras de seguridad al ciclo petición/respuesta
Session 'django.contrib.sessions.middleware.SessionMiddleware' Habilita el soporte de sesiones
Site 'django.contrib.sites.middleware.CurrentSiteMiddleware' Añade el atributo site a la petición
Authentication 'django.contrib.auth.middleware.AuthenticationMiddleware'
'django.contrib.auth.middleware.LoginRequiredMiddleware'
'django.contrib.auth.middleware.RemoteUserMiddleware'
'django.contrib.auth.middleware.PersistentRemoteUserMiddleware'
Habilita los mecanismos de autenticación
CSRF protection 'django.middleware.csrf.CsrfViewMiddleware' Añade protección contra CSRF
X-Frame-Options 'django.middleware.clickjacking.XFrameOptionsMiddleware' Añade protección simple contra «clickjacking»

Mensajes

El «middleware» de mensajes permite crear y publicar mensajes en nuestro proyecto web de manera rápida y sencilla.

Django ya lo tiene habilitado por defecto, pero si no fuera así, para activarlo simplemente tendríamos que añadirlo a la lista MIDDLEWARE en el fichero de configuraciones:

main/settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Mediante este «middleware» Django proporciona un objeto messages (disponible en vistas y plantillas) que contendrá la lista de mensajes que queremos notificar.

Para cada mensaje podemos indicar el nivel informativo asociado. El módulo messages dentro de django.contrib contiene las siguientes constantes:

Nivel Etiqueta Objetivo
DEBUG debug Mensaje de depuración
INFO info Mensajes informativo
SUCCESS success Mensaje de operación exitosa
WARNING warning Mensaje de advertencia
ERROR error Mensaje de error

Supongamos un ejemplo en el que queremos notificar al usuario que el «post» de un «blog» ha sido borrado satisfactoriamente (o no):

posts/views.py
from django.contrib import messages#(1)!
from django.shortcuts import render

from .models import Post


def delete_post(request, post_slug: str):
    try:
        post = Post.objects.get(slug=post_slug)
        post.delete()
        messages.success(request, 'Post deleted successfully')#(2)!
    except Post.DoesNotExist:
        messages.error(request, 'Post does not exist')#(3)!
    posts = Post.objects.all()
    return render(request, 'posts/post/list.html', {'posts': posts})#(4)!

  1. Importamos el objeto messages para gestionar los mensajes.
    • Añadimos un mensaje de éxito mediante el método add_message().
    • Atajo para: messages.add_message(request, messages.SUCCESS, 'Post delete successfully')
    • Añadimos un mensaje de error mediante el método add_message().
    • Atajo para: messages.add_message(request, messages.ERROR, 'Post does not exist')
  2. No es necesario incluir los mensajes en el contexto porque el «middleware» ya se encarga de ello.

posts/post/list.html
{% if messages %}
<ul class="messages">
    {% for message in messages %}
        <li{% if message.tags %} class="{{ message.tags }}"{% endif %}><!--(1)!-->
            {{ message }}
        </li>
    {% endfor %}
</ul>
{% endif %}

<ul class="posts">
{% for post in posts %}
    <li><a href="{% url 'posts:post-detail' post.slug %}">{{ post.title }}</a></li>
{% endfor %}
</ul>

  1. Cada mensaje dispone de una etiqueta tag que se está usando como clase CSS del mensaje.

El código HTML generado para el bloque de mensajes es similar a:

<ul class="messages">
    <li class="success">
        Post deleted successfully
    </li>
</ul>

El bloque de mensajes <ul>...</ul> es apropiado para incluirlo en plantillas base.

Middleware personalizado

Django avanzado

Más allá de los «middleware» existentes en Django, es posible implementar un «middleware» personalizado para aquellas tareas que sean necesarias aplicar de manera global en el ciclo petición/respuesta.

Lo único que necesitamos hacer es escribir una clase sobreescribiendo unos ciertos métodos predefinidos y activar el citado «middleware».

Imaginemos un ejemplo en el que queremos medir el tiempo de carga de cada petición en el proyecto del «blog»:

shared/middleware.py
import time


class RequestTimeMiddleware:#(1)!
    def __init__(self, get_response):#(2)!
        self.get_response = get_response

    def __call__(self, request):#(3)!
        # Code execution before view calling ↓
        start_time = time.time()
        # View calling ↓
        response = self.get_response(request)
        # Code execution after view calling ↓
        duration = (time.time() - start_time) * 1000
        print(f'⏱️ Request to {request.path} took {duration:.4f} ms')
        return response#(4)!

    def process_exception(self, request, exception):#(5)!
        pass

  1. Por convención se suele añadir el sufijo Middleware al nombre de la clase que implementa el «middleware».
  2. El constructor recibe la función get_response().
    • Podríamos decir que el método __call__() es el punto más interesante donde podemos modificar «cosas».
    • Recibe la petición HTTP como request.
  3. Es fundamental que este método devuelva siempre la respuesta HTTP.
    • El método process_exception() se llama cuando una vista lanza una excepción.
    • Recibe la petición HTTP como request y la excepción lanzada como exception.
main/settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'shared.middleware.RequestTimeMiddleware'
] 

Ahora cada vez que se realice una petición a nuestro «blog» quedará registrado su tiempo de carga por pantalla:

⏱️ Request to /es/posts/ took 4.9818 ms
[04/Dec/2025 09:25:21] "GET /es/posts/ HTTP/1.1" 200 229