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.