今回のお題
今回はdjangoのCustomUserについてまとめます。
CustomUserとは、djangoのユーザー管理機能を自分のプロジェクト用にカスタマイズすることを言います。
djangoにはallauthというユーザー認証パッケージがありますが、ここにフィールドを追加したりバリデーションの条件を変更したりすることで、より実践的なアプリになるというわけです。
なお、今回はallauth自体の導入手順は省略します。
そちらに関しては以前の記事をご参照ください。
目次
- はじめに
- settings.pyの編集
- CustomUserModelの作成
- CustomUserFormの作成
- adapterの作成
- ルーティング、viewsの作成
- テンプレートの作成
- マイグレーション
- 終わりに
はじめに
CustomUserの実装に取り掛かかる前にまず、最も大事なことを2つ話しておきます。
まず一つ目は、
フィールドの追加などをしない場合であっても、とりあえずはCustomUserを作成しておいた方が良い
ということです。
そもそもCustomUserの実装とは具体的に何なのかというと、DBが参照するUserモデルやUserFormをデフォルトのものから自分で定義したもの(CustomUser)に切り替えるという作業になります。
もしデフォルトのUserモデルやUserFormを参照した状態で開発を進めたあとで何らかの事情によりフィールドやバリデーションの追加が必要になったときに、CustomUserを作って参照先をそちらに切り替えて、というのは非常に手間がかかります。
しかしあらかじめCustomUserを参照するようにしておけば、後から必要になったフィールドなりバリデーションなりを追加するだけですみます。
この違いは非常に大きいので、基本的には既存のUserモデルで事足りる場合であってもとりあえずCustomUserを作成しておくことになっているというわけです。
大事なことの二つ目は、
CustomUserの実装が完了するまではマイグレーションを実行しない
という
ことです。
一つ目の注意事項とも関連しますが、一度マイグレーションを実行した時点でDBの参照先が既存のUserモデルに固定されます。
これを後からCustomUserに切り替えるのは骨が折れるため、中途半端な状態でマイグレーションをするなとあちこちのサイトで書かれています。
では、前置きはこれぐらいにして本題に入ります。
やるべきことは先述の通りでCustomUserとCustomUserFormを作りそちらを参照できるようにすることです。
settings.pyの編集
まずはsettings.pyに以下の用に追記してください。
このようにすることで、DBが参照するモデルやサインアップ時に利用するフォームを既存のものから切り替えることができます。
# CustomUserの利用
# 値は"ユーザー管理用アプリ名.自分で作成するユーザーモデル"
AUTH_USER_MODEL = "accounts.CustomUser"
# サインアップフォームの指定
# こちらは"アプリ名.forms.モデルフォーム名"なので注意
ACCOUNT_FORMS = {
"signup": "accounts.forms.CustomUserCreationForm",
}
# アダプターの指定
ACCOUNT_ADAPTER = 'accounts.adapter.AccountAdapter'
アダプターはサインアップ機能の実装に必要になります。
参照先が指定できたら、次はCustomUserモデルを作ります。
CustomUserモデルの作成
CustomUserモデルを作成する際には、既存のAbstractUserモデルかAbstractBaseUserモデルを継承します。
ただし後者はほとんど1からモデルを定義していく形になり、拡張性と引き換えに手軽さが犠牲になっています。
前者では自由度が足りないという場合には導入の余地があると思いますが、優先順位は低いのではないでしょうか。
今回もAbstractUserの方を使います。
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.core.validators import MinValueValidator, MaxValueValidator
class CustomUser(AbstractUser):
# フィールドを追加しない場合はpassでOK
# pass
# フィールド追加がある場合はそれを記述
age = models.IntegerField(
verbose_name="年齢",
null=Falser,
validator=[
MaxValueValidator(150, "150以下の数字で入力してください"),
MinValueValidator(0, "0以上の数字で入力してください"),
]
)
これでCustomUserが作れたので、次にformを作ります。
CustomUserFormの作成
今回は、サインアップとユーザー情報編集の2つのフォームを作ります。
djangoにはあらかじめUserCreationForm, UserChangeFormという二つのフォームが用意されているので、そちらを継承してCustomFormを作成していきます。
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
class CustomUserCreationForm(UserCreationForm):
def __init__(self, *args, **kwargs): # emailの登録を必須に変更
super().__init__(*args, **kwargs)
self.fields["email"].required = True
class Meta:
model = get_user_model()
fields = ["username", "email", "age"] # passwordは設定しない
labels = {
"username": "ユーザー名",
"email": "メールアドレス",
"age": "年齢",
}
help_texts = {
"username": "",
"email": "",
"age": "",
}
class CustomUserChangeForm(UserChangeForm):
def __init__(self, *args, **kwargs): # emailの登録を必須に変更
super().__init__(*args, **kwargs)
self.fields["email"].required = True
class Meta:
model = get_user_model()
fields = ["username", "email", "age"] # passwordは設定しない
labels = {
"username": "ユーザー名",
"email": "メールアドレス",
"age": "年齢",
}
help_texts = {
"username": "",
"email": "",
"age": "",
}
以下、個別解説です。
CustomUserCreationFormで入力するフィールドの中にパスワードは入れないでください。
パスワードフォームに関しては別で管理されているらしく、フォームのフィールドとして改めて設定すると入力欄が二つになります。
fields = ["username", "email", "age"] # passwordは設定しない
また、CustomUserChangeFormについてはパスワードをフィールドに含めても以下の用に表示されるだけなので、含めるかどうかはお好みで良いかと思います。
また、今回はemailの登録を必須に変更しました。
デフォルトのUserモデル、UserFormではemailなどのいくつかのフィールドは必須とはなっていないので、そこを必須化したい場合には以下の用に編集する必要があります。
def __init__(self, *args, **kwargs): # emailの登録を必須に変更
super().__init__(*args, **kwargs)
self.fields["email"].required = True
上記のCustomFormについてはmodels.pyには記述せず、CustomUserとはファイルを分けるようにしてください。
同じファイルになっているとマイグレーションが失敗する恐れがあります。
詳しくは以下の参考記事をご覧ください。
adapterの作成
次に、adapterというものを作成します。
adaptorとは、ユーザー新規登録時に呼び出されるsave_userメソッドを定義しているファイルになります。
新しく追加したageというフィールドはsave_userメソッドで保存されるようになっていないので、ここを上書きして年齢を登録できるようにします。
settings.pyを編集した際にアダプターの参照先も変更しているので(以下)、
# アダプターの指定
ACCOUNT_ADAPTER = 'accounts.adapter.AccountAdapter'
accounts/adopter.pyにAccountAdapterクラスを作成すればユーザー登録の際にはそちらが参照されるようになります。
from allauth.account.adapter import DefaultAccountAdapter
class AccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit):
user = super().save_user(request, user, form, commit=False)
user.age = form.cleaned_data.get("age")
user.save()
なお、上記で用いているcleaned_dataについてはこちら
引数のcommitはsave_userメソッドを用いてインスタンスに属性値をセットした後にそのまま保存まで行うかどうかを決める値です。
今回はageフィールドに年齢を設定するまでは保存を行いたくない(保存が実行されるとバリデーションエラーになる)のでcommit=falseに設定しています。
commit=false
についての解説は以下をご覧ください。
サインアップについてはテンプレートとビューは予め用意されているので、このadapterの追加を以て実装完了となります。
ユーザー情報の変更についてはこの後ビューとテンプレートを実装します。
ルーティング、viewsの作成
urlpatterns = [
path("edit/<int:pk>/", views.edit, name="accounts_edit"),
]
def edit(request, pk):
target_user = User.objects.filter(id=pk).first()
if request.method == "GET":
form = CustomUserChangeForm(instance=target_user)
return render(request, "accounts/edit.html", { "target_user": target_user })
elif request.method ==POST:
form = CustomUserChangeForm(request.POST, instance=target_user)
if form.is_valid:
form.save()
return render(request, "home.html")
else:
return render(request, "accounts/edit.html", { "target_user": target_user })
テンプレートの作成
ビューから渡されたフォームを表示するだけなので、以下のように書くだけです。
<form>
{{ form }}
</form>
マイグレーションの実行
ここまで来たらマイグレーションを実行しましょう。
% python manage.py makemigrations accounts
% python manage.py migrate
なお、マイグレーションが失敗するという方は以下の記事をご参照ください。
終わりに
これでCustomUserが実装できました。
ただ、フォームが汚いのが気になるので何とかしたいですね・・・