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:
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:
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):
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)!
- Importamos el objeto
messagespara 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 éxito mediante el método
-
- Añadimos un mensaje de error mediante el método
add_message(). - Atajo para:
messages.add_message(request, messages.ERROR, 'Post does not exist')
- Añadimos un mensaje de error mediante el método
- No es necesario incluir los mensajes en el contexto porque el «middleware» ya se encarga de ello.
{% 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>
- Cada mensaje dispone de una etiqueta
tagque se está usando como clase CSS del mensaje.
El código HTML generado para el bloque de mensajes es similar a:
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»:
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
- Por convención se suele añadir el sufijo
Middlewareal nombre de la clase que implementa el «middleware». - 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.
- Podríamos decir que el método
- 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
requesty la excepción lanzada comoexception.
- El método
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: