Djangoでカスタムユーザーを作ってみよう【AbstractUser】

Djangoでオリジナルのカスタムユーザーを作ってみましょう。カスタムユーザーを作る方法は大きく分けて2つあるのですが、今回は簡単な(ただし自由度は低い)AbstractUserを継承する方法について解説します。

目次

Djangoのユーザーモデルの構造

Djangoには標準で次のような構造のUserモデルが用意されています。

Djangoの標準Userモデルの構造

認証情報を持つAbstractBaseUserモデルとグループや権限情報を持つPermissionsMixinモデルをそれぞれ継承し、そこに一般的なユーザー情報を加えたAbstractUserモデルをさらに継承しているのがDjango標準のUserモデルです。

これでも一般的な機能は満たしているのですが、ユーザー管理モデルはアプリやプロジェクトに応じて変更したくなるもの。

標準で用意されているUserモデルはカスタマイズすることを想定していないため、後から変更するにはかなり大変で、Djangoの公式でも標準のUserモデルを使用することは非推奨としています。

そのためDjangoでユーザー管理機能を持たせたアプリを作る際は次の図のようにオリジナルのカスタムユーザーモデルを作成して使用することが推奨されています。

AbstractUserを継承して作成したカスタムユーザーモデル

ちなみにAbstractUserモデルがそうであるように、AbstractBaseUserモデルとPermissionsMixinモデルを継承すればより自由度の高いカスタムユーザーモデルを作ることができますが、今回はより簡単な(ただし自由度は低い)AbstractUserモデルを継承する方法で進めていきます。

Djangoでカスタムユーザーを作ってみよう【AbstractBaseUser】

カスタムユーザーを利用するプロジェクトとアプリの作成

カスタムユーザーの作成と利用を試してみるためのプロジェクトと簡単なアプリを用意しましょう。

Djangoプロジェクトの作成

この記事では学習のために新たなプロジェクトとアプリケーションを用意します。

カスタムユーザーモデルを作成して既存のモデルの代わりに使用するため、既存のプロジェクトでは標準のUserモデルを外部キーとして使用している場合、それらをすべて変更する必要があるためです。

WindowsPowerShellの場合、次のコマンドでプロジェクトディレクトリを作成、仮想環境を作成して有効化し、Djangoプロジェクトを作成します。

mkdir customuser-project
cd customuser-project
py -3 -m venv venv
venv\Scripts\Activate.ps1
python -m pip install Django
django-admin startproject config .

アプリケーションの作成と登録

カスタムユーザーを利用するアプリを作成しましょう。

python manage.py startapp customuser_app

作成したアプリをプロジェクトに登録しておきましょう。

settings.pyのINSTALLED_APPSに次のように記述します。

INSTALLED_APPS = [
    'customuser_app', # 追加
.
.
.
]

カスタムユーザーの作成

それでは実際にカスタムユーザーを作成してみましょう。

今作成したcustomuser_appアプリのmodels.pyに次のように記述します。

from django.db import models
from django.contrib.auth.models import AbstractUser
from datetime import date

class CustomUser(AbstractUser):
    bio = models.TextField(blank=True, null=True)
    location = models.CharField(max_length=50, blank=True, null=True)
    birth_date = models.DateField(blank=True, null=True)

    def get_age(self):
        if self.birth_date:
            today = date.today()
            return today.year - self.birth_date.year - ((today.month, today.day) < (self.birth_date.month, self.birth_date.day))
        return None

AbstractUserを継承したCustomUserモデルを作成し、新たに

  • bio(自己紹介)
  • location(場所)
  • birth_date(生年月日)

の3つのフィールドと、birth_dateから年齢を計算して返すget_ageメソッドを追加しました。

フィールドはデフォルトでは blank オプションが False になっているため、入力必須のフィールドとして作成されます。

必須にしたくない場合は blank=True を設定しておきましょう。

なお、入力必須になっている場合も createsuperuser コマンドでユーザーを追加する場合は追加したフィールドの入力欄が出てこないため、createsuperuser コマンドでのユーザーの追加が必ず失敗するようになります。

createsuperuser コマンドでも入力できるようにするには CustomUserモデルに次のように REQUIRED_FIELDS を記述し、入力必須の項目を記述しておきましょう。

class CustomUser(AbstractUser):
    bio = models.TextField(blank=True, null=True)
    location = models.CharField(max_length=50, blank=True, null=True)
    birth_date = models.DateField(blank=True, null=True)

    REQUIRED_FIELDS = ["email", "bio", "location", "birth_date"] # 追加

※ただし、REQUIRED_FIELDS に USENAME_FIELD や password を含めるとエラーになるので注意しましょう。

認証に使用するモデルを変更

続いて認証に利用するユーザー管理モデルを今作成した CustomUser に変更します。

settings.pyに次のように追記します。

AUTH_USER_MODEL = 'customuser_app.CustomUser'

このように記述することで認証に CustomUser が使用されるようになり、マイグレーション実行時にデータベースに customuser_app_customuser というテーブルが作成されます。

マイグレーション

ではマイグレーションを行います。

customuser-project ディレクトリで次のコマンドを実行します。

python manage.py makemigrations
python manage.py migrate

ここでエラーが発生する場合、標準のUserモデルを外部キーとして使用していたり、AUTH_USER_MODELの記述が抜けていないかチェックしてみて下さい。

スーパーユーザーの追加

管理用のスーパーユーザーを追加します。

customuser-project ディレクトリで次のコマンドを実行します。

python manage.py createsuperuser

カスタムユーザーを管理画面に登録

作成したカスタムユーザーを管理画面で管理できるようにしましょう。

admin.py を次のように修正します。

from django.contrib import admin
from customuser_app.models import CustomUser

admin.site.register(CustomUser)

Djangoの開発用テストサーバーを起動して管理画面を確認してみましょう。

次のコマンドを実行します。

python manage.py runserver

Webブラウザで次のURLにアクセスして管理画面を開き、今作成したスーパーユーザーでログインしてみましょう。

http://127.0.0.1:8000/

管理画面に CUSTOMUSER_APP が追加されています

管理画面に CUSTOMUSER_APP が追加されています。クリックしてみましょう。

管理画面でユーザーの一覧を確認・追加・変更できるようになっています

管理画面でユーザーの一覧を確認・追加・変更できるようになっています。ユーザー名をクリックしてみましょう。

ユーザーの詳細情報を確認・変更できます

ユーザーの詳細情報を確認・変更できます。ただ、標準のUserモデルを使った時と違って管理画面からパスワードが変更可能になってしまっています。

このままだとセキュリティ的によくないので、標準のUserモデルを使った時と同じように管理画面でパスワードがハッシュ値として表示され、直接編集できないようにしてみましょう。

admin.py を次のように修正します。

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as DefaultUserAdmin
from django.contrib.auth.forms import UserChangeForm
from customuser_app.models import CustomUser

class CustomUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = CustomUser

class CustomUserAdmin(DefaultUserAdmin):
    form = CustomUserChangeForm
    add_form = DefaultUserAdmin.add_form
    model = CustomUser
    fieldsets = DefaultUserAdmin.fieldsets + (
        (None, {'fields': ('bio', 'location', 'birth_date')}),
    )

admin.site.register(CustomUser, CustomUserAdmin)

CustomUserChangeFormという新しいフォームクラスを定義し、そのmodel属性をCustomUserモデルに変更。

次に、CustomUserAdminという新しい管理クラスを定義しています。これはDjangoのデフォルトのUserAdminを継承し、そのform属性をCustomUserChangeFormに変更。fieldsets をオーバーライドしてCustomUserモデルで新しく追加したフィールドを表示するようにしています。

最後にadmin.site.registerでCustomUserモデルとCustomUserAdmin管理クラスを関連付けることにより、管理画面からCustomUserモデルのオブジェクトを操作する際にはCustomUserAdminの設定が使用されるようにしています。

では、これらの変更を行ったうえでもう一度管理画面からユーザーの詳細データを見てみましょう。

管理画面でパスワードがハッシュ値として表示され、直接編集できないようになりました

これで標準のUserモデルを使った時と同じように、管理画面でパスワードがハッシュ値として表示され、直接編集できないようになりました。

また、グループやユーザーパーミッションなどの表示も標準のUserモデルを使った時と同じようになりました。

ルーティング

では実際にこのカスタムユーザーをフォームやビューから利用してみましょう。

次の3画面を実装します。

  • サインアップページ(カスタムユーザーを追加するページ)
  • ログインページ
  • ログインユーザーのプロフィールを表示するページ

まずはルーティングを記述します。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('customuser_app/', include('customuser_app.urls')) # 追加
]

次にアプリ内のルーティングです。customuser_appディレクトリにもurls.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('profile/', views.profile_view, name='profile'),
]

サインアップページ、ログインページ、プロフィールページの3つを作ります。

ビュー

ビューを作成します。

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
from datetime import datetime

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='/customuser_app/profile/')

    else:
        form = SignupForm()

    param = {
        'form': form
    }

    return render(request, 'customuser_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='/customuser_app/profile/')

    else:
        form = LoginForm()

    param = {
        'form': form,
    }

    return render(request, 'customuser_app/login.html', param)

@login_required
def profile_view(request):
    user = request.user

    params = {
        'age': user.get_age(),
    }

    return render(request, 'customuser_app/profile.html', params)

あくまでカスタムユーザー制作の記事用なのでいろいろ省略していますが、サインアップビューとログインビューは特筆することもないでしょう。

settings.py の AUTH_USER_MODEL で今回作成したカスタムユーザーモデルを指定しているのでrequest のデータ属性 user はカスタムユーザーのインスタンスとなります。

あとは最後のprofile_viewでカスタムユーザーモデルで定義した get_age() メソッドを使用しています。

フォーム

ビューで使用しているSignupFormとLoginFormを作成しましょう。

forms.pyを作成し次のように記述します。

from django import forms
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from customuser_app.models import CustomUser

class SignupForm(UserCreationForm):
    class Meta:
        model = CustomUser
        fields = [CustomUser.USERNAME_FIELD] + CustomUser.REQUIRED_FIELDS + ['password1', 'password2']

class LoginForm(AuthenticationForm):
    pass

SignupFormのmodelに今回作成したCustomUserを指定、fieldsにはカスタムユーザーの作成時に必要な情報を指定しておきましょう。

特に入力必須のフィールドを追加した場合はこのfieldsに指定しておかなければユーザーが入力必須のフィールドを入力することができません。

入力必須のフィールドをCustomUserモデルのREQUIRED_FIELDSに記述しておけば、CustomUser.REQUIRED_FIELDSでまとめて指定することができます。

LoginFormは継承元のAuthenticationFormをそのまま使用します。

テンプレート

あとはページを表示するテンプレートファイルを作成します。

まずはsettings.pyを開きテンプレートファイルの場所を指定します。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'], # テンプレート用のフォルダを追記
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

これでDjangoがプロジェクトディレクトリ(customuser-project)直下のtemplatesディレクトリからテンプレートファイルを探してくれるようになりました。

ではcustomuser-projectディレクトリにtemplatesディレクトリを作成し、その中にcustomuser_appディレクトリを作成、その中に次の3つのテンプレートファイルを作成します。

{% 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>{{ user }}のプロフィール</title>
</head>
<body>
  <h1>{{ user }}のプロフィール</h1>
  <p>Bio: {{ user.bio }}</p>
  <p>Location: {{ user.location }}</p>
  <p>Birth date: {{ user.birth_date }}</p>
  <p>Age: {{ age }}</p>
</body>

</html>

動作確認

では、PowerShellでcustomuser-projectディレクトリに移動し、Djangoの開発用テストサーバーを起動しましょう。

python manage.py runserver

Webブラウザで http://127.0.0.1:8000/customuser_app/signup/ にアクセスします。

カスタムユーザーモデルで追加したフィールドを入力できるユーザー登録フォーム

このようにカスタムユーザーモデルで追加したフィールドを入力できるユーザー登録フォームが表示されます。

実際に項目を入力して「登録」ボタンを押してみましょう。

カスタムユーザーモデルを使ったユーザーのプロフィールページ

登録が完了し、ユーザーのプロフィールページに自動遷移します。

さきほど入力したbio, location, birth_date のほか、birth_date から年齢を計算して表示してくれています。

ログインページも確認しておきましょう。http://127.0.0.1:8000/customuser_app/login/ にアクセスします。

ログインページ

ログイン中かどうかによって処理を分けていないので、ログイン中でもログインページが表示されます。

試しにログインしてみましょう。

カスタムユーザーモデルを使ったユーザーのプロフィールページ

ログインが完了し、ユーザーのプロフィールページに自動遷移します。

まとめ

このようにDjangoではAbstractUserを継承することで簡単にカスタムユーザーを作成することができます。(まぁ公式がカスタムユーザーの利用を推奨しているわけですし)

ただしカスタムユーザーを使用する際は settings.py や admin.py などもそれに合わせて変更する必要があることは把握しておきましょう。

また、標準のUserモデルを使用していると、アプリを運用開始後にユーザーモデルをカスタマイズしたくなった場合になかなか面倒なことになります。

そのためDjangoで実際に運用するアプリケーションを開発する際はなるべく早い段階、できれば一番最初にカスタムユーザーモデルの作成から始めた方が良いでしょう。

カスタムユーザーモデルのフィールドを後から変更するぶんには標準のUserモデルから変更するよりはかなりマシなので、最低限カスタムユーザーモデルの作成と設定だけは最初にやっておきたいところです。

また、今回解説したAbstractUserではなく、そのさらに元となるAbstractBaseUserモデルとPermissionsMixinモデルを継承することでさらに自由度の高いカスタムユーザーモデルの作成が可能となることも知っておくと役立つ時がくるかもしれません。

Djangoでカスタムユーザーを作ってみよう【AbstractBaseUser】

このエントリーをはてなブックマークに追加

コメントを残す

頂いたコメントは一読した後表示させて頂いております。
反映まで数日かかる場合もございますがご了承下さい。