このページでは、Django での「カスタムユーザー」の作り方について解説していきます。
本サイトにおいては、別のカスタムユーザーの作り方として、下記ページで AbstractUser
編として解説を行なっています。
上記ページでは AbstractUser
を継承してカスタムユーザーを作成しましたが、このページでは AbstractBaseUser
(さらには PermissionsMixin
)を継承してカスタムユーザーを作成する手順を解説していきます。
特にこのページでは「カスタムユーザーの作り方」のみに焦点を当てて解説を行なっていきたいと思っています。作ったカスタムユーザーをアプリで使用する手順については、上記ページの AbstractUser
を継承して作成する場合とほぼ同様なので、このページでは詳細な説明は省略させていただきます(一応このページでも最後にカスタムユーザーをアプリで使用する実装例を紹介します)。
なので、作成したカスタムユーザーをアプリでどうやって利用すれば良いのか?については、上記ページを参考にしていただければと思います。
Contents
AbstractBaseUser
最初に AbstractBaseUser
について解説をしておきます。
AbstractBaseUser
と関連クラスとの関係
まず、AbstractBaseUser
や AbstractUser
等の関係を図で表すと下図のようになります。
この図からも分かるように、AbstractUser
は AbstractBaseUser
と、さらには PermissionsMixin
というクラスを継承して作成されているモデルになります。
さらに、Django でデフォルトで用意されている User
は、ほぼ AbstractUser
を継承するのみで作成されているモデルになります。
AbstractUser
を継承してカスタムユーザーを作成した場合、当然 AbstractUser
の持つフィールドやメソッドもカスタムユーザーに継承されるため、開発するアプリによっては不要なものまで継承される可能性があります。
AbstractUser
は一般的なユーザーの情報を管理するためのフィールドを持っており、例えば first_name
や last_name
というフィールドを持っていますが、アプリで first_name
や last_name
が不要という場合も多いのではないかと思います。
AbstractUser
を継承してカスタムユーザーを作成した場合、そういった不要なフィールドもカスタムユーザーに継承されることになり、開発するアプリによってはそういったフィールドが邪魔になる可能性もあります。
AbstractBaseUser
を継承するメリット
それに対し、AbstractBaseUser
と PermissionsMixin
にはユーザーを管理する上で必要最低限なフィールドやメソッドしか持っていません。例えば認証関連や権限管理関連のフィールドやメソッドのみを持つクラスとなります。
そのため、これらを継承してカスタムユーザーを作成した場合は、基本的には不要なフィールドやメソッドが継承されないことになります(もし認証や権限管理を行わないのであれば、継承されるフィールドやメソッドは不要ということになりますが…)。
なので、継承して定義したモデルに、自身で好きなフィールドやメソッドのみを追加していくことで、アプリにとって必要な情報のみを管理するカスタムユーザーを作成することが出来ます。
そのため、AbstractUser
を継承するよりも、AbstractBaseUser
と PermissionsMixin
を継承した方が、作成できるカスタムユーザーの自由度が高いというメリットがあります。
ただ、自由度が高い分、AbstractBaseUser
と PermissionsMixin
を継承してカスタムユーザーを作成する方が難易度は高いと思います。
この理由は、Django フレームワークのクラスの中には、カスタムユーザーに特定のフィールドが存在することを前提に作成されているものがあるからです。
分かりやすいのが username
フィールドです。例えば、作成するカスタムユーザーに username
フィールドを持たせない場合、username
が存在することを前提として作成されているクラスも含めて変更する必要があります。もしくは不要であっても username
フィールドをカスタムユーザーに持たせるようなことが必要になります。
そのため、AbstractBaseUser
と PermissionsMixin
を継承してカスタムユーザーを作成する場合であっても、一旦 AbstractUser
と同等のカスタムユーザーを定義して他のクラスへの影響を最小限に抑え、その上で、定義したカスタムユーザーに対して不要なフィールドを削除したり、必要なフィールドを追加してカスタマイズしていく手順を取るのが無難だと思います。
このあたりも含めて、ここから AbstractBaseUser
と PermissionsMixin
を継承してカスタムユーザーを作成する手順を解説していきます。
カスタムユーザーを定義する
では、AbstractBaseUser
および、PermissionsMixin
を継承してカスタムユーザーを作成する手順について解説していきます。
AbstractBaseUser
および PermissionsMixin
を継承してカスタムユーザーを作成していく場合、前述の通り、一旦 AbstractUser
(AbstractBaseUser
ではなく)の定義を models.py
にコピペしてカスタムユーザーモデルとして定義し、不要なフィールドやメソッドの削除と必要なフィールドやメソッドの追加を行なっていくのが無難だと思います。
AbstractUser
は django.contrib.auth.models
で定義されています。もし django.contrib.auth.models
の具体的なパスが分からない場合は、下記ページで紹介しているような手順でクラスの定義を表示してみると良いと思います。
ちなみに、Django 4.0.5 の場合は AbstractUser
の定義は下記のようなものになります(モデル名の変更と abstract = True
のコメントアウトを行なっています)。
使用している Django のバージョンによっては、もしかしたらエラーが出る可能性がありますが、エラーが出ないのであれば下記をコピペしてモデルを定義するので良いと思います。
from django.db import models
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.mail import send_mail
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import PermissionsMixin, UserManager
class CustomUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_("username"),
max_length=150,
unique=True,
help_text=_(
"Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
),
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
},
)
first_name = models.CharField(_("first name"), max_length=150, blank=True)
last_name = models.CharField(_("last name"), max_length=150, blank=True)
email = models.EmailField(_("email address"), blank=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 = "username"
REQUIRED_FIELDS = ["email"]
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
#abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_full_name(self):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = "%s %s" % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"""Return the short name for the user."""
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
AbstractUser
をコピペする上で注意すべき点は、class Meta
における abstract = True
の行を削除(or コメントアウト)する必要があるところになります。
この行があると、AUTH_USER_MODEL
にカスタムユーザーを設定した後にマイグレーション等を行うと下記のようなエラーが発生してしまうことになります。
django.core.exceptions.ImproperlyConfigured: AUTH_USER_MODEL refers to model 'login_app.CustomUser' that has not been installed
要は、abstract = True
が設定されていると、抽象モデル(クラス)とみなされて実体が作成されません。なので、そのモデルを参照した際に「そんなモデルは存在しないよ」といったエラーが発生することになります。
ちなみに、先ほど触れた AUTH_USER_MODEL
については下記ページで解説していますので、詳しく知りたい方は下記ページをご参照いただければと思います。
スポンサーリンク
カスタムユーザーのカスタマイズを行う
さて、先ほど定義した CustomUser
(カスタムユーザー)は AbstractUser
の定義をそのままコピペしてきただけなので、当然 AbstractUser
と同等のモデルということになります。
さらに、下記ページでも解説しているように、User
は AbstractUser
と同等のモデルなので、上記で定義した CustomUser
も User
と同等のモデルということになります。
当然 User
が持つフィールドを上記の CustomUser
も持つことになるため、不要なフィールドがあれば削除し、さらに他に必要なフィールドがあれば追加してカスタマイズすることが必要になります。
カスタマイズできること
ここからは、そのカスタマイズの仕方について解説していきますが、まずは AbstractBaseUser
と PermissionsMixin
を継承してカスタムユーザーを作成する場合に、上記で定義したカスタムユーザーに対してどのようなカスタマイズを行うことができるのかについて説明しておきます。
フィールド・メソッドの追加
まず、AbstractBaseUser
と PermissionsMixin
を継承してカスタムユーザーを作成する場合、AbstractUser
を継承してカスタムユーザーを作成するときと同様に「フィールド・メソッドの追加」を行うことが可能です。
フィールド・メソッドの削除 / 変更
また、カスタムユーザーを定義する で定義したカスタムユーザーから「フィールド・メソッドの削除 / 変更」を行うこともできます。
AbstractUser
を継承してカスタムユーザーを作成する場合でも、やろうと思えばフィールドの削除 / 変更を行うこともできるのですが、AbstractBaseUser
と PermissionsMixin
を継承してカスタムユーザーを作成する方が削除 / 変更はしやすいと思います。
ただし、特にフィールドを削除する際には注意が必要です。なぜなら、前述の通り、そのフィールドを他のクラスが利用している可能性があるからです。
そのクラスの具体的な例が、上記の CustomUser
モデルの中でも使用している UserManager
となります。UserManager
には createsuperuser
コマンド実行時に使用されるメソッドが定義されており、そのメソッドの中で CustomUser
モデルで定義されるフィールドとして username
・email
・is_staff
が利用されています。
つまり、これらのフィールドが存在することを前提として動作しているクラスということになります。
ですので、上記で挙げた3つのフィールドを CustomUser
から削除してしまうと、createsuperuser
コマンド実行時に UserManager
から例外が発生してしまって上手くスーパーユーザーの作成を行うことが出来ません。
このように、他のクラスから利用されているフィールドを削除する場合、単に削除しただけだとアプリ動作時に他のクラスで例外が発生する可能性があります。
この例外の発生を防ぐためには、その削除するフィールドを「利用しているクラス」の変更も必要になります。
各種設定
また、カスタムユーザーに対して各種設定を行うことも可能です。分かりやすい例が、ユーザーを一意に識別するフィールドの指定です。
カスタムユーザーにおいては、各ユーザーを一意に識別するためのフィールドが必要になります。デフォルトでは、そのユーザーを一意に識別するためのフィールドとして username
が指定されています。
そのため、ユーザー作成時に入力された username
が重複している場合、エラーが発生してユーザー作成に失敗することになります。皆さんもユーザー登録時に「そのユーザー名は既に使用されています」などのエラーが出てユーザー作成に失敗した経験があるのではないかと思います。Django のウェブアプリにおいても、このような制御が行われています。
前述の通り、ユーザーを一意に識別するフィールドはデフォルトで username
となっていますが、これを変更するようなことが可能です。例えば email
に変更してやれば、各ユーザーは email
で識別されるようになり、これによって username
の重複を許したり、username
フィールドを削除するようなことも可能になります。
また、メールアドレスが設定されるフィールドを指定したり、createsuperuser
コマンド時に入力受付を行うフィールドを設定したりすることも可能です。
こういった「各種設定」をカスタムユーザー作成時にカスタマイズすることが可能です。
まとめると、カスタムユーザーを定義する で定義したカスタムユーザーに対して下記のようなカスタマイズを行うことが出来ます。
- フィールド・メソッドの追加
- フィールド・メソッドの削除 / 変更
- 各種設定
必要に応じて上記のようなカスタマイズを行えば良いのですが、フィールドの削除に関しては注意が必要で、削除するフィールドを利用しているクラスが存在する場合、そのクラスの変更も必要となります。
そのため、「カスタマイズを行うために必要な実装」としては大きく分けて下記の4つが挙げられると思います。
- フィールド・メソッドの追加
- フィールド・メソッドの削除 / 変更
- 関連クラスの変更(削除するフィールドに応じて)
- 各種設定
「フィールド・メソッドの追加」に関しては、やり方や注意点は AbstractUser
を継承してカスタムユーザーを作成する時と同様ですので、このページでの解説は省略させていただきます。追加に関して詳しく知りたい方は、下記ページをご参照いただければと思います。
ここからは、上記における「フィールド・メソッドの削除 / 変更」「関連するクラスの変更」「各種設定」の具体的な手順について解説していきます。
具体的には、カスタムユーザーから email
と is_staff
以外のフィールドを削除する実装例を示しながら、各手順について解説していきたいと思います(AbstractBaseUser
と PermissionsMixin
から継承されるフィールドに関しては削除や変更は行いません)。
フィールド・メソッドの削除 / 変更
では、カスタムユーザーのカスタマイズにおける「フィールド・メソッドの削除 / 変更」について解説していきます。
前述の通り、ここではカスタムユーザーから email
と is_staff
以外のフィールドを削除していきたいと思いますので、下記の5つのフィールドを削除することになります。
username
first_name
last_name
is_active
date_joined
単純にフィールドを削除するだけであれば、カスタムユーザーを定義する で定義した CustomUser
(カスタムユーザー)を下記のように変更すれば良いだけになります。
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_("email address"), blank=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
#abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_full_name(self):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = "%s %s" % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"""Return the short name for the user."""
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
上記のように変更することで、email
と is_staff
以外のフィールドが CustomUser
から削除されることになります。
ただし、上記の CustomUser
では、削除した first_name
フィールドと last_name
フィールドが get_full_name
メソッドと get_short_name
メソッドで使用されているため、これらのメソッドが実行されると例外が発生することになります。
ですので、get_full_name
メソッドと get_short_name
メソッドを削除する or 削除したフィールドを利用しないように変更する必要があります。
おそらくですが、これらのメソッドは他のクラスから利用されていないため、今回は単にメソッドの削除を行いたいと思います。
上記の2つのメソッドを削除した場合、CustomUser
は下記のようになります。
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_("email address"), blank=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
#abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
スポンサーリンク
依存クラスの変更(必要に応じて)
とりあえず上記の変更によって不要なフィールドを削除することができたことになります。
具体的に削除したフィールドは下記の5つになります。
username
first_name
last_name
is_active
date_joined
ただし、フィールドは削除できたものの、実は現状の CustomUser
を利用するとアプリは正常に動作することができません。
なぜなら、先ほど削除したフィールドを利用するクラスが存在するからです(以降、フィールドを利用しているクラスを「依存クラス」と呼ばせていただきます)。
そのため、こういった依存クラスが動作する際に、削除されて存在しなくなったフィールドを利用しようとして例外が発生することになります。
そういった存在しなくなったフィールドが利用されないよう、依存クラスの変更が必要になります。
依存クラスを見つけ出す
この変更を行う際には、まず削除するフィールドに対する依存クラスを見つけてやる必要があります。フィールド名などで Django フレームワーク内の .py
ファイルに対して文字列検索を行えば、この依存クラスを見つけることができると思います。
詳しい解説は省略させていただきますが、今回の場合、上記の5つのフィールドのうち、username
以外のフィールドに関しては管理画面(admin
画面)の変更を行わない限りは他のクラスの変更なしでもアプリとしては上手く動作してくれるはずです。
その一方で、username
フィールドに関しては依存クラスが存在するため、そのクラスを変更しないとアプリとして上手く動作してくれません。
具体的には、現状だとスーパーユーザーを作成する際(createsuperuser
コマンドを実行する際)に例外が発生してスーパーユーザーを作成することができません。
この原因はスーパーユーザー作成時に UserManager
クラスが username
フィールドを利用するようになっているからです。
そのため、次は UserManager
を変更して username
フィールドを利用しないようにすることで、スーパーユーザー作成を正常に実行できるようにしていきたいと思います。
依存クラスを変更する
依存クラスを変更する際には、削除するフィールドを依存クラスで利用している箇所を変更し、削除するフィールドを利用しないようにしてやる必要があります。
ただし、今回変更するクラスは前述のとおり UserManager
になりますが、このクラスは Django フレームワーク内で定義されており、UserManager
クラスを直接変更すると他のアプリにまで影響を及ぼしてしまうことになります。
そのため、別途 models.py
に UserManager
の定義をコピペし、その後 UserManager
を変更していきたいと思います。これにより、UserManager
の変更による影響をアプリ内(プロジェクト内)にとどめるようにします。
ということで、まずは models.py
に UserManager
の定義をコピペしましょう!
Django 4.0.5 の場合は、先ほどフィールドの削除を行なった CustomUser
の定義も含めると、models.py
は下記のようになります。import
の追加や import
の削除を行なっているので注意してください。
from django.db import models
from django.apps import apps
from django.contrib import auth
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.mail import send_mail
from django.contrib.auth.hashers import make_password
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import PermissionsMixin
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, username, email, password, **extra_fields):
"""
Create and save a user with the given username, email, and password.
"""
if not username:
raise ValueError("The given username must be set")
email = self.normalize_email(email)
# Lookup the real model class from the global app registry so this
# manager method can be used in migrations. This is fine because
# managers are by definition working on the real model.
GlobalUserModel = apps.get_model(
self.model._meta.app_label, self.model._meta.object_name
)
username = GlobalUserModel.normalize_username(username)
user = self.model(username=username, email=email, **extra_fields)
user.password = make_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)
def with_perm(
self, perm, is_active=True, include_superusers=True, backend=None, obj=None
):
if backend is None:
backends = auth._get_backends(return_tuples=True)
if len(backends) == 1:
backend, _ = backends[0]
else:
raise ValueError(
"You have multiple authentication backends configured and "
"therefore must provide the `backend` argument."
)
elif not isinstance(backend, str):
raise TypeError(
"backend must be a dotted import path string (got %r)." % backend
)
else:
backend = auth.load_backend(backend)
if hasattr(backend, "with_perm"):
return backend.with_perm(
perm,
is_active=is_active,
include_superusers=include_superusers,
obj=obj,
)
return self.none()
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_("email address"), blank=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
#abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
まずは UserManager
クラスに注目してみましょう!
各メソッドの定義を見てみると、引数や処理の中で username
を使用していることが確認できると思います。
特に _create_user
メソッドにおける下記の処理では、username
を設定してデータベースのテーブルへのレコード保存が行われています。
user = self.model(username=username, email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
ですが、カスタムユーザーから username
フィールドを削除してしまうと、テーブルから username
の列が削除されることになります(Django ではモデルに対してテーブルが作成され、そのテーブルの列はモデルの持つフィールド毎に用意される)。
そのテーブルに対して username
の列にユーザー名が設定されたレコードを保存しようとしても、保存先のテーブルに username
の列がないため、レコード保存時に例外が発生してしまいます。
こういった例外が発生しないよう、カスタムユーザーから username
フィールドを削除するためには、まずはこれらのメソッドの変更を行う必要があります。
詳細な解説は省略しますが、username
フィールドを削除するためには、下記のように UserManager
クラスを変更してやれば良いと思います。基本的には、単に username
に関連する記述を削除しているだけです(引数を削除したり処理を削除したり)。
from django.db import models
from django.contrib import auth
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.core.mail import send_mail
from django.contrib.auth.hashers import make_password
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import PermissionsMixin
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""
Create and save a user with the given email and password.
"""
if not email:
raise ValueError("The given email must be set")
email = self.normalize_email(email)
# Lookup the real model class from the global app registry so this
# manager method can be used in migrations. This is fine because
# managers are by definition working on the real model.
user = self.model(email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user
def create_user(self, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, 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(email, password, **extra_fields)
def with_perm(
self, perm, is_active=True, include_superusers=True, backend=None, obj=None
):
if backend is None:
backends = auth._get_backends(return_tuples=True)
if len(backends) == 1:
backend, _ = backends[0]
else:
raise ValueError(
"You have multiple authentication backends configured and "
"therefore must provide the `backend` argument."
)
elif not isinstance(backend, str):
raise TypeError(
"backend must be a dotted import path string (got %r)." % backend
)
else:
backend = auth.load_backend(backend)
if hasattr(backend, "with_perm"):
return backend.with_perm(
perm,
is_active=is_active,
include_superusers=include_superusers,
obj=obj,
)
return self.none()
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_("email address"), blank=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
#abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
上記のように変更を行えば、UserManager
クラスは username
フィールドを使用しなくなるため、username
フィールドを削除しても UserManager
クラスは正常に動作することができるようになります。
前述でも簡単に触れましたが、UserManager
はスーパーユーザー作成時に動作するクラスであり、上記の変更によって正常にスーパーユーザーを作成することができるようになります。
各種設定の変更
以上で、CustomUser
からの不要なフィールドの削除と、その削除したフィールドを利用するクラスの変更が完了したことになります。
変更可能な設定
あとは、CustomUser
の下記のクラス変数を変更することで、開発するアプリに合わせて CustomUser
の各種設定の変更を行うことが出来ます。
EMAIL_FIELD
:メールアドレスとして扱うフィールドの指定USERNAME_FIELD
:ユーザーを一意に識別するフィールドの指定REQUIRED_FIELDS
:createsuperuser
コマンド実行時に入力受付を行うフィールドの指定
ちょっと分かりにくいのは USERNAME_FIELD
だと思います。
USERNAME_FIELD
はユーザー名として扱うフィールドではなく、ユーザーを一意に識別するためのフィールドを指定します。ユーザーを一意に識別するためのフィールドですので、USERNAME_FIELD
で指定されたフィールドは、ユーザー毎に異なる必要があります。
また、上記の各クラス変数では指定の仕方にルールがあるので注意してください。
まず EMAIL_FIELD
に関していえば、このクラス変数に指定されるフィールドはメールの送信先として利用されるため、当然メールアドレスとして扱うことのできるフィールドを設定する必要があります。
また、USERNAME_FIELD
は前述の通りユーザーを一意に識別するためのフィールドですので、このクラス変数に指定されるフィールドの値は各ユーザーで重複しないようにする必要があります。
そのため、USERNAME_FIELD
に指定するフィールドにおいては、unique
オプションを True
に設定しておく必要があります。
さらに、REQUIRED_FIELDS
に指定するリストには、USERNAME_FIELD
に指定するフィールドを含ませてはいけません。この辺りは下記ページで解説していますので、詳細については下記ページを参照していただければと思います。
各種設定の変更例
上記で挙げたクラス変数は、現状の CustomUser
ではコピペ元の AbstractUser
と同じように次のように設定が行われています。
EMAIL_FIELD = "email"
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
これらは開発するアプリや作成するカスタムユーザーに合わせて必要に応じて変更してやれば良いです。
ですが、今回に関しては username
フィールドを削除しているため、上記の設定のままだと USERNAME_FIELD
に指定しているフィールドがカスタムユーザーに存在しないのでエラーになってしまいます。ですので、今回の場合は必ず USERNAME_FIELD
に username
以外を指定するよう変更してやる必要があります。
例えば、USERNAME_FIELD
に email
を指定してやれば、カスタムユーザーに存在するフィールドを USERNAME_FIELD
に指定することになるのでエラーを解消することが出来ます。そして、この場合、ユーザーを一意に識別するフィールドとして email
が使用されることになります。
ただし、USERNAME_FIELD
に email
を指定しただけだと、実はアプリ動作時やマイグレーション時にエラーが発生することになります。
これは、変更可能な設定 で挙げたルールに反しているからになります。
まず、現状の email
フィールドには unique
オプションに True
が設定されていないため、USERNAME_FIELD
に email
を指定すると下記のようなエラーが発生することになります。
ERRORS: login_app.CustomUser: (auth.E003) 'CustomUser.email' must be unique because it is named as the 'USERNAME_FIELD'.
また、REQUIRED_FIELDS
に指定するリストに USERNAME_FIELD
に指定した email
が含まれるため、上記の設定のままだと次のようなエラーが発生することになります。
ERRORS: login_app.CustomUser: (auth.E002) The field named as the 'USERNAME_FIELD' for a custom user model must not be included in 'REQUIRED_FIELDS'. HINT: The 'USERNAME_FIELD' is currently set to 'email', you should remove 'email' from the 'REQUIRED_FIELDS'.
こういったエラーを解消しつつ、USERNAME_FIELD
に email
を指定するためには、CustomUser
を下記のように変更する必要があります。
class CustomUser(AbstractBaseUser, PermissionsMixin):
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."),
)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
#abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
USERNAME_FIELD
と REQUIRED_FIELD
の変更及び、最初の行の EmailField
への引数指定で blank=True
を削除し、代わりに unique=True
を指定するようにしています(blank=True
を削除することで入力必須フィールドとなる)。
前述の通り、これらのクラス変数の設定は開発するアプリや作成するカスタムユーザーに合わせて必要に応じて変更をすれば良いのですが、削除するフィールドによって設定が必須となる場合もあるので注意してください。
メールアドレス認証を行うサンプル
以上が、AbstractBaseUser
と PermissionsMixin
を継承してカスタムユーザーを作成する際の基本的な流れとなります。
ここまでカスタムユーザーを作成する部分のみに焦点を当てて説明を行ってきましたので、最後にカスタムユーザーを作成する部分以外の手順も含めて「メールアドレス認証」を行うアプリの実装例を紹介していきたいと思います。
カスタムユーザーを作成する部分以外の手順は下記ページで紹介したものと同じですので、必要に応じて下記ページも参照しながら実装を進めていただければと思います。
【Django】カスタムユーザー(独自のユーザー)の作り方【AbstractUser編】スポンサーリンク
プロジェクトとアプリの作成
まずは、いつも通りプロジェクトとアプリを作成していきます。
最初に、下記コマンドを実行してプロジェクト login_project
を作成します。
% django-admin startproject login_project
続いて、上記コマンドで作成される login_project
フォルダの中に移動し、さらに startapp
を実行してアプリ login_app
を作成します。
% cd login_project % python manage.py startapp login_app
次に、login_project
フォルダの中にある settings.py
内の INSTALLED_APPS
を下記のように変更し、プロジェクトに対してアプリを登録します。
# Application definition
INSTALLED_APPS = [
'login_app', # 追加
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
カスタムユーザーの作成
続いてカスタムユーザーを作成していきたいと思います。
作成するカスタムユーザーとしては、カスタムユーザーをカスタマイズする で示したカスタムユーザーと同じもの使用したいと思います。
このカスタムユーザーを使用すれば、USERNAME_FIELD
に email
が指定されているため email
でユーザーを一意に識別することになり、さらに後述の手順を踏むことで email
フィールドで認証が行われるようになるため、結果的にユーザー名ではなく、メールアドレスでの認証が行われるようになります。
ということで、login_app
フォルダの中にある models.py
を開き、下記のように変更してください。
from django.db import models
from django.contrib import auth
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.core.mail import send_mail
from django.contrib.auth.hashers import make_password
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import PermissionsMixin
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""
Create and save a user with the given email and password.
"""
if not email:
raise ValueError("The given email must be set")
email = self.normalize_email(email)
# Lookup the real model class from the global app registry so this
# manager method can be used in migrations. This is fine because
# managers are by definition working on the real model.
user = self.model(email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user
def create_user(self, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, 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(email, password, **extra_fields)
def with_perm(
self, perm, is_active=True, include_superusers=True, backend=None, obj=None
):
if backend is None:
backends = auth._get_backends(return_tuples=True)
if len(backends) == 1:
backend, _ = backends[0]
else:
raise ValueError(
"You have multiple authentication backends configured and "
"therefore must provide the `backend` argument."
)
elif not isinstance(backend, str):
raise TypeError(
"backend must be a dotted import path string (got %r)." % backend
)
else:
backend = auth.load_backend(backend)
if hasattr(backend, "with_perm"):
return backend.with_perm(
perm,
is_active=is_active,
include_superusers=include_superusers,
obj=obj,
)
return self.none()
class CustomUser(AbstractBaseUser, PermissionsMixin):
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."),
)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
#abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
カスタムユーザーをカスタマイズする でも解説したように、username
を削除する場合は createsuperuser
コマンド実行時に上手くスーパーユーザーが作成できるよう、UserManager
の変更も必要になります。
AUTH_USER_MODEL
の設定
カスタムユーザーが作成できましたので、ここからはほぼ下記ページで示した手順でアプリを開発していくことになります。
【Django】カスタムユーザー(独自のユーザー)の作り方【AbstractUser編】まずは、settings.py
の最後に下記を追記し、アプリ内で認証時に利用するユーザー管理モデルとして先ほど作成した CustomUser
を設定します。
AUTH_USER_MODEL = 'login_app.CustomUser'
スポンサーリンク
マイグレーションの実行
続いてマイグレーションを実行しましょう!
startproject
で作成された login_project
フォルダ内で下記の2つのコマンドを実行してみてください。おそらくエラーが発生することなく正常にマイグレーションが実行できるはずです。
% python manage.py makemigrations % python manage.py migrate
このマイグレーションによってデータベースに login_app.customuser
というテーブルが作成されることになります。
ポイントは、この login_app.customuser
テーブルには、CustomUser
から削除した username
等の列が存在しない点になります。
スーパーユーザーの追加
次は管理画面でカスタムユーザーを管理できるように設定していきたいと思います。
この管理画面にアクセスするためにはスーパーユーザーが必要ですので、まずはスーパーユーザーを追加していきましょう!
このスーパーユーザーの追加を行うために、startproject
で作成された login_project
フォルダ内で下記コマンドを実行してみてください。
% python manage.py createsuperuser
上記コマンドを実行し、作成するスーパーユーザーのメールアドレスとパスワード(確認用も含めて2回)を入力すれば、スーパーユーザーの作成に成功するはずです。
% python manage.py createsuperuser Email address: hanako@yamada.jp Password: Password (again): Superuser created successfully.
いつも通りであれば、createsuperuser
コマンド実行時にはユーザー名の入力受付も行われるはずですが、CustomUser
の USERNAME_FIELD
を username
とは異なるフィールドに設定したため& REQUIRED_FIELDS
に指定するリストに username
を含ませていないため、ユーザー名の入力受付が行われないようになっています。
また、createsuperuser
コマンド実行時に動作する UserManager
も username
を使用しないように変更を行なったため、username
の入力受付が行われなくても正常にスーパーユーザーの作成を行うことが可能となっています。
ちなみに、UserManager
の変更を行わなかった場合、createsuperuser
コマンド実行時には下記のようにエラーが発生することになります。
self.UserModel._default_manager.db_manager(database).create_superuser( TypeError: create_superuser() missing 1 required positional argument: 'username'
管理画面の設定
次は、管理画面でカスタムユーザーを管理できるように設定を行なっていきます。
カスタムユーザーを管理画面から管理できるようにするためには、admin.py
で admin.site.register
を実行してカスタムユーザーを登録することが必要になります。
ということで、login_app
フォルダの下にある admin.py
を下記のように変更しましょう!
from django.contrib import admin
from django.contrib.auth import get_user_model
CustomUser = get_user_model()
admin.site.register(CustomUser)
変更後に、まず下記コマンドを実行して Django の開発用ウェブサーバーを起動してください。
% python manage.py runserver
コマンド実行後、下記 URL をウェブブラウザで開いてみてください。
http://localhost:8000/admin/
すると、下の図のような管理画面へのログインフォームが表示されると思います。
ここでも入力受付が行われるのはメールアドレスとパスワードであり、管理画面への認証をメールアドレスによって行えるようになったことが確認できると思います。
このフォームでは、先ほど createsuperuser
コマンドによって作成したスーパーユーザーのメールアドレスとパスワードを入力して Log in
ボタンを押してください。
ログインに成功すれば、下の図のような画面が表示されるはずです。
この画面における LOGIN_APP
セクション内の Users
というリンクをクリックすれば、下の図のように「ユーザー一覧リスト」が表示されます。
このユーザー一覧リストからユーザー名をクリックすれば、下の図のように「ユーザー詳細フォーム」が表示され、さらにここでユーザーの情報の変更も可能です。
また、ユーザー一覧リストの右上にある ADD USER +
のボタンをクリックすれば、下の図のような「ユーザ作成フォーム」が表示され、必要な入力を行なって画面下の SAVE
ボタンをクリックすることでユーザーの追加を行うことができます(太字部分が入力必須項目となります)。
これらのユーザー詳細フォームやユーザー作成フォームでは、CustomUser
で削除したフィールド(username
・first_name
・last_name
など)の入力や変更が不可となっており、作成した CustomUser
に応じて管理画面も変化していることを確認できると思います。
こんな感じで、admin.py
の変更により、独自で作成したユーザーの管理を行うことができるようになります。
ただ、上記の「ユーザー一覧リスト」にはユーザー名しか表示されていませんので一覧性が悪いですし、「ユーザー詳細フォーム」や「ユーザー作成フォーム」では CustomUser
の持つフィールド全てが表示されているので、ちょっと冗長な気もします。
さらに、ユーザー追加時等に入力したパスワードがそのまま表示されてセキュリティ的にもイマイチです。
こういった具合に、ユーザー管理を行う画面・フォーム等に不満がある場合は管理画面のカスタマイズを行なった方が良いです。
この管理画面(ユーザー管理画面)のカスタマイズ方法については下記ページで解説していますので、必要に応じて参考にしていただければと思います。
【Django】管理画面でカスタムユーザーを管理する(UserAdmin)スポンサーリンク
フォーム・ビュー・テンプレートの作成
ここまでの変更によって、カスタムユーザーが定義され、カスタムユーザーを管理画面から管理・作成することができるようになりました。
最後に、カスタムユーザーをフォームやビュー等から利用する例を紹介していきます。
実現するページは3つのみで、1つ目はユーザーアカウント(カスタムユーザー)を追加するページ、2つ目はログインを行うページ、3つ目はログインユーザーのメールアドレスを表示するページになります。
ユーザーアカウントの作成やログインに関しては下記ページで詳しく解説していますので、これらを実現するための手段の詳細を知りたい方は下記ページを参照して頂ければと思います。
【Django】ログイン機能の実現方法(関数ベースビュー編)このページでは、スクリプトの紹介および、ユーザーに関連する説明のみを行なっていきたいと思います。
フォームの作成
まずは login_app
フォルダの下に forms.py
を新規作成し、中身を下記のように変更します。
from django import forms
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth import get_user_model
CustomUser = get_user_model()
class SignupForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ['email']
class LoginForm(AuthenticationForm):
pass
UserCreationForm
や AuthenticationForm
に関しては先ほど紹介した 【Django】ログイン機能の実現方法(関数ベースビュー編) で解説していますので、詳細を知りたい方は【Django】ログイン機能の実現方法(関数ベースビュー編) をご参照ください。
各フォームの model
には models.py
に定義した CustomUser
を指定する必要があります。この CustomUser
は、settings.py
で AUTH_USER_MODEL
に models.py
で定義した CustomUser
を指定している場合、上記のように get_user_model
から取得することが可能です。
また、SignupForm
の fields
にはユーザー作成時(アカウント登録時)にユーザーから入力受付を行うフィールドを指定する必要があります。今回はメールアドレスとパスワードの入力受付を行うため、上記のように指定を行なっています(SignupForm
の fields
への指定においては、パスワードの指定は省略可能です)。
さらに、LoginForm
の fields
にはログイン時にユーザーから入力受付を行うフィールドを指定する必要があります。今回はメールアドレス認証を行うため、メールアドレスとパスワードの入力受付のみを行うよう、上記のように指定を行なっています。
これらの fields
の指定においては、model
に指定したモデルに存在しないフィールドを指定するとエラーになるので注意してください。今回の場合、CustomUser
から username
を削除しているわけですから、username
を指定するとエラーになることになります。
こんな感じで、モデルの持つフィールドに合わせる形で、フォームやビュー、テンプレートを作成していく必要があるので注意してください。
テンプレートの作成
続いてテンプレートを作成していきます。
今回用意するテンプレートは基本的に下記ページの ログイン機能実現用のスクリプト で紹介している signup.html
・login.html
・user.html
とほぼ同等のものなので、詳細な説明は省略させていただきます(user.html
はメールアドレスのみを表示するように変更しています)。
これらのファイルの作成フォルダが login_app/templates/login_app/
であることに注意してください。
{% load static %}
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ユーザー登録</title>
</head>
<body>
<h1>ユーザー登録</h1>
<form action="{% url 'signup' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<p><input type="submit" value="登録"></p>
</form>
</body>
</html>
{% load static %}
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ログイン</title>
</head>
<body>
<h1>ログイン</h1>
<p>{{message}}</p>
<form action="{% url 'login'%}" method="post">
{% csrf_token %}
{{ form.as_p }}
<p><input type="hidden" name="next" value="{{next}}"></p>
<p><input type="submit" value="ログイン"></p>
</form>
<p><a href="{% url 'signup'%}">ユーザー登録</a></p>
</body>
</html>
{% load static %}
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>あなたの情報</title>
</head>
<body>
<h1>{{email}}</h1>
</body>
</html>
ビューの作成
次はビューを作成していきます。
こちらも基本的に下記ページの ログイン機能実現用のスクリプト で紹介している views.py
と似ている部分が多いのですが、簡略化して logout_view
や other_view
を削除したり、リダイレクト関連の処理を省略している部分もあるので注意してください。
また、user_view
においては、引数 request
のデータ属性 user
にはログイン中のユーザーに対する CustomUser
のインスタンスが設定されています。なので、ログインユーザーのメールアドレスは request.user.email
から取得することが可能です。
from django.shortcuts import render, redirect
from .forms import SignupForm, LoginForm
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
def signup_view(request):
if request.method == 'POST':
form = SignupForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect(to='/login_app/user/')
else:
form = SignupForm()
param = {
'form': form
}
return render(request, 'login_app/signup.html', param)
def login_view(request):
if request.method == 'POST':
next = request.POST.get('next')
form = LoginForm(request, data=request.POST)
if form.is_valid():
user = form.get_user()
if user:
login(request, user)
return redirect(to='/login_app/user/')
else:
form = LoginForm()
param = {
'form': form,
}
return render(request, 'login_app/login.html', param)
@login_required
def user_view(request):
user = request.user
params = {
'email': user.email
}
return render(request, 'login_app/user.html', params)
パスとビューの関連付け
最後にパスとビューの関連付けを行なっていきます。
まず login_project
フォルダの下の urls.py
を下記のように変更し、login_app/
から始まるパスが指定された際に login_app
フォルダ下の urls.py
を参照するようにします。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('login_app/', include('login_app.urls'))
]
続いて、その参照先となる urls.py
を login_app
フォルダの下に下記のように作成します。
これにより、path
の第1引数で指定したパス(URL)へのアクセスがあった際に、第2引数で指定した views.py
の各関数が実行されるようになります。
from . import views
from django.urls import path
urlpatterns = [
path('signup/', views.signup_view, name='signup'),
path('login/', views.login_view, name='login'),
path('user/', views.user_view, name='user'),
]
動作確認
ここまでフォームやビューの作成を行なってきましたので動作確認をしてみましょう!
まずは Django 開発用ウェブサーバーを起動し(python manage.py runserver
を実行)、さらに下記 URL をウェブブラウザで開いてみてください。
http://localhost:8000/login_app/signup/
そうすると、下の図のようなユーザー登録フォームが表示されるはずです。
forms.py
で SignupForm
の fields
に指定したフィールド&パスワードの入力を行うことができるようになっている点を確認できると思います。
さらに、上の図の登録フォームにメールアドレスとパスワードの入力を行なった後に 登録
ボタンを押せば、次は下の図のようなユーザーの情報表示ページに遷移するはずです。
今回の場合は表示されるのがメールアドレスのみとなりますが、CustomUser
にフィールドを追加しておけば、そのフィールドの情報をログイン中のユーザーに合わせて表示するようなことも可能です。例えばユーザーのアバターを表示することなども可能です。
最後に下記 URL をウェブブラウザで表示してみてください。
http://localhost:8000/login_app/login/
今度は下の図のようなログインフォームが表示されるはずです。
このログインフォームに、スーパーユーザーの追加 で追加したユーザーもしくは先程のユーザー登録フォームで作成したユーザーのメールアドレスとパスワードを入力して ログイン
ボタンを押してみてください。ログインに成功し、先ほど同様にユーザー情報表示ページに遷移するはずです。
いつもはユーザー名とパスワードによって認証を行なっていたのが、メールアドレスとパスワードによって認証されるようになっており、目的のメールアドレス認証を実現できていることが確認できると思います!
スポンサーリンク
まとめ
このページでは、Django での「カスタムユーザー」の作り方について解説しました!
カスタムユーザーは AbstractBaseUser
、さらには PermissionsMixin
を継承することで作成することが出来ます。
AbstractUser
よりも作成できるカスタムユーザーとしては自由度は高いですが、その分カスタムユーザー作成時の難易度は高いと思います。
そのため、まずは カスタムユーザーを定義する で解説したように AbstractUser
同等のモデルを定義し、そのモデルを カスタムユーザーをカスタマイズする で解説したようにカスタマイズする手順を踏むのが無難だと思います。
もちろん慣れてきたら、AbstractBaseUser
と PermissionsMixin
を継承するモデルを定義し、そのモデルをゼロから作り上げていくのでも良いと思います。
特にウェブアプリにとってはユーザー管理が重要ですので、こういったカスタムユーザーの作り方などはしっかりマスターしておくと良いと思います!