Executando verificação de segurança...
1

Como criar um Mixin no Django

Como criar um Mixin no Django

Introdução

O Django é um framework open source para criação de aplicações web em Python.
Na minha opinião é o melhor framework atualmente disponível para esse fim.

Durante o desenvolvimento de uma aplicação para a empresa que trabalho, surgiu a necessidade de limitar o acesso a algumas urls de acordo com algumas regras definidas pela empresa.
Esse processo começa a ficar repetitivo caso o sistema possua muitas 'views'.

Como eu uso apenas views baseadas em classes no sistema, resolvi criar um 'mixin', que é uma classe que vai implementar a lógica de restrição do sistema. Então basta cada 'view' herdar esse mixin para ter a funcionalidade desejada.

Pra fins de exemplo, vamos definir que o acesso ao nosso sistema só poderá ser feito após o usuário acessar os termos de uso.
Então vamos criar uma 'model' com os termos de uso, que seja vinculada ao usuário e o mixin vai testar se o usuário aceitou os termos.

No final bastará que a(s) view(s) herdem o mixing para que tenham essa validação.

Criação da aplicação básica

Vamos começar criando o venv e o projeto Django.

$ mkdir projeto_mixin
$ cd projeto_mixin
$ python -m venv venv
$ source venv/bin/activate

Crie um arquivo chamado 'requirements.txt', que vai listar todas as dependências do seu projeto. No nosso caso será apenas o django.

(venv) $ echo django > requirements.txt

Agora vamos instalar as dependências do projeto.

(venv) $ pip install -r requirements.txt

Com as dependências instaladas, podemos criar o projeto.

(venv) $ django-admin startproject stm .

No caso acima estamos pedindo para o django criar um projeto chamado "stm" na pasta atual ".".

Agora devemos criar uma aplicação em nosso projeto.

(venv) $ python manage.py startapp web

No caso acima criamos uma aplicação chamada "web".

Agora aplique as migrações necessárias para o funcionamento inicial da aplicação.

(venv) $ python manage.py migrate

Agora precisamos adicionar a aplicação recém-criada à lista de aplicações do projeto.
Para isso abra o arquivo stm/settings.py e procure pelo array INSTALLED_APPS.
Adicione a aplicação "web" ao array.
Ficará assim:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'web'
]

Agora vamos criar uma página html apenas para testar a aplicação.
Para isso crie uma pasta chamada 'templates' dentro da pasta 'web'.

(venv) $ mkdir web/templates

Dentro crie o arquivo 'web/templates/index.html' com o conteúdo abaixo:
(Coloquei a bootstrap pra ficar mais 'bonitinho' do que um html puro)

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>STM</title>
  </head>
  <body>
    <h1>Bem Vindo ao STM</h1>

    <p>Se você está vendo esta página, significa que aceitou os termos de uso.</p>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>

  </body>
</html>

Agora vamos criar a 'view' que vai renderizar este template.
Vamos usar uma View genérica do django, que já vai impletementar os métodos pra manipulas as requisições e renderizar o template.
Obs. O Django possui views genéricas pra várias operações comuns, como listar conteúdo de bd, manipular forms, etc. Vale a pena dar uma olhada na documentação.
Para isso edite o arquivo web/views.py e adicione o conteúdo abaixo:

from django.views.generic import TemplateView, FormView

class IndexView(TemplateView):
    template_name = 'index.html'

Isso é tudo que precisamos pra renderizar um template.

Agora devemos criar a url que estar vinculada a essa view.
Para isso, crie o arquivo web/urls.py com o conteúdo abaixo:

from django.urls import path
from . import views

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
]

E finalmente edite o arquivo stm/urls.py para usar o arquivo de urls da aplicação:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('web.urls')),
]

Com isso temos uma aplicação básica, mas ainda sem a funcionalidade de restrição de conteúdo baseada no mixin.

Para testar a aplicação use o comando abaixo para executar a aplicação.

(venv) $ python manage.py runserver

Agora basta abrir um navegador e acessar o endereço http://localhost:8000

Você deverá ver uma página com o conteúdo de template index.html.

Configuração da aplicação para solicitar login e senha do usuário

Para que possamos solicitar que o usuário aceite um termo de uso, primeiramente precisaremos criar um sistema de autenticação de usuários.

O Django já vem com um sistema pronto para este fim, bastando fazermos algumas configurações.

Inicialmente vamos colocar um mixin padrão do django que restringe o acesso a usuários logados.
Edite o arquivo web/views.py e deixe-o como abaixo:

from django.views.generic import TemplateView, FormView
from django.contrib.auth.mixins import LoginRequiredMixin

class IndexView(LoginRequiredMixin, TemplateView):
    template_name = 'index.html'

Para utilizar o mixin LoginRequiredMixin, será necessário configurar mais algumas coisas.

Primeiramente, devemos incluir o arquivo com as urls de autentição.
Para isso altere o array urlpatterns do arquivo web/urls.py para ficar como abaixo:

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('accounts/', include('django.contrib.auth.urls')),
]

Além disso será necessário criar mais um template com o form de login do usuário.
Crie uma pasta chamada 'accounts' dentro da pasta 'web/templates':

(venv) $ mkdir web/templates/registration

Agora crie o arquivo web/templates/registration/login.html com o conteúdo abaixo:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico">

    <title>Fazer login</title>

    

    <!-- Bootstrap core CSS -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <style>
    .form-signin {
        width: 100%;
        max-width: 330px;
        padding: 15px;
        margin: 0 auto;
    }
    </style>
    
  </head>

  <body class="text-center">
  <div class="container mb-3">
    <form class="form-signin" method="post">
    {% csrf_token %}
      <img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
      <h1 class="h3 mb-3 font-weight-normal">Entrar</h1>
      <label for="input-username" class="sr-only">Nome de usuário</label>
      <input type="text" name="username" id="input-username" class="form-control" placeholder="Digite seu nome de usuário" required autofocus>
      <label for="input-password" class="sr-only">Senha</label>
      <input type="password" name="password" id="input-password" class="form-control" placeholder="Digite sua senha" required>
      <button class="btn btn-lg btn-primary btn-block" type="submit">Entrar</button>
    </form>
    </div>
  </body>
</html>

Finalmente devemos criar um usuário para testa a funcionalidade de login:

(venv) $ python manage.py createsuperuser

Preencha os dados solicitados e tente acessar a aplicação novamente em http://localhost:8000

Agora forneça os dados de login informados anteriormente e você conseguirá acessar a aplicação.

Com isso terminos o sistema de autenticação e agora podemos criar nosso mixin de termos de uso.

Criação do mixin de termos de uso

Finalmente vamos criar nosso Mixin que vai restringir o acesso aos usuários apenas para aqueles que aceitarem os termos de uso.

Primeiramente edite o arquivo web/models.py e adicione o conteúdo abaixo:

from django.db import models
from django.conf import settings

class Termo(models.Model):
    aceito = models.BooleanField(default=False)
    data_hora = models.DateTimeField()
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        null=True
    )

Agora será necessário criar as migrações para alterar o banco de dados e inserir a nova model.

(venv) $ python manage.py makemigrations

E por fim aplicamos as migrações criadas.

(venv) $ python manage.py migrate

Agora crie o arquivo web/mixins.py vamos com o conteudo abaixo:

from django.core.exceptions import ImproperlyConfigured
from django.shortcuts import redirect

class TermosDeUsoMixin:
    redirect_url = None
    
    def get_redirect_url(self):
        if not self.redirect_url:
            raise ImproperlyConfigured(
                '{0} não possui o atributo redirect_url. Defina {0}.redirect_url'.format(self.__class__.__name__)
            )
        return str(self.redirect_url)
   
    def test_func(self):
        valor = False
        try:
            valor = self.request.user.termo.aceito
        except:
            pass
        return valor
    
    def get_test_func(self):
        """
        Override this method to use a different test_func method.
        """
        return self.test_func

    def dispatch(self, request, *args, **kwargs):
        test_result = self.get_test_func()()
        if not test_result:
            return redirect(self.get_redirect_url())
        return super().dispatch(request, *args, **kwargs)

Agora devemos criar um formulário, uma view e um template para os termos de uso.

Primeiramente criamos o form.
Crie o arquivo web/forms.py com o conteúdo abaixo:

from django import forms
from .models import Termo

class TermoForm(forms.ModelForm):
    aceito = forms.BooleanField(
        required=True,
        label='Li e aceito os termos de uso ',
    )

    class Meta:
        model = Termo
        fields = ['aceito']

Agora precisamos criar a view que vai renderizar o formulário de termos de uso.
Para isso edite o arquivo web/views e crie a view abaixo:

class TermoView(LoginRequiredMixin, FormView):
    form_class = TermoForm
    template_name = 'termo.html'
    success_url = reverse_lazy('index')

    def form_valid(self, form: TermoForm):

        try:
            termo = self.request.user.termo
        except:
            termo = Termo(
                user=self.request.user,
                data_hora = timezone.now(),
                aceito = self.request.POST.get('aceito')
            )

        form = TermoForm(self.request.POST, self.request.FILES, instance=termo)
        form.instance.data_hora = timezone.now()
        if form.is_valid():
            form.save()
        return super().form_valid(form)

Agora devemos criar o arquivo web/templates/termo.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico">

    <title>Termos de Uso</title>

    

    <!-- Bootstrap core CSS -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <style>
    .form-signin {
        width: 100%;
        max-width: 330px;
        padding: 15px;
        margin: 0 auto;
    }
    </style>
    
  </head>

  <body class="text-center">
  <div class="container mb-3">
    <form class="form-signin" method="post">
    {% csrf_token %}
    <p>
        Prezado {{request.user}} para acessar o sistema, você deve aceitar os termos de uso.
    </p>
    <ul>
        <li>Sou um cara legal</li>
        <li>Não vou fazer besteiras</li>
        <li>Aceito as regras</li>
    </ul>
    {{ form }}
    <button class="btn btn-lg btn-primary btn-block" type="submit">Aceitar os termos</button>
    </form>
    </div>
  </body>
</html>

Tudo pronto, agora basta alterar a view principal para exigir o aceite dos termos de uso.
Para isso edite o arquivo web/views.py e na classe IndexView, deixe como abaixo:

class IndexView(LoginRequiredMixin, TermosDeUsoMixin, TemplateView):
    template_name = 'index.html'
    redirect_url = 'termo'

Edit:
Lembre-se de adicionar os imports abaixo ao início do arquivo web/views.py

from django.utils import timezone
from django.urls import reverse_lazy
from web.forms import TermoForm
from web.mixins import TermosDeUsoMixin
from web.models import Termo

Finalmente, adicione a url do formulário de termos de uso ao array urlpatterns do arquivo web/urls.py:

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('accounts/', include('django.contrib.auth.urls')),
    path('termo/', views.TermoView.as_view(), name='termo'),
]

Agora, acesse novamente o sistema, e será solicitado que você aceite os termos de uso antes de ver a página.
Em qualquer página que você precisar colocar a verificação dos termos de uso, basta que a view herde de TermosDeUsoMixin.

Conclusão

Neste artigo mostrei como criar um Mixin no Django dando como exemplo um mixin de termos de uso.

Também precisei desenvolver uma forma de restringir o acesso da aplicação a alguns ips e ranges específicos.
Então desenvolvi um middleware e um mixin para restringir a aplicação toda, ou partes dela a determinados endereços ip.
Caso queiram, comentem que posso fazer um artigo mostrando esses recursos.

Espere que seja útil.
Até o próximo.

Carregando publicação patrocinada...