Custom User Model no Django
Eu me enrolei, e sei que muita gente também se enrola quando se trata de User Authentication do framework. Mas agora seus problemas acabaram! ;)
Qual o problema com o User Model default do Django?
A princípio, nenhum. Se você está começando no Django, só brincando, fazendo testes e experimentando não há nada de errado em usá-lo. O problema é quando você precisa desenvolver um grande projeto, sério, cisudo e pomposo e vai colocá-lo em produção.
O User default atende muito bem quase todas expectativas de um projeto do mundo real. Mas ele acaba atrapalhando, e até desencorajando os novos djanglers na hora da customização. Isso porque esse model não permite muita customização, e a recomendação na documentação do próprio Django é que você extenda esse model ou crie o seu próprio. É nessa hora que a coisa começa a ficar complicada…
Extendendo o User Model
A maneira mais simples de adicionar alguma customização pra esse model, é criando um outro model com um relacionamento OneToOne pra ele. Para exemplificar, imagine que você planejou que seus usuários tenham perfis, e seus perfis têm dados como: idade e gênero. Extendendo o User default com um relacionamento OneToOne, seu código ficaria mais ou menos assim:
from django.contrib.auth.models import User
from django.db import modelsclass Profile(models.Model):
user = models.OneToOneField(User, related_name='profile')
age = models.IntegerField()
gender = models.BooleanField()
Ps: Todos os blocos de código desse post vão estar meio sem identação. Eu não consegui fazer a identação direitinho neles… Se alguém souber como, ajuda bastante! ;)
Dessa forma você extendeu o User default do Django e criou uma nova entidade para comportar os campos que ele não oferece nativamente.
Substituindo o User Model
Uma vez que você não precisa alterar o comportamento nativo do User default, a solução anterior se aplica perfeitamente a você. Mas e se, por exemplo, o usuário do nosso site precisa se logar com o email ao invés de username? Ou se a senha do usuário necessita ter um limite de 8 caracteres? Aqui está a triste verdade: não existe uma maneira fácil e/ou painless de alterar esses comportamentos. Você precisa meter a mão na massa e ralar um pouquinho…
Mas como eu já ralei bastante, vou tentar diminuir seu sofrimento, ok? Vamos lá, como eu disse, você precisa que o seu usuário se logue no nosso site com o email, e não com o seu username. Além disso, você quer uma informação importante sobre o usuário, quer saber se ele confirmou seu email, ou seja, se ele realmente existe e é confiável. Para isso nós vamos substituir o User Model default do Django e usar nosso próprio model.
É muito importante ressaltar aqui que você inicie um novo projeto para esse método. Se você tentar substituir o User Model com uma aplicação viva ou um projeto que já tenha qualquer tipo de estrutura, pode dar merda, e você pode acabar num beco sem saída, ok?
Pra começar, vamos criar um novo app chamado account:
python manage.py startapp account
Agora a brincadeira começa. Vamos deixar claro que a repetição vai existir aonde você não quer alterar, ou seja, nós vamos repetir aqui o comportamento de alguns campos, portanto, se você só precisa alterar um comportamento, tenha certeza de ser extremamente necessário, ok? Vamos começar a escrever nosso próprio User model:
# Arquivo models.py do app accountfrom django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.core import validators
from django.utils.translation import ugettext_lazy as _
import re
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(_('username'), max_length=15, unique=True, help_text=_('Required. 15 characters or fewer. Letters, numbers and @/./+/-/_ characters'), validators=[ validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), _('invalid'))]) first_name = models.CharField(_('first name'), max_length=30) last_name = models.CharField(_('last name'), max_length=30) email = models.EmailField(_('email address'), max_length=255, unique=True) is_staff = models.BooleanField(_('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.')) is_active = models.BooleanField(_('active'), default=True, help_text=_('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
Até aí, estamos bem perto do model default, agora vamos adicionar um campo a mais nesse model:
is_trusty = models.BooleanField(_('trusty'), default=False, help_text=_('Designates whether this user has confirmed his account.'))
Esse bloco de código acrescenta o campo is_trusty, que nos dirá se o usuário confirmou o email, ou seja, se é um usuário confiável. A estrutura do nosso model está basicamente pronta, vamos agora trocar a forma de login do usuário, que será via email. Vamos também adicionar os campos obrigatórios e uns métodos de apoio. Ainda na nossa classe User:
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()def get_short_name(self):
return self.first_namedef email_user(self, subject, message, from_email=None):
send_mail(subject, message, from_email, [self.email])
Agora sim nós temos nosso model (quase) completo. Ainda vai ficar faltando uma linhazinha, mas a gente já fala dela. Vamos analisar o código acima:
- USERNAME_FIELD: essa variável recebe o campo do model que será utilizado pelo Django para autenticação. É aqui que ao invés de username do model default, eu coloco email para fazer o login pelo email do usuário.
- REQUIRED_FIELDS: os campos que são obrigatórios desse model. Nenhuma dúvida quanto isso, certo?
- class Meta, verbose_name e verbose_plural: aqui a gente define a taxonomia que nosso model terá para o Django no singular e no plural.
- get_full_name, get_short_name e email_user: são métodos de apoio que facilitam nossa vida. Se eu não me engano, elas são obrigatórias para o sistema do Django.
Agora nós precisamos criar um manager pro nosso model. O manager é a classe que disponibiliza funções para criação e administração do usuário e superuser. Vamos escrever nosso manager, no mesmo arquivo, porém em outra classe e acima da nossa classe User (já explico o motivo):
class UserManager(BaseUserManager): def _create_user(self, username, email, password, is_staff, is_superuser, **extra_fields):
now = timezone.now()
if not username:
raise ValueError(_(‘The given username must be set’))
email = self.normalize_email(email)
user = self.model(username=username, email=email, is_staff=is_staff, is_active=True, is_superuser=is_superuser, last_login=now, date_joined=now, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user def create_user(self, username, email=None, password=None, **extra_fields):
return self._create_user(username, email, password, False, False, **extra_fields) def create_superuser(self, username, email, password, **extra_fields):
user=self._create_user(username, email, password, True, True, **extra_fields)
user.is_active=True
user.save(using=self._db)
return user
Depois de criarmos o User Manager, voltamos ao User para inserir o Manager. E é aqui que entendemos o motivo de inserir a classe UserManager acima da User. A classe User faz referência a UserManager, ou seja, se colocarmos a UserManager depois vamos causar um erro, pois a aplicação não vai econtrar UserManager quando for parsear essa linha.
...
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']objects = UserManager()class Meta:
...
O arquivo final, deve se parecer com o código abaixo, devidamente identado, claro…
import refrom django.db import models
from django.core import validators
from django.utils import timezone
from django.core.mail import send_mail
from django.utils.http import urlquote
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.conf import settingsclass UserManager(BaseUserManager):def _create_user(self, username, email, password, is_staff, is_superuser, **extra_fields):
now = timezone.now()
if not username:
raise ValueError(_(‘The given username must be set’))
email = self.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=is_staff, is_active=True,
is_superuser=is_superuser, last_login=now,
date_joined=now, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return userdef create_user(self, username, email=None, password=None, **extra_fields):
return self._create_user(username, email, password, False, False,
**extra_fields)def create_superuser(self, username, email, password, **extra_fields):
user=self._create_user(username, email, password, True, True,
**extra_fields)
user.is_active=True
user.save(using=self._db)
return userclass User(AbstractBaseUser, PermissionsMixin):username = models.CharField(_(‘username’), max_length=15, unique=True,
help_text=_(‘Required. 15 characters or fewer. Letters, \
numbers and @/./+/-/_ characters’),
validators=[
validators.RegexValidator(
re.compile(‘^[\w.@+-]+$’),
_(‘Enter a valid username.’),
_(‘invalid’))])
first_name = models.CharField(_(‘first name’), max_length=30)
last_name = models.CharField(_(‘last name’), max_length=30)
email = models.EmailField(_(‘email address’), max_length=255, unique=True)
is_staff = models.BooleanField(_(‘staff status’), default=False,
help_text=_(‘Designates whether the user can log into this admin site.’))
is_active = models.BooleanField(_(‘active’), default=True,
help_text=_(‘Designates whether this user should be treated as active. \
Unselect this instead of deleting accounts.’))
date_joined = models.DateTimeField(_(‘date joined’), default=timezone.now)
is_trusty = models.BooleanField(_(‘trusty’), default=False,
help_text=_(‘Designates whether this user has confirmed his account.’))USERNAME_FIELD = ‘email’
REQUIRED_FIELDS = [‘username’, ‘first_name’, ‘last_name’]objects = UserManager()class Meta:
verbose_name = _(‘user’)
verbose_name_plural = _(‘users’)def get_full_name(self):
full_name = ‘%s %s’ % (self.first_name, self.last_name)
return full_name.strip()def get_short_name(self):
return self.first_namedef email_user(self, subject, message, from_email=None):
send_mail(subject, message, from_email, [self.email])
Feito isso, agora precisamos dizer ao Django que queremos usar o nosso model ao invés do default dele. Pra isso, vamos adicionar uma linha ao arquivo settings.py:
...
ALLOWED_HOSTS = []# Eu coloquei aqui entre o ALLOWED_HOSTS e o LOGIN_URL, mas você
# pode colocar aonde quiser, desde que faça algum sentido.
AUTH_USER_MODEL = ‘account.User’LOGIN_URL = ‘/’
...
Não se esqueça de adicionar o app “account” no INSTALLED_APPS!
Agora basta rodar um makemigrations e migrate da account, criar um superuser e voilà!
python manage.py makemigrations account
python manage.py migrate account
# Eu gosto de rodar um migrate puro por segurança...
python manage.py migratepython manage.py createsuperuser
Espero que não tenha sido muito maçante ler esse artigo imenso! Haha. O código desse artigo está no Github, e já vem até com uma Vagrant se você quiser levantar uma VM de brinks pra testar. É isso galera, aquele abraço!