Mind Bending

Por padrão, o Django utiliza como identificador único um "nome de usuário" (username). Apesar de ser adotada por muitos sites, não é uma prática que me agrada muito, então, uma das primeiras coisas que eu faço quando inicio um projeto Django é customizar o usuário base do projeto e configurar para que o email do usuário seja utilizado para identificá-lo durante o login.

Django

Se existem vantagens/desvantagens reais? Além de menos confusão na hora de login e menos campos durante o registro, eu desconheço qualquer outra vantagem. Ou seja, saibam que é apenas uma mania minha, não uma boa prática ou recomendação.

Novo Projeto e Configurações Básicas

Vamos começar criando um novo projeto:

$ mkvirtualenv -p /usr/bin/python3 MeuProjeto
$ pip install django==1.9
$ django-admin startproject MeuProjeto
$ mkdir MeuProjeto/apps MeuProjeto/templates/users
$ cd MeuProjeto/apps
$ django-admin startapp users
$ cd users
$ mv apps.py config.py

Em seguida altere a configuração dos templates para que ele busque-os em MeuProjeto/templates:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates/'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

O Modelo e a Migração

O primeiro passo é criar um model para refletir os dados que você deseja que o usuário possua. Segue abaixo o meu model (MeuProjeto/apps/users/models.py):

# Arquivo: /apps/users/models.py
from django.db import models
from django.utils.translation import ugettext as _
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth.models import PermissionsMixin


class EmailUserManager(BaseUserManager):
    def create_user(self, *args, **kwargs):
        email = kwargs["email"]
        email = self.normalize_email(email)
        password = kwargs["password"]
        kwargs.pop("password")

        if not email:
            raise ValueError(_('Users must have an email address'))

        user = self.model(**kwargs)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, *args, **kwargs):
        user = self.create_user(**kwargs)
        user.is_superuser = True
        user.save(using=self._db)
        return user


class MyUser(PermissionsMixin, AbstractBaseUser):
    email = models.EmailField(
        verbose_name=_('Email address'),
        unique=True,
    )
    first_name = models.CharField(
        verbose_name=_('Nome'),
        max_length=50,
        blank=False,
        help_text=_('Inform your name'),
    )
    last_name = models.CharField(
        verbose_name=_('Sobrenome'),
        max_length=50,
        blank=False,
        help_text=_('Inform your last name'),
    )
    USERNAME_FIELD = 'email'
    objects = EmailUserManager()

Como podem ver existem duas classes. A primeira, EmailUserManager, é uma classe auxiliar que é irá mimetizar a API do Manager do modelo de usuário original do Django. Isso é necessário pois precisaremos disponibilizar para o Django os métodos MyUser.obejcts.create_user e MyUser.obejcts.create_superuser.

Note que herdamos de AbstractBaseUser (que provê o esqueleto básico de um usuário) e de PermissionsMixin (que provê funcionalidade de permissionamento). Sem a primeira classe MyUser não poderia ser utilizado como um modelo de um usuário. Já sem a Mixin de permissionamento, a aplicação até funcionaria, mas faltariam funcionalidades de controle de superusuário (is_superuser), grupos (groups) e permissões (user_permissions).

Em seguida precisamos ativar o app no arquivo MeuProjeto/settings.py adicionando a linha 'apps.users' à chave de configuração INSTALLED_APPS. Ao final ela deve conter as seguintes linhas:

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

Agora podemos executar as migrações desse app:

$ ./manage.py makemigrations
$ ./manage.py migrate

Por último vamos adicionar em MeuProjeto/MeuProjeto/settings.py a indicação da classe que servirá como modelo para os usuários. Para isso adicione a seguinte linha: AUTH_USER_MODEL = "users.MyUser".

Forms, Views e mais Views

Vamos começar pelo mais fácil, um form para registro de usuário:

# Arquivo: apps/users/forms.py
from django.contrib.auth.forms import UserCreationForm

from .models import MyUser


class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = MyUser
        fields = ['first_name', 'email']

Como podem foram poucas linhas de código graças á possibilidade de herdar do form UserCreationForm.

Para apresentar esse form customizado vamos criar algumas views.

# Arquivo: /apps/users/views.py
from django.shortcuts import render
from django.views.generic import CreateView
from django.http import HttpResponseRedirect
from django.contrib.auth.views import login
from django.contrib.auth.views import logout
from django.core.urlresolvers import reverse_lazy, reverse

from .forms import CustomUserCreationForm


def home(request):
    return render(request, 'users/home.html')


def login_view(request, *args, **kwargs):
    if request.user.is_authenticated():
        return HttpResponseRedirect(reverse('users:home'))

    kwargs['extra_context'] = {'next': reverse('users:home')}
    kwargs['template_name'] = 'users/login.html'
    return login(request, *args, **kwargs)


def logout_view(request, *args, **kwargs):
    kwargs['next_page'] = reverse('users:home')
    return logout(request, *args, **kwargs)


class RegistrationView(CreateView):
    form_class = CustomUserCreationForm
    success_url = reverse_lazy('users:login')
    template_name = "users/register.html"

Vamos com calma aqui. Primeiramente temos uma view home que apenas renderiza um template que mostraremos mais abaixo.

Antes de falarmos da view login_view, vamos descrever qual é o comportamento esperado de uma tela de login:

  1. Apresentar a tela de login caso o usuário não esteja autenticado;
  2. Caso um usuário já autenticado tente acessar a tela de login, este deve ser redirecionado e a tela de login não deve ser apresentada.
  3. Após o login, redirecionar o usuário para uma tela específica;

Para conseguir o primeiro e o segundo item dessa lista de comportamento adicionamos um if que verifica se o usuário está logado e, em caso positivo, redireciona-o para uma "home" (view home). Já o para o segundo item precisamos informar a próxima tela após a autenticação, para isso customizamos alguns parâmetros através da sobrescrita do dicionário kwargs.

A view de logout (logout_view) também foi ligeiramente customizada, adicionando apenas o argumento next_url para que, assim que acessada esta página realiza o processo de logout e em seguida redireciona o usuário.

Por último, temos a view que vai renderizar o form criado anteriormente. Ela é uma CBV (Class Based View) bem simples que herda de CreateView e customiza o form_class para o form que criamos, a succcess_url e o template_name.

Agora vamos apresentar estes templates. Começando pelo mais simples…

<!-- Arquivo: templates/users/home.html -->
<p>Seja bem vindo {% if user.is_authenticated %}{{ user.first_name }}{% else %}usuário anônimo{% endif %}</p>

{% if user.is_authenticated %}
<p><a href="{% url 'users:logout' %}">Logout</a>.</p>
{% else %}
<p><a href="{% url 'users:register' %}">Registre-se</a>.</p>
<p><a href="{% url 'users:login' %}">Login</a>.</p>
{% endif %}

Nada de mais mesmo, apenas apresenta o nome do usuário (ou a string usuário anônimo) e alguns links, dependendo se o usuário está logado ou não.

Agora o template para registrar o usuário:

<!-- Arquivo: templates/users/register.html -->
<h1>Registrarion</h1>

<form role="form" class="form" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-warning">Register</button>
</form>

Por último o formulário para login.

<!-- Arquivo: templates/users/login.html -->
<h1>Login</h1>

<form role="form" class="form" method="POST" action="{{ request.path }}{% if next %}?next={{ next }}{% endif %}">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-warning"><i class="fa fa-plus" aria-hidden="true"></i> Login</button>
</form>

Este tem um pequeno detalhe, que é inserção do atributo action apontando para a URL atual e tendo como argumento uma variável next, que foi informada na view login_view. Este argumento irá garantir que após o login o usuário será redirecionado para esta URL.

Apontamento de URLs

Para finalizar vamos realizar o apontamento das URLs:

# Arquivo: apps/users/urls.py
from django.conf.urls import url

from . import views

app_name = 'users'
urlpatterns = [
    url(r'^$', views.home, name="home"),
    url(r'^login/$', views.login_view, name="login"),
    url(r'^logout/$', views.logout_view, name="logout"),
    url(r'^register/$', views.RegistrationView.as_view(), name="register"),
]

Antes que alguém comente dizendo que eu poderia ter feito boa parte das customizações das views de login e logout, assim como a view home diretamente no arquivo de URLs, eu já adianto que eu não gosto dessa prática. Já adotei esse tipo de prática e percebi que é um maintenance hell, aprendi que as urls devem possuir apenas apontamento de URLs, nada de variáveis como nome de templates e etc.

Mais simples que este arquivo de URLs, somente mesmo o mapeamento de URLs do principais do projeto.

# Arquivo: MeuProjeto/urls.py
from django.conf.urls import include, url
from django.contrib import admin

import apps.users.urls

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^user/', include('apps.users.urls', namespace="users")),
]

Fechamento

Com poucas linhas de código (aproveitando o máximo que o framework disponibiliza) já temos a estrutura básica de registro, login e logout de usuários. Claro que ainda não é um primor, e muito pode ser melhorado, principalmente na parte de HTML/CSS, mas este já é um esqueleto básico de 75% das aplicações que você pode vir a desenvolver.

Pontos pendentes:

  • Edição de dados de perfil do usuário;
  • Troca de senha;
  • Reset de senha (o famoso "esqueci minha senha");
Magnun

Magnun

Engenheiro de telecomunicações por formação, mas trabalha com suporte à infraestrutura GNU/Linux, e nas horas vagas é Programador OpenSource (Python e C) desenhista e escritor do Mind Bending Blog.


Comments

comments powered by Disqus