Middlewares Avanzados En Django

Fecha de publicación: 16 Septiembre 2020
Tiempo de lectura: 6 min.
Premium: False
Número de visitas: 118


En una entrega anterior aprendimos a crear e implementar nuestros propios middlewares en Django. Si recordamos logramos estos utilizando funciones, muy puntualmente un decorador. Aquí un pequeño ejemplo.

def my_super_middleware(get_response):

    def middleware(request):
        # >>> Código que se ejecutan del llamando a la vista.
        response = get_response(request)
        # >>> Código que se después del llamando a la vista.

        return response

    return middleware

Para utilizar nuestros middlewares será necesarios registrarlos al proyecto, agregándolos a la lista middleware del archivo _settings.py. 🧐

MIDDLEWARE = [
    'my_app.middlewares.my_super_middleware',
]

Bastante sencillo ¿no? Si recordamos los middleware nos permitirán extender funcionalidades sobre nuestras vistas, ya sea ejecutando código antes o después del llamado a la misma.

Los middlewares serán ejecutados por cada petición realizada al servidor, lo cual nos viene perfecto si deseamos validar o simplemente debuguear nuestra aplicación.

Lo interesantes de lo middlewares en Django es que no están limitado a ser implementados únicamente mediante funciones, no, nada de ello. Si nosotros así lo deseamos seremos capaces de crear nuestros propios middlewares utilizando clases. Al nosotros hacer esto, crearemos middlewares mucho más robustos y flexibles que si lo hiciéramos con una función.

Y eso es justo lo que haremos en esta ocasión, aprenderemos a crear middlewares utilizando clases. Es algo bastante interesante, veamos. 😎

Middlewares mediante clases

Antes de comenzar el tutorial es importante mencionar un par de cosas:

  • Me encuentro trabajando con Django en su versión 3.2.
  • Todos los middlewares se encuentran dentro de mi archivo middleware.py de mi aplicación my_app.

Bien, una ves dicho todo esto, pongamos manos a la obra.


Vayamos directo al grano, aquí un pequeño ejemplo de cómo implementar un middleware mediante una clase. Será necesario definir tanto el método init como el método call.

class MySuperMiddleware():

    def __init__(self, get_response):
        self.get_response = get_response

        print('>>> Middleware registrato exitosamente.')

    def __call__(self, request):

        # >>> Código antes del llamando a la vista.
        response = self.get_response(request)
        # >>> Código después del llamando a la vista.

        return response

Que te parece si desglosamos la clase método por método. Comencemos con el método init. Este método nos permitirá definir, e inicializar la n cantidad de atributos que nuestros Middleware necesite. El método, de forma obligatoria, deberá poseer un solo parámetro, me refiero a la función get_response. Esta función será la encargada de llamar a la vista correspondiente quien resolverá la petición.

Por temas de rendimientos el método init será llamado una sola vez, esto cuando el servidor sea lanzado. Teniendo esto en cuenta será necesario almacenar la función get_response dentro de un atributo, esto para que posteriormente podamos hacer uso de ella.

self.get_response = get_response

Ahora hablemos del método call. Este método, a diferencia del método init, será llamado por cada petición realizada al servidor. El método recibirá como argumento dicha petición y deberá retornar un objeto de tipo HTTPResponse.

En la mayoría de los casos el método será el encargado de ejecutar la función get_response, que a su vez (cómo mencionamos anteriormente) hará el llamado a la vista correspondiente.

A través de este método seremos capaces de extender funcionalidades sobre nuestra vista, ejecutando código antes o después de su llamado.

Ok, hasta el momento tenemos 3 elementos a considerar:

  • Método init; método encargado de definir, e inicializar los atributos para nuestro Middleware. Deberá poseer un solo parámetro, la función get_response.
  • Función get_response; función encargada de encontrar y ejecutar la vista correspondiente que resolverá le petición. Recibe como argumento la petición y retorna lo que la viste retorne.
  • Método call; método que será llamado por cada petición realizada al servidor. Recibe como argumento la petición y deberá retornar un objeto de tipo HTTPResponse.

Por supuesto, para hacer uso de nuestro middleware será necesario registrarlo.

MIDDLEWARE = [
    'my_app.middlewares.MySuperMiddleware',
]

Algo interesante de crear middlewares utilizando clases es la posibilidad de ejecutar código en momentos muy puntales del proceso petición-respuesta (request-response), esto mediante 3 métodos:

  • process_view
  • process_template_response
  • process_exception

Expliquemos en detalle cada uno de ellos.

Método process_view.

El método process_view será llamado una vez Django conozca qué vista será la encargada de resolver la petición, pero antes que esta sea ejecutada.

A través de este método tendremos acceso a la petición (request), vista (view_func), al listado de argumentos para la vista (view_args) y al diccionario de parámetros para la vista (view_kwargs ).

Le método deberá retornar None o un objeto HTTPResponse. Si es retornado None Django continua con el proceso habitual, llamando cualquier otro método process_view de los siguientes middlewares. Por el contrario, si es retornado un objeto HTTPResponse, Django retornara inmediatamente dicho objeto al cliente.

Ojo, no será necesario hacer el llamado a la vista, Django de forma interna ya se encarga de ello.

def process_view(self, request, view_func, view_args, view_kwargs):
    # >>> El método process_view se ejecuta antes del llamado a la vista
    return None

Este método nos viene perfecto cuando deseamos validar la petición del cliente. Por ejemplo, que te parece si imprimimos en consola su dirección IP.

def process_view(self, request, view_func, view_args, view_kwargs):
    from ipware import get_client_ip
    ip, _ = get_client_ip(request)

    print('La dirección IP del cliente es:', ip)

    return None

Método process_template_response.

El método process_template_response será llamado después que la vista haya sido finalizada y su respuesta sea un objeto de tipo TemplateResponse.

A través del método tendremos acceso a la petición (request) y la respuesta de la vista (response).

El método deberá, de forma obligatoria, retornar la respuesta por parte de la vista (el objeto de tipo TemplateResponse) o cualquier objeto que implemente el método render.

def process_template_response(self, request, response):
    """
    El método process_template_response se ejecuta después del llamado a la vista,
    cuando esta retorne un objeto de tipo TemplateResponse''')
    """
    return response

Al método process_template_response ejecutarse después de la vista y antes del renderizado del template, con él seremos capaces de inyectar nuevos valores al contexto, esto gracias al atributo context_data del objeto response.

def process_template_response(self, request, response):
        response.context_data['message'] = 'Mensaje desde el Middleware MySuperMiddleware'
        return response

Método process_exception.

Finalmente el método process_exception. Este método será ejecutando cuando una excepción es lanzada desde la vista.

A través de este método tendremos acceso tanto a la petición (request) como a la excepción misma (exception).

Le método deberá retornar None o un objeto HTTPResponse. Si es retornado None Django continua con el proceso habitual, llamando cualquier otro método process_exception de los siguientes middlewares. Por el contrario, si es retornado un objeto HTTPResponse, Django retornara inmediatamente dicho objeto al cliente.

def process_exception(self, request, exception):
    # El método process_exception es llamado cuando una excepción es lanzada.
    return None

Aquí un pequeño ejemplo, la clase completa.

class MySuperMiddleware():

    def __init__(self, get_response):
        self.get_response = get_response

        print('>>> Middleware registrato exitosamente.')

    def __call__(self, request):

        # >>> Código antes del llamando a la vista.
        response = self.get_response(request)
        # >>> Código después del llamando a la vista.

        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        from ipware import get_client_ip
        ip, _ = get_client_ip(request)

        print('La dirección IP del cliente es:', ip)

        return None

    def process_template_response(self, request, response):
        """
        El método process_template_response se ejecuta después del llamado a la vista,
        Cuando se haya creado una instancia de TemplateResponse
        """
        response.context_data['message'] = 'Mensaje desde el Middleware MySuperMiddleware'
        return response

    def process_exception(self, request, exception):
        # El método process_exception es llamado cuando una excepción sea lanzada.
        return None

Para que el middleware funcione tal y como deseamos, tendremos que modificar tanto nuestra vista como el template a renderizar.

En la vista debemos retornar un objeto de tipo TemplateResponse, esto para que el método process_template_response sea llamado.

from django.template.response import TemplateResponse

def index(request):
    return TemplateResponse(request, 'my_app/index.html', {
        'title': 'Hola desde un live'
    })

En el template podemos hacer uso de la variable message, variable que el método process_template_response agrega al contexto.

<html>
    <title> {{ title }}</title>
    <body>
        <h2>{{ message }}</h2>
    </body>
</html>

Si ejecutamos, obtendremos las siguientes salidas.

>>> Middleware registrato exitosamente.
La dirección IP del cliente es: 127.0.0.1

Si queremos probar el método process_exception será necesario lanzar una excepción. Una división sobre 0 será más que suficiente. 😲

def index(request):
    resultado = 10 / 0

    return TemplateResponse(request, 'my_app/index.html', {
        'title': 'Hola desde un live'
    })
def process_exception(self, request, exception):
    print('>>> Una excepción acaba de ocurrir, se debe notificar al administrador!!!!')
    return None

Resultado

>>> Una excepción acaba de ocurrir, se debe notificar al administrador!!!!
Internal Server Error:

Conclusión

Listo, ahora ya conocemos 2 formas de poder crear e implementar nuestros propios middlewares en Django, ya sea mediante funciones o clases. De forma personal recomiendo utilizar funciones siempre que nuestro middleware realice tareas sencillas, quizás validar datos de entrada o salida. 🐙 Por otro lado recomiendo utilizar clases cuando nuestro middleware realice tareas un poco más complejas, quizás realizar consultas a nuestra base de datos, reportar errores, consumir APIs etc... esto por la mayor flexibilidad que nos ofrecen las clases, pudiendo ejecutar código en momento muy puntales del proceso petición-respuesta. 👻

Más Tips y Ejercicios 🐍

Adquiere una subscripción PyWombat por tan solo $3 USD. al mes.

Conoce los beneficios de ser usuario premium:
Niveles desbloqueados: Ten accesos a todos los niveles de ejercicios. 🔓
Nuevo límite: Incrementa tu límite de ejercicios por semana. 🚀
Contenido único: Recibe semanalmente recursos exclusivos de Python (Videos, Artículos y Capitulos del libro PyWombat, comienza como desarrollador Python. 🐍

Comentarios

Para poder conocer tu opinión es necesario ser un usuario autenticado. Login