MENU

DjangoのCustomUserを設定する

Django CustomUser

今回は、DjangoでCustomUserの設定をしていきます。
Djangoのカスタムユーザーの設定が推奨されていることを知り、学び直しましたので、備忘録も兼ねてまとめていきます。

目次

Djangoの開発環境の構築

今回は、DockerでDjangoの環境を用意します。
Dockerを使用した環境の用意はこちらの記事でまとめていますので、参考にしてください。

Djangoの開発環境が整ったら各ファイルを作成していきます。

CustomUserの設定の推奨

DjangoでWebアプリケーションを開発する場合、Djangoにデフォルトで用意されているUserモデルを使用してユーザー管理をすることができますが、Djangoの公式ドキュメントによると、プロジェクトの開始後、かつ、初回のマイグレーションの前にデフォルトのUserモデルをオーバーライドした独自のCustomUserを設定することが推奨されています。

Djangoの公式ドキュメントによると、まずプロジェクトのsettings.pyで以下のようにオーバーライドの設定を記述します。

AUTH_USER_MODEL = 'myapp.MyUser'

これにより、models.pyに定義した独自ユーザーモデルによりデフォルトのAUTH_USER_MODELをオーバーライドします。

また、models.pyに定義するUserモデルの最小構成の例として、以下のような設定が公式ドキュメントでは示されています。

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

この設定を追記することで、Djangoに搭載されているデフォルトのUserモデルと同様に動作し、かつ将来的に個別の設定が必要になった場合にカスタマイズが可能なCustomUserモデルを設定することができます。

また、設定したモデルについては、admin.pyにおいて読み込む必要があります。

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)

admin.site.register(User, UserAdmin)について

admin.site.registerの第1引数(ここではUser)は、管理画面で実際に管理を行うユーザーモデルを指定します。これにより、管理画面で第1引数に指定したモデルのインスタンスを管理することができるようになります。

また、admin.site.register の第2引数(ここではUserAdmin)は、管理画面を表示する時に使用するクラスを指定します。これにより、管理画面が第2引数に指定したクラスに応じた画面に変化します。

・第2引数を指定をしなかった場合はデフォルトのModelAdmin が自動で指定されます。
・第2引数に指定するクラスは、ModelAdmin以外の独自クラスを指定することも可能です。

このように admin.site.register を記述することで、UserAdmin クラスによって作成される管理画面で User のインスタンスを管理することができるようになります。

この仕組みを使用して、DjangoのCustomUserを作成していきます。

CustomUserの作成

それでは実際にCustomUserの設定をしていきます。

こちらの記事で作成したDjangoプロジェクトができている前提で、DjangoのUser管理のアプリケーションであるcustomuserアプリケーションを作成します。

docker-compose run backend[ここはdockerのコンテナ名] python manage.py startapp customuser[任意のアプリ名]

これにより作成されたアプリケーションをプロジェクトディレクトリのsettings.pyで読み込みます。

INSTALLED_APPS = [
    'customuser.apps.CustomuserConfig', # add
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

次に、models.pyを編集していきます。
今回のmodels.pyでは、Djangoの公式ドキュメントとは異なり、admin.site.registerの第2引数に指定するUserAdminクラスをデフォルトのUserAdminクラスではなく、UserManagerクラスを作成し、この作成したクラスを設定していきます。

UserManagerのコード全体はこちら

from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager

class UserManager(BaseUserManager):
  use_in_migrations = True
  
  def _create_user(self, email, password, **extra_fields):
    if not email:
      raise ValueError("メールアドレスは必須項目です。")
    email = self.normalize_email(email)
    
    user = self.model(email = email, **extra_fields)
    user.set_password(password)
    user.save(using = self._db)
    return user
  
  def cretate_user(self, email, password = None, **extra_fields):
    extra_fields.setdefault("is_staff", True)
    extra_fields.setdefault("is_superuser", False)
    return self._create_user(email, password, **extra_fields)
  
  def create_superuser(self, email, password, **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("管理者の場合は「is_staff」を有効にしてください。")
    if extra_fields.get("is_superuser") is not True:
      raise ValueError("管理者の場合は「is_superuser」を有効にしてください。")
    
    return self._create_user(email, password, **extra_fields)

UserMangerクラスについて

DjangoのGithub(models.py)では、Djangoがデフォルトで設定しているcreate_user関数またはcreate_superuser関数がありますが、デフォルトではusernameemailpasswordを使用して関数を実行するようにしています。

しかし、今回のCustomUserクラスの定義にあたり、ユーザーの新規作成時にはusernameは必須とせず、多くのWebサイトで用いられているemailpasswordによるユーザー作成の形を取ることとするため、独自のUserManagerを定義します。

ベースとなっているのは、DjangoのGithub(models.py)です。

UserManagerでは、
_create_user関数
create_user関数
create_superuser関数
の3つの関数が定義されています。
このうち「_create_user関数」については、Djangoのmodels.pyでも定義されていますが、先頭についている_(アンダースコア)は「内部関数」を意味し、内部的にのみ使用(今回定義するUserManagerの中でのみ使用)される関数という取り扱いになります。

use_in_migration = Trueについて

これは、公式ドキュメントでも記載がありますが、今回設定するクラスをRunPythonで操作できるようにするための設定です。

_create_user関数について

def _create_user(self, email, password, **extra_fields):
    if not email:
      raise ValueError("メールアドレスは必須項目です。")
    email = self.normalize_email(email)
    
    user = self.model(email = email, **extra_fields)
    user.set_password(password)
    user.save(using = self._db)
    return user

これは、デフォルトのUserManagerで定義されているusernameでのユーザー作成をemailによるユーザー作成に作り替えているものです。

引数には、email(バリデーションの設定もしています)、password、その他のフィールである**extra_fieldを指定し、外部から受け取った値をそれぞれ保存して、ユーザーを作成(dbに登録)しています。

create_user関数、create_superuser関数について

def cretate_user(self, email, password = None, **extra_fields):
    extra_fields.setdefault("is_staff", True)
    extra_fields.setdefault("is_superuser", False)
    return self._create_user(email, password, **extra_fields)
  
  def create_superuser(self, email, password, **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("管理者の場合は「is_staff」を有効にしてください。")
    if extra_fields.get("is_superuser") is not True:
      raise ValueError("管理者の場合は「is_superuser」を有効にしてください。")
    
    return self._create_user(email, password, **extra_fields)

こちらもベースの設定はUserManagerと同じで巣が、デフォルトではusernameを各関数の引数に指定していますが、今回はemailでのユーザー作成をする設定とすることからusernameは引数から削除しています。

ちなみに、create_user関数とcreate_superuser関数は内部においてのみ使用することとしている_create_userを呼び出すようになっています。

デフォルトのUserManagerとの相違点は異常になります。

CustomUserクラスの定義

UserManagerが設定できたところで、続けてCustomUserクラスについても作成します。

CustomUser全体のコードはこちらです。

from django.db import models
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from django.core.mail import send_mail

import uuid

class CustomUser(AbstractBaseUser, PermissionsMixin):
  username_validator = UnicodeUsernameValidator()
  
  uuid = models.UUIDField(
    default = uuid.uuid4,
    primary_key = True,
    editable = False 
  )
  username = models.CharField(
    "ユーザー名",
    max_length = 150,
    unique = True,
    help_text = "150文字以内で文字や数字を使うことができます(記号は「@/./+/-/_」のみ使用可能)。" ,
    validators = [username_validator],
    error_messages = {
      "unique": "このユーザー名はすでに登録済みです。"
    }
  )
  first_name = models.CharField("氏", max_length = 150, blank = True)
  last_name = models.CharField("名", max_length = 150, blank = True)
  email = models.EmailField("E-mailアドレス", unique = True, blank = False)
  
  is_staff = models.BooleanField(
    "staff status",
    default = False,
    help_text = "この管理サイトにユーザーがログインできるかどうかを指定します。"
  )
  is_active = models.BooleanField(
    "active",
    default = True,
    help_text = "このユーザーをアクティブとして扱うかどうかを指定する。"
  )
  
  date_joined = models.DateTimeField("date joined", default = timezone.now)
  
  objects = UserManager()
  
  EMAIL_FIELD = "email"
  USERNAME_FIELD = "email"
  REQUIRE_FIELDS = ["email"]
  
  class Meta:
    verbose_name = "ユーザー"
    verbose_name_plural = "ユーザー"
  
  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)

username_validator = UnicodeUsernameValidator()について

これはユーザーネームを登録する際に、使用できる文字等のチェックを行う機能です。
Djangoがデフォルトで提供しているバリデーションです。

Djangoのデフォルトのusernameの説明文では、「半角アルファベット、半角数字、@/./+/-/_ で150文字以下にしてください。」とされています。
しかし、実際はunicodeが許容される仕様に変更されており、日本語での登録もできる状態です。
つまり、UnicodeUsernameValidatorはデフォルトの説明文とは一致していませんが、実態には合っていることになります。
これは運用上の方針に合わせて設定することがよいでしょう。

uuidの使用について

今回は、ユーザー登録の際に、自動でuuidを割り当てる設定にしています。

uuidとは?
uuid(Universally Unique IDentifier)は、世界で唯一のIDを割り当てる仕組みです。

デフォルトではuuid.uuid4を指定し、割り当てられたuuidprimary_keyとしています。
また、uuidは世界で唯一のIDを割り当てているので容易に変更されては困るため、editableFalseとしています。

【その他のフィールド、関数について】
・その他のusernameからdate_joinedまでのフィールドはDjangoのAbstractUserと同一のフィールド設定です。
cleanからemail_userの関数についてもAbstractUserと同様の内容です。

objects = UserManager()

objects = UserManager()については、最初に定義したUserManagerをDjangoのデフォルトのBaseUserManagerの代わりに使用するよう設定するための設定です。

USERNAME_FIELD = “email”

USERNAME_FIELDは、Djangoで使用される認証フィールドを指します。
デフォルトではusernameになっていますが、今回の設定では、emailpasswordによるログインを設定しているので、この認証フィールドはemailに変更しています。

REQUIRE_FIELDS = [“email”]

REQUIRE_FIELDSは、一般ユーザーまたはスーパーユーザーの作成時の必須項目を設定します。
デフォルトではusernameとなっていますが、今回のログイン設定を踏まえて、この設定もemailを追加しています。

これで、CustomUserの設定は完了です。

settings.pyで認証モデルを読み込む

作成したCustomUserモデルは、このままでは使用できません。

Djangoが作成したCustomUserモデルを使用するように認識していないためです。

そこで、Djangoのプロジェクトディレクトリのsettings.pyに以下の1行を追記します。

AUTH_USER_MODEL = 'customuser.CustomUser'

これにより、Djangoが認証モデルとして、作成したCustomUserモデルを使用するように宣言することになります。

デフォルトの管理画面を使う場合の追加設定

Djangoのアプリケーションを作成する場合、デフォルトの管理画面を使用することが多いと思います。

この管理画面において、先ほど作成したCustomUserモデルを使用するには、少し設定が必要ですので、その設定をしていきます。

admin.pyの編集

まずは、全体のコードを表示します。

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _
from .models import CustomUser
from .forms import MyUserChangeForm, MyUserCreationForm

class MyUserAdmin(UserAdmin):
  fieldsets = (
    (None, {"fields": ("email", "password")}),
    (_("Personal info"), {"fields": ("username", "first_name", "last_name")}),
    (_("Permissions"), {
      "fields": (
        "is_active",
        "is_staff",
        "is_superuser",
        "groups",
        "user_permissions"
      )
    }),
    (_("Important dates"), {"fields": ("last_login", "date_joined")}),
  )
  
  add_fieldsets = (
    (None, {
      "classes": ("wide",),
      "fields": ("email", "password1", "password2"),
    }),
  )
  
  form = MyUserChangeForm
  add_form = MyUserCreationForm
  
  list_display = ("email", "username", "is_staff")
  list_filter = ("is_staff", "is_superuser", "is_active", "groups")
  search_fields = ("email", "username")
  ordering = ("email",)
  
admin.site.register(CustomUser, MyUserAdmin)

基本的にはDjangoのdjango.contrib.auth.adminに定義されているUserAdminとほとんど同じコードです。

デフォルトのUserAdminではusernameを使用している箇所がありますが、その部分を今回のCustomUserの設定に合わせて、emaiフィールドを表示するよう設定しています。

これにより、管理画面などでusernameが表示されている項目にはemailが表示されるようになります。

form = MyUserChangeForm及びadd_form = MyUserCreationFormは別途ファイルを作成し、それぞれを定義しています。

from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser

class MyUserChangeForm(UserChangeForm):
  class Meta:
    model = CustomUser
    fields = '__all__'

class MyUserCreationForm(UserCreationForm):
  class Meta:
    model = CustomUser
    fields = ('email',)

基本的には、既に設定済みのmodelであるCustomUserをそれぞれのクラスに紐付けており、MyUserChangeFormでは全てのフィールドを編集できるように、MyUserCreationFormはユーザー登録のためのFormになるので、emailフィールドのみを設定しています。

MyUserCreationFormemailフィールドのみを指定していますが、Djangoではパスワードのフィールドがデフォルトで設定されるようです。

また、ordering=("emai")とし、割り当てられたemailフィールドで並び替えができるようになっています。

その他の設定はDjangoの管理画面を確認すると、どの画面の設定になっているかわかるはずです。
デフォルトの管理画面と見比べながら確認してみてください。

作成したCustomUserモデルを反映させる

それでは、作成したCustomUserモデルをDjangoのアプリケーションに反映させます。

Dockerを立ち上げた状態(下記のコマンドを実行した状態)にします。

docker-compose up

そしてこのコマンドが正常に実行されている状態で、別のターミナル(Mac)またはコマンドプロンプト(Windows)から、以下のコマンドを順番に実行します。

docker-compose run backend python manage.py makemigrations
docker-compose run backend python manage.py migrate
docker-compose run backend python manage.py createsuperuser

上記のコマンドによりDjangoのsuperuserが作成できたら完了です。
実際にログイン画面から作成したsuperuserでログインできることを確認してみてください。

最後に

今回はDjangoでCustomUserの設定をしました。
デフォルトのUserを使用して学習をしていましたが、CustomUserの設定が推奨されていることから今回の学習をしてみました。

ユーザーの登録情報の設定や管理画面での表示内容等は適宜調整できそうなので、使いやすい管理画面を設定するためにも、CustomUserの設定は必要になるのではないかと思います。

CustomUserの設定は初回のマイグレーション前に行う必要があることも今回の重要なポイントです。
一度作成したユーザーは変更が難しくなるため、Webアプリケーションの作成に当たっては、初回のマイグレーションまでにCustomUserを設定しておくことをお勧めします。一度設定してしまえば、その後の更新は難しくないでしょう!

次回は、Django NinjaでAPIの作成をしてみます。
今回作成したCustomUserの操作をAPI経由で行うよう設定してみたいと思います。

今回は以上となります。
最後まで読んでいただきありがとうございました。
次回もぜひ読んでいただければと思います。

Django CustomUser

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

はじめまして、ふじです。
Python、Django、FastAPI、React.js、Next.jsを学習している、ずっと文系のプログラミング独学者です。

目次