Criando seu próprio decorator para uma view do Django

01/10/2020 - 4 min de leitura

DJANGO PYTHON

O Django é um excelente framework para o desenvolvimento web. É a minha stack principal nessa área e não estou pensando em mudar tão cedo.

É muito fácil ocorrer bagunça e repetições no nosso arquivo de views do Django. Isso porque gostamos de delegar mais responsabilidades do que a view é designada para fazer. Mas isso é assunto para outro post.

Nesse post eu vou tratar do assunto de repetições nas views e como podemos criar o nosso próprio decorator para minimizar verificações de permissões no nosso código.

Contexto

Vamos imaginar que temos uma aplicação para uma rede de lojas e filiais. Para simplificar, teremos 2 tipos de usuários: usuário do tipo gerente ou funcionário.

A modelagem do tabela de usuário ficaria algo mais ou menos assim:

Os gerentes são os superusers e os funcionários são os employees.

O que é um decorator

Decorator (decorador) é um padrão de projeto (normalmente visto nas disciplinas de POO, para quem está em uma universidade/faculdade) que tem como objetivo adicionar um tipo de comportamento ou funcionalidade a um objeto em tempo de execução. Em outras palavras, decorator é uma função que é executada antes de executar a "função principal".

O Django possui alguns decorators. Certamente você já deve ter usado o login_required:

from django.contrib.auth.decorators import login_required


@login_required
def only_auth_users(request):
    # somente os usuários que estiverem logado no sistema
    # que poderão acessar essa view

O '@' antes do nome da função decoradora é padrão do python, para indicar que aquilo trata-se de um decorator.

Quando a gente decora a função only_auth_users com a função login_required, primeiro é verificado o decorator e só depois, se tudo tiver passado, é que o Python e o Django vão executar a função only_auth_users.

Jeito tradicional

Certo. E se quiséssemos restringir o acesso a uma view só para usuários gerentes? Como faríamos?

Bem, a princípio faríamos algo assim:

from django.contrib.auth.decorators import login_required
from django.shortcuts import render, HttpResponseRedirect, reverse


@login_required
def only_managers(request):
    if not request.user.is_superuser:
        return HttpResponseRedirect(reverse("core:home"))

    return render(request, "managers/statistics.html")

E tá tudo bem fazer assim! Não está errado!

Mas, e se tivéssemos não uma view para os gerentes, mas duas, três, QUINZE views?

O Django foi desenhado para nos dar condições para aplicar a filosofia do DRY (Don't Repeat Yourself) - não repita você mesmo(a). Com isso, podemos extrair essas verificações para outra camada, afim de criar o nosso próprio decorator.

O Decorator user_passes_test

Criar um decorator na mão, é muito trabalhoso, principalmente para iniciantes. Felizmente, o Django oferece um decorator que trabalha diretamente com o usuário da sessão. Nós podemos usar essa função como um tipo de API.

A função user_passes_test recebe até três parâmetros:

  • Um chamável para uma função verificadora. Essa função deve retornar verdadeiro ou falso. Caso ela retorne falso, ela redireciona o usuário para a URL settings.LOGIN_URL.
  • login_url, se você quiser especiicar outra URL para redirecionamento, caso a verificação retorne falso.
  • redirect_field_name, que por padrão é a variável next, encontrada no login. Esse campo diz respeito pra onde o usuário vai ser redirecionado caso tenha ocorrido tudo conforme.

Normalmente só precisaremos nos preocupar com a função verificadora.

Você pode procurar saber mais do decorator user_passes_test na documentação.

Criando nosso decorator

Beleza! Depois de introuzir o assunto, tá na hora de botar a mão na massa para construir o nosso próprio decorator no Django.

Quando eu faço algum decorator, eu gosto de criar um arquivo chamado decorators.py na mesma pasta onde fica o modelo que eu quero decorar. No nosso caso, eu criaria o arquivo na pasta accounts/decorators.py.

E é mais ou menos asim que o código deve ficar:

# accounts/decorators.py
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import user_passes_test


def manager_required(function=None, login_url=settings.LOGIN_URL, redirect_field_name=REDIRECT_FIELD_NAME):
    '''
    Decorator que verifica se o usuário da sessão é ou não um gerente.
    '''
    actual_decorator = user_passes_test(
        lambda u: u.is_active and u.is_superuser,
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    return actual_decorator

Fiz uma função que recebe os três parâmetros que eu mencionei e estou executando o decorator user_passes_test por debaixo dos panos e retorno ele.

** É importante lembrar que o primeiro parâmetro do user_passes_test é uma função verificadora que retorna verdadeiro ou falso. E essa função SEMPRE recebe como parâmetro, o usuário da sessão.

Para quem não sabe, lambda é uma maneira diferente de escrever funções em 1 linha. A função lambda acima é a mesma coisa de:

def checker(user):
    return user.is_active and user.is_superuser

Daí, era só substituir o lambda por a referência do checker. Algo assim:

actual_decorator = user_passes_test(
    checker,
    login_url=login_url,
    redirect_field_name=redirect_field_name
)

E na view

Na view ficaria bem mais simples. Bastaria eu decorar a função que eu quero restrição, com o @manager_required. Aqui está um exemplo com o nosso caso de uso:

from django.contrib.auth.decorators import login_required
from django.shortcuts import render, HttpResponseRedirect, reverse

from accounts.decorators import manager_required


@login_required
@manager_required
def only_managers(request):
    return render(request, "managers/statistics.html")

Conclusão

O Django nos oferece muitas camadas para a gente organizar nosso código e parar de fazer repetições. Se um dia a estratégia de verificação do gerente mudar, eu só preciso alterar em um lugar e automaticamente será refletido em todas as views. Bem melhor que ter que trocar em quinze lugares.

Foto da capa by Anas Alshanti on Unsplash

Compartilhe

Twitter