- モデルはクラスなので、正確にはモデルクラスと呼ぶが本記事では、モデルと表記する
- 途中で、デフォルトUserモデルからカスタムユーザモデルに切り替えたい場合以下の操作を行う
- settings.py
- INSTALLED_APPSのdjango.contrib.adminをコメントアウト
- AUTH_USER_MODEL = 'accounts.CustomUser'を追加しているか確認
- urls.py
- urlpatternsのpath('admin/', admin.site.urls)をコメントアウト
- マイグレーション
- 2つのコメントアウトを外す
- settings.py
結論
-
カスタムユーザモデルとは
- アプリ開発者が自ら作成するユーザを管理するモデル(ユーザ管理モデル)のこと。
- サインアップ機能を実装する前に、作成しておくことが公式から推奨されている。
-
カスタムするメリット
- ユーザ名ではなく、Eメールアドレスでユーザ認証(ログイン)する方法にしたい場合
- 独自のフィールド(カラム)を定義したい場合
- ユーザ管理モデルはアプリに応じて変更することが可能(デフォルトUserモデルは変更できない)
- デフォルトUserモデルの代わりにカスタムユーザーを利用しようと思ってもマイグレーション時にエラーが出て大変。
-
カスタムする方法(2パターン)
- AbstractUserクラスを継承したモデルを作成
- 標準のUserモデルにカラム(フィールド)の追加のみをしたい場合はこれを使う
- 実装は簡単だが、自由度は低い
- AbstractBaseUser, PermissionsMixinクラスを継承したモデルをカスタマイズする
- 認証機能以外(フィールドやメソッド)は、1から実装する必要がある(AbstractUserをコピペすれば楽に実装できる)
- 実装は難しいが、自由度が高い
- AbstractUserクラスを継承したモデルを作成
- デフォルトのUserモデルは、AbstractUserモデルを継承している
- AbstractUserモデルは、AbstractBaseUser, PermissionsMixinモデルを継承している
- AbstractUser は一般的なユーザーの情報を管理するためのフィールドを持っている。
- 具体例) first_name や last_name というフィールド
- しかし、これらのフィールドはアプリで使用しない、つまり不要である場合が多いと思われる
- AbstractUser を継承してカスタムユーザーを作成した場合、そういった不要なフィールドもカスタムユーザーに継承されるため、冗長化してしまう
デフォルトのUserモデルが定義しているフィールド
- デフォルトのUserモデルは、親モデルクラスから以下のフィールドを継承し、そのまま定義している
フィールド | 親モデル |
---|---|
id | AbstractUser |
username | AbstractUser |
first_name | AbstractUser |
last_name | AbstractUser |
AbstractUser | |
is_staff | AbstractUser |
is_active | AbstractUser |
date_joined | AbstractUser |
password | AbstractBaseUser |
last_login | AbstractBaseUser |
is_superuser | PermissionMixin |
groups | PermissionMixin |
user_permissions | PermissionMixin |
- デフォルトのUserモデルで定義されているこれらのフィールドをみてわかるように、first_nameやlast_nameフィールドはアプリで使用しない、つまり不要である場合が多いと考えられる。
- つまり、用途に応じてモデルをカスタムする必要がある
- AbstractUserモデルを継承して、カスタムユーザモデルを作成する場合
- これらのフィールド全てが継承される
- 継承したこれらのフィールドを削除することもできるが面倒臭い。さらに、フィールドを削除する際は2点注意が必要
- AbstractBaseUser, PermissionsMixinモデルを継承して、カスタムユーザモデルを作成する場合
- 5つのフィールドしか継承されないため、本当に必要なフィールドのみ定義することができる。そのため、コードの冗長化を防ぐことができる。
- また、簡単にフィールドを追加することができる
- フィールドを削除する際は2点注意が必要
AbstractUserを継承したカスタムユーザモデル
- 単に、AbstractUserモデルを継承しただけだと、デフォルトのUserモデルを全く同じモデルになる
- デフォルトUserモデルは、ほとんどAbstractUserモデルクラスを継承しただけなので
カスタムユーザモデルの作成手順
- AbstractUserモデルを継承したカスタムユーザモデルを定義する
- settings.pyでの設定
- Django管理サイトに登録
- マイグレーション
- スーパーユーザの作成
- サーバ起動
カスタムUserモデル作成
- カスタムユーザモデルを以下のように定義する
- AbstractUser, AbstractBaseUser, PermissionsMixinモデルクラスが定義しているフィールドやメソッドを継承する
- 追加したいフィールドがあれば定義しても可
- デフォルトUserモデルと異なり、カスタムユーザモデルはウェブアプリ(もしくはプロジェクト)毎に定義するモデルであるため、変更したとしても影響を及ぼすのはウェブアプリ内のみになる
from django.db import models
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
'''AbstractUserの全フィールドを継承
'''
pass
※DjangoデフォルトのUserモデルとほとんど同じモデルユーザ管理モデルを作成する理由。
デフォルトUserモデルは、正確には変更することはできるのですが、変更すると全アプリに影響を及ぼすことになるため。そのため、Userモデル自体を変更せず、内容が同じモデルを定義する。
フィールドの追加方法
- 上記カスタムユーザモデルに対して、nicknameフィールドを追加。
class CustomUser(AbstractUser):
nickname = models.CharField(max_length=100)
settings.py
・デフォルトでUserモデルを使用するようになっている
・以下のように、作成したカスタムユーザモデルをユーザモデルとして設定する
AUTH_USER_MODEL = 'accounts.CustomUser'
Django管理サイトに登録
- 作成したカスタムUserモデルをDjango管理サイトに登録する
- get_user_model は、settings.py で AUTH_USER_MODEL に指定されているモデルを取得する関数
from django.contrib import admin
from django.contrib.auth import get_user_model
CustomUser = get_user_model()
admin.site.register(CustomUser)
管理サイトで表示の仕方をカスタムしたい場合
from django.contrib import admin
from .models import CustomUser
class CustiomUserAdmin(admin.ModelAdmin):
'''管理ページのレコード一覧に表示するカラムを設定するクラス(※任意)
'''
list_display = ('id', 'username') # レコード一覧にid, usernameカラムを表示
list_display_links = ('id', 'username') # list_displayに指定したカラムにリンクを表示
# Django管理サイトにCustomUser, CustomUserAdminを登録
admin.site.register(CustomUser, CustomUserAdmin)
マイグレーション
$ python3 manage.py makemigrations accounts
$ python3 manage.py migrate
管理サイトのスーパーユーザを作成
- メールアドレスは空欄でもok
$ python3 manage.py createsuperuser
AbstractBaseUser, PermissionsMixinを継承したカスタムユーザモデル
- AbstractBaseUser を継承するメリット
- AbstractUserに対し、AbstractBaseUser と PermissionsMixin にはユーザーを管理する上で必要最低限なフィールドやメソッドしか持っていません。例えば認証関連や権限管理関連のフィールドやメソッドのみを持つクラスとなります。
- 基本的には不要なフィールドやメソッドが継承されないことになります
- 自身で好きなフィールドやメソッドのみを追加していくことで、アプリにとって必要な情報のみを管理するカスタムユーザーを作成することが出来ます。
- そのため、AbstractUser を継承するよりも、AbstractBaseUser と PermissionsMixin を継承した方が、作成できるカスタムユーザーの自由度が高いというメリットがあります。
- しかし、難易度が高くなる
- 分かりやすいのが username フィールドです。例えば、作成するカスタムユーザーに username フィールドを持たせない場合、username が存在することを前提として作成されているクラスも含めて変更する必要があります。もしくは不要であっても username フィールドをカスタムユーザーに持たせるようなことが必要になります。
- そのため、AbstractBaseUser と PermissionsMixin を継承してカスタムユーザーを作成する場合であっても、一旦 AbstractUser と同等のカスタムユーザーを定義して他のクラスへの影響を最小限に抑え、その上で、定義したカスタムユーザーに対して不要なフィールドを削除したり、必要なフィールドを追加してカスタマイズしていく手順を取るのが無難だと思います。
カスタムできること、カスタムに必要な実装
- フィールド・メソッドの追加
- フィールド・メソッドの削除 / 変更
- 各種設定
- 関連クラスの変更(削除するフィールドに応じて)
フィールド・メソッドの削除 / 変更
- 特にフィールドを削除する際には注意が必要です。なぜなら、前述の通り、そのフィールドを他のクラスが利用している可能性があるからです。
- 依存クラスを見つけ出す方法
- フィールド名などで Django フレームワーク内の .py ファイルに対して文字列検索を行えば、この依存クラスを見つけることができる
- 依存クラスを見つけ出す方法
- モデル内のメソッドで削除したフィールドが定義されていないかを確認。
- 定義されている場合は、メソッドを削除するか既存のフィールド、自分が追加したフィールドを指定する。
- メソッドを削除する場合は、他のクラスから利用されていないかを確認する必要がある
- AbstractUser を継承してカスタムユーザーを作成する場合でも、やろうと思えばフィールドの削除 / 変更を行うこともできるのですが、AbstractBaseUser と PermissionsMixin を継承してカスタムユーザーを作成する方が削除 / 変更はしやすいと思います。
username フィールドに関しては依存クラスが存在するため、そのクラスを変更しないとアプリとして上手く動作してくれません。
具体的には、現状だとスーパーユーザーを作成する際(createsuperuser コマンドを実行する際)に例外が発生してスーパーユーザーを作成することができません。
この原因はスーパーユーザー作成時に UserManager クラスが username フィールドを利用するようになっているからです。
そのため、次は UserManager を変更して username フィールドを利用しないようにす
今回変更するクラスは前述のとおり UserManager になりますが、このクラスは Django フレームワーク内で定義されており、UserManager クラスを直接変更すると他のアプリにまで影響を及ぼしてしまうことになります。
そのため、別途 models.py に UserManager の定義をコピペし、その後 UserManager を変更していきたいと思います。これにより、UserManager の変更による影響をアプリ内(プロジェクト内)にとどめるようにします。
- models.py に UserManager の定義をコピペしてusernameフィールドが指定されている箇所を自分が定義したフィールドに修正する
- UserManager クラスは username フィールドを使用しなくなるため、username フィールドを削除しても UserManager クラスは正常に動作することができるようになります。
各種設定
変更可能な設定
-
EMAIL_FIELD:メールアドレスとして扱うフィールドの指定
-
USERNAME_FIELD:ユーザーを一意に識別するフィールドの指定
-
REQUIRED_FIELDS:createsuperuser コマンド実行時に入力受付を行うフィールドの指定
-
EMAIL_FIELD に関していえば、このクラス変数に指定されるフィールドはメールの送信先として利用されるため、当然メールアドレスとして扱うことのできるフィールドを設定する必要があります。
-
USERNAME_FIELD は前述の通りユーザーを一意に識別するためのフィールドですので、このクラス変数に指定されるフィールドの値は各ユーザーで重複しないようにする必要があります。
- そのため、USERNAME_FIELD に指定するフィールドにおいては、unique オプションを True に設定しておく必要があります。
-
REQUIRED_FIELDS に指定するリストには、USERNAME_FIELD に指定するフィールドを含ませてはいけません。
- REQUIRED_FIELDS = []でも問題ない
-
ただし、USERNAME_FIELD に email を指定しただけだと、実はアプリ動作時やマイグレーション時にエラーが発生することになります。
- emailフィールドのunique オプションに True が設定されていないため、USERNAME_FIELD に email を指定すると下記のようなエラーが発生することになります。
- email = models.EmailField(_("email address"), unique=True)
- blank=True を削除し、代わりに unique=True を指定するようにしています(blank=True を削除することで入力必須フィールドとなる)。
- emailフィールドのunique オプションに True が設定されていないため、USERNAME_FIELD に email を指定すると下記のようなエラーが発生することになります。
カスタムユーザの作成手順
- 一旦 AbstractUser(AbstractBaseUser ではなく)の定義を models.py にコピペしてカスタムユーザーモデルとして定義し、不要なフィールドやメソッドの削除と必要なフィールドやメソッドの追加を行なっていくのが無難
- AbstractUser をコピペする上で注意すべき点は、class Meta における abstract = True の行を削除(or コメントアウト)する必要があるところになります。
- abstract = True が設定されていると、抽象モデル(クラス)とみなされて実体が作成されません。なので、そのモデルを参照した際に「そんなモデルは存在しないよ」といったエラーが発生することになります。
- AbstractUser をコピペする上で注意すべき点は、class Meta における abstract = True の行を削除(or コメントアウト)する必要があるところになります。
from django.db import models
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.core.mail import send_mail
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)
- settings.pyに以下の環境変数を追加
- デフォルトのモデルに、Userモデルではなく、作成したCustomUserモデルを設定する
AUTH_USER_MODEL = 'accounts.CustomUser'
- マイグレーション
- このマイグレーションによってデータベースに login_app.customuser というテーブルが作成されることになります。ポイントは、この login_app.customuser テーブルには、CustomUser から削除した username 等の列が存在しない点になります。
% python manage.py makemigrations
% python manage.py migrate
- スーパーユーザの作成
- 作成するスーパーユーザーのメールアドレスとパスワード(確認用も含めて2回)を入力すれば、スーパーユーザーの作成に成功するはずです。
- USERNAME_FIELD を username とは異なるフィールドに設定したため& REQUIRED_FIELDS に指定するリストに username を含ませていないため、ユーザー名の入力受付が行われないようになっています。
- ちなみに、UserManager の変更を行わなかった場合、createsuperuser コマンド実行時には下記のようにエラーが発生することになります。
- 作成するスーパーユーザーのメールアドレスとパスワード(確認用も含めて2回)を入力すれば、スーパーユーザーの作成に成功するはずです。
% python manage.py createsuperuser
- Django管理サイトに登録
- カスタムユーザーを管理画面から管理できるようにするためには、admin.py で admin.site.register を実行してカスタムユーザーを登録することが必要になります。
from django.contrib import admin
from django.contrib.auth import get_user_model
CustomUser = get_user_model()
admin.site.register(CustomUser)