PyWombat

← Volver a artículos

Decoradores en Django

November 27, 2020

4464 views

4 min de lectura

Como sabemos, y si no, ahora ya lo sabes, Djangos nos permite crear vistas a partir de clases. Estas clases ya se encuentran definidas por el framework mismo, y a partir de ellas podremos crear, leer, listar, actualizar, y por supuesto eliminar objetos. A estas vistas las conoceremos como: Vistas basadas en clase.

Utilizando estas clase no solo agilizaremos nuestro proceso de desarrollo, si no además, seremos capaces de gestionar, de una mejor manera, nuestro proyecto. Teniendo un código mucho más legible, estructurado, y en algunos casos, re utilizable. 😊

Algunas de las clases más populares que posee Django son las siguiente:

  • ListView
  • CreateView
  • DetailView
  • UpdateView
  • DeleteView

Al ser clases diseñadas con el objetivo de ser utilizadas en las vistas, todas tienen algo en común, todas heredan de una misma clase padre, la clase view. Te comparto el link a la clase per se por si gustas echarle un vistazo.

Esta clase se encarga de manejar la petición que llega al servidor, validando argumentos, parámetros, métodos etc... Dentro de la clase nos encontraremos con un método de mucha utilidad, me refiero al método dispath. Este es el método intermediario entre la petición del cliente y la respuesta del servidor. Vaya, es la puerta de entrada y salida de la petición.

Por la naturaleza misma del método, es posible añadir funcionalidades extras que modifiquen su comportamiento. Algo muy común es interceptar la petición y realizar validaciones sobre ella, pudiendo así retornar una respuesta completamente diferente a la prevista.

Veamos un ejemplo.

class ArticleDetailView(DetailView):
    template_name = 'articles/subscription.html'
    model = Article

    def dispatch(self, request, *args, **kwargs):

        if not self.get_object().public:
                return redirect('articles:list')

        return super(ArticleDetailView, self).dispatch(request, *args, **kwargs)

En el ejemplo sobre escribimos el método dispatch, validando sobre el articulo a visualizar. Si el articulo no es publico entonces se retornará un redirect por parte del servidor, en caso contrario el flujo continuará de forma normal.

Nota: Cómo podemos observar, para DetailView, le método get_object se ejecuta antes que el método dispatch.

Esto esta super cool, ya que inclusive el método nos permite añadir valores extras a la petición 🤯 Recuerda, este método es prácticamente el primero en llamarse cuando la vista es construida.

Pero bueno, esto que tiene que ver con decoradores en Django. Pues bien, con esta breve introducción me gustaría examinaramos el siguiente ejemplo.

class ArticleCreateView(CreateView):
    model = Article
    ...

    def dispatch(self, request, *args, **kwargs):
        if not request.user.admin():
            return redirect('index')
        return super(ArticleCreateView, self).dispatch(request, *args, **kwargs)


class ArticleDetailView(LoginRequiredMixin, DetailView):
    model = Article
    ...

    def dispatch(self, request, *args, **kwargs):
        if not request.user.admin():
            return redirect('index')
        return super(ArticleDetailView, self).dispatch(request, *args, **kwargs)


class ArticleUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
    model = Article
    ....

    def dispatch(self, request, *args, **kwargs):
        if not request.user.admin():
            return redirect('index')
        return super(ArticleUpdateView, self).dispatch(request, *args, **kwargs)

Para este ejemplo tenemos 3 clases que sobre escriben el método dispatch y validan si la petición la realizo un usuario con permisos de administrador. Si la condición no se cumple, el usuarios es re dirigido para index.

Hasta aquí, con todo lo que acabamos de aprender, nada nuevo, todo funciona bien. Sin embargo, si somos un poco más observadores podremos notar que tenemos líneas de código duplicadas, algo que sin duda no es nada bueno.

Podemos llegar a pensar que solo es una simple condición, que no pasa nada, pero ¿y si no solo validamos en 3 clases, si no ahora en 10, 20, 50 o inclusive más? o ¿qué si la condición se vuelve cada vez más compleja? tendríamos que realizar cambios en múltiples lugares a la vez, algo que a la larga nos traería muchos problemas. 😰

Es aquí donde tenemos que implementar un refactor. Ahora, ¿Cómo podemos solucionar el problema? ¿Acaso sobre escribiendo la clase View? 😳 Aun que es una idea factible, creo que podemos dejarla como última opción.

¿Alguna forma de añadir funcionalidades a un método? Claro, decoradores. 🤓

Afortunadamente Django nos permite implementar decoradores sobre los método de una clases, esto utilizando la función method_decorator. . Haciendo uso de esta función no simplemente nos enfocaremos en realizar nuesras validaciones, y no es comprender y sobre escribir la clase view.

Veamos.

Lo primero que hay que hacer será definir nuestro decorador. Procura que este sea los más abstracto posible, que funciones para cualquier método. Por ejemplo:

def admin_required(function):
    def wrap(request, *args, **kwargs):

        if not request.user.admin():
            return redirect('index')

        return function(request, *args, **kwargs)

    return wrap

Debido a que, lo que deseamos decorar es el método dispatch nuestra función anidada debe recibir como primer argumento el objeto request, tal y como lo hace el método.

Una vez tengamos nuestro decorador será el turno de implementarlo. La función method_decorator recibe cómo argumento el decorador a utilizar.

from django.utils.decorators import method_decorator

class AnswerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
    model = Answer
    ....

    @method_decorator(admin_required)
    def dispatch(self, request, *args, **kwargs):
        return super(AnswerUpdateView, self).dispatch(request, *args, **kwargs)

Al nosotros estaremos encapsulando en un solo lugar nuestra lógica de validación, y el decorador podrá ser utilizado la n cantidad de veces en la n cantidad de métodos que deseemos. Claro, lo ideal sería siempre usarlo con en el método dispatch. 🦆

Al ser decoradores por supuesto que estos se pueden encadenar, logrando así una secuencia de condiciones.

@method_decorator(admin_required)
@method_decorator(user_authenticated)
@method_decorator(article_pubished)
def dispatch(self, request, *args, **kwargs):
    return super(ArticleCreateView, self).dispatch(request, *args, **kwargs)

Les recomiendo siempre crear decoradores lo más sencillos posibles, que solo validen una o dos cosas a la vez. Nunca colocar lógica de negocios en ellos, y en dado caso se necesite añadir algún parámetro extra a la petición, intentar siempre hacerlo en decoradores independientes a las validaciones. 🍻