[Django] カスタムユーザーを利用する
Djangoのユーザー認証をDjango標準のUser
モデルではなく自分で定義したものを利用するメモです。
カスタムユーザー
Djangoでカスタムユーザーを利用する場合は大きく以下の三つに分類できます。
- データベースのリレーションを利用する
AbstractUser
を継承するAbstractBaseUser
を継承する
一つ目はDjango標準のUser
モデルにOneToOneField
やForeignKey
を利用してリレーションを持たせる方法です。
厳密にはカスタムユーザーではありませんが、お手軽に実装できます。
二つ目はAbstractUser
を継承する方法です。
こちらは標準User
モデルが持っているフィールドは全て持っているので、フィールドを追加したい場合におススメです。
三つ目はAbstractBaseUser
を継承する方法です。
こちらは最低限のフィールド、機能以外は持っていないのでフィールドを削りたい場合や動作を柔軟に定義したい場合におススメです。
ただし、柔軟に定義できる分記述する量も増えてきます。
メールアドレスでログインするユーザー
今回は、ログイン時、登録時にユーザー名ではなくメールアドレスを利用するケースを考えます。
AbstractUser
まずは、お手軽なAbstractUser
を継承する方法から
実装
モデル
from django.contrib.auth.models import AbstractUser, UnicodeUsernameValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
class User(AbstractUser):
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
help_text=_(
'Required. 150 characters or fewer.'
' Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
)
email = models.EmailField(
_('email address'),
unique=True,
error_messages={
'unique': _("A user with that email address already exists."),
},
)
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
クラス変数のUSERNAME_FIELD
を'email'
にする事でログイン時に要求されるユーザー名をメールアドレスに変更しています。
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
部分でDjango標準のユーザーと重複利用されるのを防いでいます。
AbstractBaseUser
次に、AbstractBaseUser
を継承する方法を紹介します。
実装
モデル
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
class AbstractUser(AbstractBaseUser, PermissionsMixin):
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
blank=True
)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
email = models.EmailField(_('email address'), 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)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
swappable = 'AUTH_USER_MODEL'
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
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_name
def email_user(self, subject, message, from_email=None, **kwargs):
send_mail(subject, message, from_email, [self.email], **kwargs)
こちらのAbstractBaseUser
は、パーミッション関連の機能を持っていないので、パーミッションの機能を利用したい場合は、PermissionsMixin
を同時に継承しておく必要があります。
Django標準のパーミッション機能は、実際にサービスを作る場合だと微妙に要件を満たさないことが多いので、あまり必要だと感じる機会はありませんが今回は一応継承しています。
マネージャー
from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, username, email, password, **extra_fields):
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
username = self.model.normalize_username(username)
user = self.model(username=username, email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(username, email, password, **extra_fields)
def create_superuser(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(username, email, password, **extra_fields)
create_superuser
メソッドはpython manage.py createsuperuser
コマンドの時に実行されます。
今回の要件の場合は、わざわざ面倒なAbstractBaseUser
を継承する必要はありませんでしたが、登録時に色々な操作を挟む場合はこちらの方が有用な場合があります。
ログイン時に利用するユーザーを変更する
せっかく自分でカスタムユーザーを作成しても利用することを宣言しないとログイン時に利用してくれません。
なのでプロジェクトの設定に先ほど作成したカスタムユーザーモデルを利用するように設定を加えます。
AUTH_USER_MODEL = 'アプリケーション名.User'
アプリケーション名
部分は、カスタムユーザーモデルを定義したアプリケーションの名前に置き換えてください。
あとは、適宜マイグレーションを実行してください。
まとめ
- カスタムユーザーにフィールドを追加したい場合は
AbstractUser
を継承する - カスタムユーザーをより細かくカスタムするのなら
AbstractBaseUser
を継承する AUTH_USER_MODEL
の設定を忘れずに!