【Django】管理画面でカスタムユーザーを管理する(UserAdmin)

管理画面でカスタムユーザーを管理する方法の説明ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Django において、管理画面(admin)でカスタムユーザーを管理する方法について解説していきます。

下記ページで解説しているとおり、AbstractUserAbstractBaseUser を継承することで独自のユーザー管理モデルクラスを定義することができます。このページでは、この AbstractUserAbstractBaseUser を継承して定義した独自のユーザー管理モデルクラスのことを「カスタムユーザー」と呼ばせていただきます。

【Django】カスタムユーザー(独自のユーザー)の作り方【AbstractUser編】 カスタムユーザーをAbstractBaseUserを継承して作成する手順の解説ページアイキャッチ 【Django】カスタムユーザー(独自のユーザー)の作り方【AbstractBaseUser編】

さらに、下記ページで解説しているとおり、admin.py の実装により、自身のアプリの models.py で定義したモデルクラスを管理画面で管理できるようにすることもできます。また、ModelAdmin のサブクラスを定義することにより、モデルクラス毎の管理画面(インスタンスの一覧表や追加・編集フォーム)を変更することも可能です。

【Django入門11】管理画面のカスタマイズ(ModelAdmin)

ただし、カスタムユーザーの管理画面での管理に限れば、上記のページで解説している内容だけでは手順が実は不十分です。ModelAdmin のサブクラスではユーザーの管理がうまく行えません。 

このページでは、その理由や、カスタムユーザーを管理画面で管理するための手順について解説していきたいと思います。

カスタムユーザーの管理に ModelAdmin が不十分である理由

まずは、カスタムユーザーの管理に ModelAdmin が不十分である理由について解説していきます。

管理画面とは

まずは復習として、「管理画面」と「管理画面のカスタマイズ」について簡単に説明していきます。

管理画面とは、通常下記の URL からアクセスできるページのことになります(設定変更等により管理画面の URL は変更することも可能です)。

http://localhost:8000/admin/

実際の画面は下図のようなものになります。

管理画面を示す図

管理画面自体の説明や管理画面の使い方については下記ページで解説していますので、詳しくは下記ページをご参照いただければと思います。

Djangoの管理画面の使い方の解説ページアイキャッチ 【Django入門10】管理画面(admin)の使い方の基本

ポイントは、管理画面では各モデルクラスのインスタンスの管理が行えるという点になります。

スポンサーリンク

管理画面のカスタマイズ

ただし、デフォルトでは管理画面で管理できるモデルクラスは auth というアプリから提供される GroupUser のみとなっています。

ですが、admin.py を変更することにより、他のモデルクラスも管理画面で管理できるようになります。さらに、このモデルクラスの管理画面は ModelAdmin というクラスに従って表示されるようになっています。そして、モデルクラス毎の管理画面を ModelAdmin のサブクラスの定義により変更することも可能となっています。

この辺りについては下記ページで詳細を解説していますので、詳しく知りたい方は下記ページを読んでみていただければと思います。

【Django入門11】管理画面のカスタマイズ(ModelAdmin)

ただし、このページの冒頭でも触れたように、カスタムユーザーに関しては、管理画面の表示に ModelAdminModelAdmin のサブクラスを利用しても、基本的には管理が上手くできません。

問題点:ModelAdmin では暗号化が行われない

この決定的な理由の1つはパスワード管理になります。カスタムユーザー等のユーザーを管理するモデルクラスは、認証を行うためにパスワードフィールドを持つようになっています。

そして、このパスワードフィールドの文字列は、セキュリティ面から考えるとデータベースに保存される際には暗号化されている方が望ましいです。

MEMO

簡単のため暗号化と表記していますが、実際には SHA256 というアルゴリズムでハッシュ化が行われることになります

したがって、パスワードを保存する際には、パスワードフィールドの文字列の暗号化が必要となります。これは、カスタムユーザーにおいても、User においても同様です。

パスワードが暗号化されて保存される様子

ですが、ModelAdmin を利用した管理画面でパスワードを保存したとしても、基本的にはパスワードは平文のまま暗号化されずに保存されます。まず、この時点でセキュリティ的にアウトです。

そして、セキュリティだけでなく、平文でパスワードが保存されることは機能的にも問題となります。前述の通り、パスワードはユーザー認証を行う際に利用されます。

例えばログインを行う際にユーザー認証が利用されます。そして、ユーザー認証が行われる際には、ログインフォームのパスワードフィールドに入力された文字列を暗号化した結果と、データベースに保存されているパスワードの比較が行われます。

データベースにパスワードを保存する際に行う暗号化と、ログインフォームに入力されたパスワードの暗号化では同じアルゴリズムが用いられます。そのため、「ログインフォームに入力されたパスワードが正しい」&「データベースに保存されているパスワードが暗号化されている」場合は認証に成功することになります。

保存されたパスワードと入力されたパスワードを暗号化した結果を用いて認証が行われる様子

ですが、そもそもデータベースに保存されているパスワードが平文の場合、ログインフォームに入力されたパスワードが正しくても認証には成功しません。これは、ログインフォームに入力されたパスワードは暗号化されるため、暗号化された文字列と平文の文字列とを利用して認証が行われてしまうことになるからです。

保存されたパスワードの平文と入力されたパスワードを暗号化した結果を用いて認証が行われる様子

逆に、ログインフォームに入力されたパスワードが間違っていても、認証に成功してしまうようなケースもあり得ます。

このように、データベースに保存されるパスワードが平文である点は、認証機能を正常に動作させるという点においても問題になります。

ModelAdminUser を管理した時の動作

せっかくなので、この問題点を実際に確認してみましょう。確認が不要という方は、次の カスタムユーザーを管理する方法 にスキップしていただければと思います。

プロジェクトとアプリの作成

まず、適当な作業フォルダに移動し、下記のコマンドで testmodeladmin プロジェクトを作成してください。

% django-admin startproject testmodeladmin

続いて、下記のコマンドで testmodeladmin フォルダ内に移動し、

% cd testmodeladmin

さらに下記のコマンドで accounts アプリを作成してください。

% python manage.py startapp accounts

models.pyadmin.py の変更

このコマンドの実行によって accounts フォルダが作成され、その中に models.py というファイルが存在するはずですので、このファイルを開いて下記のように変更して保存してください。CustomUser が、このページの題材となっているカスタムユーザーとなります。

models.pyの変更
from django.db import models
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    pass

さらに、accounts フォルダ内には admin.py というファイルが存在するはずですので、このファイルを開いて下記のように変更して保存してください。下記のように admin.site.register を実行することで、CustomUser が管理画面での管理対象として登録されることになります。また、admin.site.register に第2引数を指定していないため、この CustomUser の管理画面は ModelAdmin の定義に基づいて表示されることになります。

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

admin.site.register(CustomUser)

プロジェクトの設定

次に、accounts フォルダと同階層(つまり今いるフォルダ内)に testmodeladmin というフォルダが存在し、その中に settings.py というファイルがあるはずですので、このファイルを開き、まずはINSTALLED_APPS を下記のように変更してください。1行目に 'accounts', を追加してやれば良いだけです。

settings.pyの変更1
INSTALLED_APPS = [
    'accounts',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

さらに、ファイルの末尾に下記を追加して保存してください。

settings.pyの変更2
AUTH_USER_MODEL = 'accounts.CustomUser'

以上により、カスタムユーザー CustomUser が定義され、このモデルクラスによって認証が行われるようになったことになります。さらに、admin.py の変更により、管理画面で CustomUserを管理できるようになったことになります。前述の通り、admin.site.register には第2引数を指定していないため、CustomUser に対する管理画面は ModelAdmin クラスに基づいて表示されることになります。

次は、管理画面を利用するための準備を行なっていきます。

マイグレーションとスーパーユーザの作成

まずは、下記の2つのコマンドを実行してマイグレーションを実行しましょう。

% python manage.py makemigrations
% python manage.py migrate

続いて、下記のコマンドを実行してスーパーユーザーを作成します。ユーザー名とメールアドレスとパスワード2回を入力してください(パスワードやメールアドレスが出鱈目だと妥当でないと判断される可能性もあるので注意してください)。

% python manage.py createsuperuser

ちなみに、スーパーユーザーとは何なのか?という点に関しては下記ページで解説していますので、詳しく知りたい方は読んでみてください。

Djangoの管理画面の使い方の解説ページアイキャッチ 【Django入門10】管理画面(admin)の使い方の基本

また、上記の createsuperuser コマンドではパスワードの入力を行っていますが、このパスワードに関しては暗号化されて保存されるようになっています。平文で保存されるのは、あくまでも管理画面でのユーザー作成時の話になります(さらに ModelAdmin を利用している場合の話)。

スーパーユーザーでのログイン

次に、下記コマンドで開発用サーバーを起動すれば、管理画面を利用できる準備が整ったことになります。

% python manage.py runserver

ということで、次は管理画面を利用していきます。まずはウェブブラウザを起動し、下記の URL を開いてください。

http://localhost:8000/admin/

ログインフォームが表示されるはずですので、先ほど作成したスーパーユーザーのユーザー名とパスワードを入力して Log in ボタンをクリックしてください。

管理画面のログインフォーム

このログイン時には、前述の通りユーザー認証が行われることになります。先ほども説明したように、createsuperuser コマンドで作成したユーザーのパスワードはデータベースに暗号化された状態で保存されるため、正しいパスワードを入力されれば、認証に成功してログインできるはずです。

ログイン後は下図のようなページが表示されると思います(色味などは違うかも…)。

管理画面のトップページ

管理画面でのユーザーの追加

次は、CustomUser のインスタンスの追加を行なってみましょう!Users の右側にある Add リンクをクリックしてください。現状の設定だと、管理画面においては CustomUserUser or Users として表示されています。

すると、CustomUser のインスタンス追加フォームが表示されるはずですので、ここでは下記を入力してページ下部にある SAVE ボタンをクリックしてください(そもそも、この入力時にパスワードが丸見えになっている時点でセキュリティ的に問題があることが確認できると思います…)。

  • Password:適当なもの
  • Superuser status:チェック ON に変更
  • Username:適当なもの
  • Staff status:チェック ON に変更

Staff status と Superuser status にチェックを ON している理由は、追加する CustomUser に管理画面での全管理の権限を与えるためになります。つまり、本来であれば、上記のように設定して作成したユーザーは管理画面へのログインを行うことが可能となるはずです。が、前述の通り、パスワードの暗号化が行われないことが原因でログインできなくなってしまっています。

このログイン不可であることを確認する前に、追加したユーザーの情報を見てみましょう!

追加フォームで SAVE ボタンをクリックした後、画面左側にある Users をクリックすれば CustomUser のインスタンスの一覧が表示されることになります。ここで、先ほど追加したインスタンスのユーザー名をクリックしてみてください。

おそらく、下の図のようにパスワードが平文で表示されていることが確認できると思います。

追加フォームで追加したユーザーのパスワードが平文になっている様子

次は、先ほどの一覧表から createsuperuser で追加したインスタンスのユーザー名をクリックしてください。この場合は、パスワードが意味不明な文字列になっており、パスワードが暗号化されていることが確認できるはずです。

createsuperuserで追加したユーザーのパスワードが暗号化されている様子

このように、現状の管理画面の CustomUser フォームでインスタンスを追加した場合、パスワードが平文で保存されていることになります。流石にパスワードが平文で丸見え状態で保存されているアプリを使いたいとは思えないですよね…。

管理画面で追加したユーザーでのログイン

さらに、平文でパスワードが保存されると認証機能もうまく動作しません。次はログイン時の認証の動作を確認してみましょう!

まず、管理画面のページの上部にある LOG OUT リンクをクリックしてください。これによりログインしていたスーパーユーザーのログアウトが行われます。さらに、リンククリック後に表示されるページの Log in again リンクをクリックしてください。

これにより、再度ログインフォームが表示されることになります。ここでは、先ほど管理画面の追加フォームで追加したユーザーのユーザー名とパスワードを入力して Log in ボタンをクリックしてください。

すると、下の図のようにログインに失敗してしまうことになるはずです。

正しい情報を入力してもログインに失敗する様子

このようにログインに失敗するのは、あなたがパスワードの入力を間違ったことが原因ではなく、インスタンス追加時にパスワードが平文で保存されてしまったことが原因となります。

つまり、ログインできない原因はインスタンスの追加フォームに問題があります。そして、この追加フォームは ModelAdmin に従って表示されているため、結局は ModelAdmin が悪いということになります。悪いというよりも、パスワードを扱うようなモデルクラスの管理画面としては ModelAdmin は不十分ということになります。

スポンサーリンク

カスタムユーザーを管理する方法

ちょっと説明が長くなってしまいましたが、カスタムユーザーの管理に ModelAdmin が不十分である理由の解説は以上となります。

では、カスタムユーザー向けの管理画面はどのようにして実現すれば良いのでしょうか?その点について解説していきたいと思います。

UserAdmin のサブクラスを利用する

結論を言うと、カスタムユーザーを管理するためには ModelAdmin ではなく UserAdmin のサブクラスを利用するようにしてやれば良いです。

つまり、admin.py を下記のように変更してやれば良いことになります。CustomUserAdminUserAdmin のサブクラスとなります。一旦、CustomUserAdmin の中身の定義は pass としていますが、UserAdmin のサブクラスの実装については後述で解説していきます。

CustomUserの管理を行うadmin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser

class CustomUserAdmin(UserAdmin):
    pass

admin.site.register(CustomUser, CustomUserAdmin)

UserAdmin とは

いきなり UserAdmin というクラスが登場しましたが、この UserAdmin とはどのようなクラスなのでしょうか?

一言で言えば、User の管理画面を実現するためのクラスとなります。

前述の通り、通常のモデルクラスの管理画面は ModelAdmin によって実現されることになります。そして、ModelAdmin のサブクラスを定義することで、モデルクラス毎に管理画面を変更することが可能となります。例えば、インスタンスの一覧表示ページやインスタンスの追加・編集フォームの変更を行うようなことができます。

UserAdmin は、まさに、その ModelAdmin のサブクラスの1つとなります。これは auth アプリで定義されているクラスで、User の管理を行うことに特化したクラスとなっています。

ModelAdminとUserAdminの関係性

また、下記ページを読んでくださった方であれば分かると思いますが、管理画面では User のインスタンスの追加を行うことが可能で、User の追加フォームから追加した場合、そのユーザーでのログインが可能となっています(ログインするためにはユーザーの Staff status のチェックを ON しておく必要があります)。

Djangoの管理画面の使い方の解説ページアイキャッチ 【Django入門10】管理画面(admin)の使い方の基本

そして、この User の追加フォームは UserAdmin に従って表示されるものになります。つまり、UserAdmin の追加フォームを利用すれば、ModelAdmin の時とは異なりログイン可能なユーザーを作成することができます。もちろん、パスワード保存時の暗号化も行われますし、パスワード入力時には入力した文字列がマスクされて表示されることにもなります。

パスワードがマスクされる様子

追加フォームだけでなく、UserAdmin の編集フォームでもパスワード保存時の暗号化やパスワード入力時のマスク処理が行われるようになっています。

したがって、カスタムユーザーの管理においても、ModelAdmin ではなく UserAdmin のサブクラスが利用されるようにすれば、ログイン等も可能なカスタムユーザーの追加・編集が可能となることになります。

スポンサーリンク

UserAdmin のサブクラスが必要な理由

で、ここまで説明してきた通り、カスタムユーザーの管理画面を実現するためには、UserAdmin そのものではなく、UserAdmin の “サブクラス” を定義して利用することになります。UserAdmin でもパスワードの暗号化等が行われるのに、なぜわざわざサブクラスを定義する必要があるのでしょうか?

それは、UserAdmin はあくまでも User の管理画面を実現するためのクラスとなっているからになります。

もっと具体的に言えば、UserAdmin は User の持つフィールドに合わせたインスタンスの一覧表やフォームの表示が行われるように実装されています。したがって、カスタムユーザーの管理画面を UserAdmin で実現しようとした場合、User の持つフィールドをカスタムユーザーが持っていないと画面表示時に例外が発生することになります。

逆に、 User の持たないフィールドをカスタムユーザーに追加した場合でも、その追加したフィールドは一覧表やフォームには表示されないことになります。

UserAdminがUserを管理すること前提に作られていることを示す図

このように、UserAdmin はあくまでも User の管理画面を実現するためのクラスです。そのため、カスタムユーザーの管理を行う際は UserAdmin をそのまま利用するのではなく、UserAdmin のサブクラスを定義し、そのサブクラスでカスタムユーザーと User の差を吸収するように実装を行う必要があります。

もっと具体的に言えば、カスタムユーザーの持たないフィールドが参照されないように UserAdmin のクラス変数を上書きしたり、カスタムユーザーのみに存在するフィールドが表示されるように UserAdmin のクラス変数を上書きしたりする必要があります。

CustomUserの管理を行うadmin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser

class CustomUserAdmin(UserAdmin):
    #CustomUserとUserとの差を吸収する

admin.site.register(CustomUser, CustomUserAdmin)

ここまでの話をまとめると、カスタムユーザーを管理画面で管理する際には ModelAdmin ではなく UserAdmin のサブクラスを定義し、これをカスタムユーザーの管理に利用するように admin.pyadmin.site.register を実行する必要があります。

UserAdmin のサブクラスを定義する理由は、UserAdmin の良いところ、具体的にはパスワードの暗号化などを継承しつつ、カスタムユーザーならではのクラスにカスタマイズを行うことができるからです。

UserAdminを継承してカスタムユーザーを管理するクラスを作成する様子

もし、カスタムユーザーが User と全く同じフィールドのみを持っているのであれば、UserAdmin をそのまま利用しても管理画面での管理は正常に行うことができます。例えばカスタムユーザーが AbstractUser を継承しているだけであれば、Userと同じフィールドを持つことになるため、UserAdmin をそのまま admin.site.register の第2引数に指定してやれば良いことになります。

また、カスタムユーザーの管理を ModelAdmin のサブクラスによって実現することも可能だと思います。そのサブクラスの定義によってパスワードの暗号化やパスワードのマスクなどの処理を実現してやれば、少なくとも暗号化の問題は解決できるはずです。

ModelAdminを継承してカスタムユーザーを管理するクラスを作成する様子

ですが、せっかく UserAdmin という便利なクラスが存在しているので、それを利用した方が楽に、かつ、品質も高いカスタムユーザーの管理を実現することができるため、このページでは UserAdmin を利用することをオススメしています。

また、下記ページでも解説しているとおり、通常のモデルクラスの管理画面は ModelAdmin のサブクラスの定義によって変更を行うことになります。

【Django入門11】管理画面のカスタマイズ(ModelAdmin)

今回は UserAdmin のサブクラスを利用することになりますが、実は通常のモデルクラスの時の「モデルクラスの管理画面は ModelAdmin のサブクラスの定義によって変更する」という方針は変わっていません。前述の通り、UserAdminModelAdmin のサブクラスですので、UserAdmin のサブクラスも ModelAdmin のサブクラスと考えられます。したがって、結局はカスタムユーザーの管理画面も ModelAdmin のサブクラスの定義によって実現していくことになります。

UserAdmin のサブクラスの実装

ここまでの説明で、カスタムユーザーを管理するために UserAdmin のサブクラスの定義が必要であることは理解していただけたのではないかと思います。

また、UserAdmin のサブクラスをどのように定義すれば良いのか?についてもイメージは湧いているのではないかと思います。要は、User とカスタムユーザーの差を吸収するように定義してやれば良いです。

じゃあ、具体的にどんな実装をすれば良いの?という点について、ここから説明していきたいと思います。

今回、カスタムユーザー向けの管理画面を実現するクラスの名前は CustomUserAdmin として説明していきます。

また、実例があった方が良いと思うので、下記の CustomUser を管理することを想定し、この CustomUser に応じた管理画面を作成する実例を踏まえながら解説をしていきたいと思います(UserManager も一応用意しています)。

今回想定する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."),
    )

    height = models.FloatField()
    weight = models.FloatField()

    objects = UserManager()

    EMAIL_FIELD = "email"
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["height", "weight"]

    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)

この CustomUserAbstractBaseUser と PermissionsMixin を継承して作成したカスタムユーザーモデルであり、AbstractBaseUser と PermissionsMixin を継承してカスタムユーザーを作成する手順は下記ページで解説していますので、カスタムユーザーの作り方について知りたい方は下記ページをご参照いただければと思います。

カスタムユーザーをAbstractBaseUserを継承して作成する手順の解説ページアイキャッチ 【Django】カスタムユーザー(独自のユーザー)の作り方【AbstractBaseUser編】

少し説明を加えておくと、上記の CustomUser では、User に比べて下記のフィールドを削除しています。

  • username
  • first_name
  • last_name
  • is_active
  • date_joined

さらに、User に比べて下記のフィールドを追加しています。そして、これらのフィールドは必須フィールドとして設定しています(必須フィールドなので一応 REQUIRED_FIELDS にもこれらのフィールドを追加しています)。

  • height
  • weight

基本的に UserCustomUser の違いは、上記のフィールドの有無のみとなります。

前述の通り、UserAdmin は User を管理することを前提としたクラスとなっていますので、UserAdmin のサブクラスである CustomUserAdmin“上記のフィールドの有無の違いを考慮しながら” 実装していくという点が、カスタムユーザー向けの管理画面の実現を行う上でのポイントになります。

CustomUserAdmin の定義

では、続いて本題の CustomUserAdmin の定義を行なっていきます。

定義先のファイルはアプリフォルダ内の admin.py となります。ひとまず、単に UserAdmin を継承するだけのクラスとして、下記のように CustomUserAdmin を定義したいと思います。

CustomUserAdminの定義
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin


class CustomUserAdmin(UserAdmin):
    pass

CustomUser = get_user_model()
admin.site.register(CustomUser)

前述の通り、CustomUserAdminUserAdmin を継承するのみで作成しているため、UserAdmin 同等のクラスになります。

ですので、このままだと User を管理することを前提としたクラスになってしまっていますので、User ではなく CustomUser を管理できるようにカスタマイズしていく必要があります。

スポンサーリンク

CustomUserAdmin の管理画面への登録

カスタマイズを行う前に、まずは CustomUser に対する管理画面の表示時に CustomUserAdmin が利用されるように、CustomUserAdmin の管理画面への登録を行いたいと思います。

この登録は、下記のように admin.site.register の第2引数に CustomUserAdmin を指定することで実現することができます。

CustomUserAdminの登録
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin


class CustomUserAdmin(UserAdmin):
    pass

CustomUser = get_user_model()
admin.site.register(CustomUser, CustomUserAdmin)

admin.site.register の第1引数では、管理画面で管理を行うモデルクラスの指定を行います。これにより、管理画面で第1引数に指定したモデルクラスのインスタンスを管理することができるようになります。

また、admin.site.register の第2引数では、管理画面表示時に使用するクラスを指定します。これにより、管理画面が第2引数に指定したクラスに応じた画面に変化します(指定をしなかった場合は ModelAdmin がデフォルトで指定されるようになっています)。

ですので、上記のように admin.site.register を実行することで、CustomUser の管理を行う画面は CustomUserAdmin に従って表示されることになります。

ただし、現状の CustomUserAdmin は User のインスタンスを管理することを前提としたクラスとなっています。そのため、admin.py を上記のように変更して Django の開発用ウェブサーバーを起動した場合、User に対して CustomUser のフィールドが不足しているので例外が発生することになります。

具体的に発生する例外は下記のようなものになります。

ERRORS:
<class 'login_app.admin.CustomUserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not a field of 'login_app.CustomUser'.
<class 'login_app.admin.CustomUserAdmin'>: (admin.E108) The value of 'list_display[0]' refers to 'username', which is not a callable, an attribute of 'CustomUserAdmin', or an attribute or method on 'login_app.CustomUser'.
<class 'login_app.admin.CustomUserAdmin'>: (admin.E108) The value of 'list_display[2]' refers to 'first_name', which is not a callable, an attribute of 'CustomUserAdmin', or an attribute or method on 'login_app.CustomUser'.
<class 'login_app.admin.CustomUserAdmin'>: (admin.E108) The value of 'list_display[3]' refers to 'last_name', which is not a callable, an attribute of 'CustomUserAdmin', or an attribute or method on 'login_app.CustomUser'.
<class 'login_app.admin.CustomUserAdmin'>: (admin.E116) The value of 'list_filter[2]' refers to 'is_active', which does not refer to a Field.

ご覧の通り、User と比較して CustomUser に足りないフィールドに対して例外が発生していることを確認していただけると思います。

現状の CustomUserAdminUser を管理することを前提としたクラスとなっているため上記のような例外が発生してしまいますが、CustomUserAdmin を実装して例外が発生する原因となっているフィールドを利用しないようにすることで上記の例外を解消することができます。

CustomUserAdmin の実装

ということで、次は CustomUserAdminCustomUser に存在するフィールドのみを利用するように実装していきたいと思います。

まずは、現状の把握の意味も込めて、UserAdmin がどのようなフィールドを利用しているのかを調べたいと思います。これは、UserAdmin の定義を表示してやることで簡単に調べることができます。

VScode 限定の話になりますが、クラスの定義の調べ方については下記ページでも紹介しているので、こちらを参考にしていただければと思います。

VSCodeでのクラス定義の確認方法の解説ページアイキャッチ 【Django】VSCodeでクラスの定義を簡単に確認する方法

とりあえず Django 4.0.5 の場合、UserAdmin の定義を利用するフィールドのみに焦点を当てて抜粋すると下記のようになっています。

UserAdminが利用するフィールド
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    fieldsets = (
        (None, {"fields": ("username", "password")}),
        (_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_active",
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
    )
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("username", "password1", "password2"),
            },
        ),
    )
    list_display = ("username", "email", "first_name", "last_name", "is_staff")
    list_filter = ("is_staff", "is_superuser", "is_active", "groups")
    search_fields = ("username", "first_name", "last_name", "email")
    ordering = ("username",)

上記で定義されている各クラス変数の簡単な意味合いについて解説しておきます。

fieldsets

fieldsets はユーザーの詳細画面(編集フォーム)で表示するフィールド、および、ユーザーの情報変更を行う際に入力受付を行うフィールドを指定するクラス変数になります。

fieldsetsの説明図

add_fieldsets

add_fieldsets はユーザー追加フォームでユーザーからの入力を受け付けるフィールドを指定するクラス変数になります。

add_fieldsetsの説明図

list_display

list_display はユーザー一覧リストに表示するフィールドを指定するクラス変数になります。

list_displayの説明図

list_filter

list_filter は、ユーザー一覧リストに対するフィルタリング可能なフィールドを指定するクラス変数になります。

list_filterの説明図

例えば、このフィルタリングを利用して is_superuser フィールドが True のユーザーのみをユーザー一覧リストに表示するようなことが可能です。

上の図では表示されていませんが、list_filter"groups" を指定しておけば、グループが1つ以上存在する場合にフィルターの項目に By groups が追加されます。

search_fields

search_fields はユーザー検索を行う際に、検索対象に含めるフィールドを指定するクラス変数になります。

search_fieldsの説明図

例えば上の図のユーザーの例で考えると、serarch_fields"email" を指定している場合、abc で検索を行うと emailabc が含まれるユーザー Jiro がヒットすることになります。それに対し、serarch_fields"email" を指定していない場合、email は検索対象として扱われませんので、abc で検索してもユーザーは一人もヒットしないことになります。

ordering

ordering はユーザー一覧リスト初期表示時のユーザー表示順を決定するフィールドを指定するクラス変数になります。

orderingの説明

例えば ordering に指定するタプルの第1要素に "email" を指定すれば、email の文字列に対して昇順にユーザーが並んだ状態でユーザー一覧リストが表示されるようになります。

filter_horizontal

この filter_horizontal については正直私も余り理解できていないのですが、おそらくユーザー追加時等に下図のように選択式での設定を行うことができるフィールドを指定するクラス変数になると思います。

filter_horizontalの説明図

今回は filter_horizontal の変更は行いませんが、モデルに ManyToManyField を持たせるような場合、filter_horizontal にそのフィールドを指定して選択式でフィールドの設定を行えるようにしてやることでユーザー管理の利便性が上がります。

MEMO

今回はフィールドを指定するクラス変数のみに焦点を当てて解説していますが、他にも管理画面で使用するテンプレートなどをクラス変数の定義によって指定することも可能です

例えば add_form_template を定義することによりユーザー追加フォーム表示時のテンプレートを変更するようなことも可能です

他にどのような指定を行うことができるのかについては UserAdmin クラスの定義をご確認いただければと思います

削除したフィールドを利用しないように実装

UserAdmin の定義をご覧いただければ分かる通り、各クラス変数において CustomUser に存在しない下記のフィールドが指定されています。

  • username
  • first_name
  • last_name
  • is_active
  • date_joined

現状の CustomUserAdminUserAdmin を継承するのみで作成しているため、UserAdmin 同様に、CustomUserAdmin でも上記のフィールドの表示や上記のフィールドの入力受付が行われるようになっていることになります。

そのため、管理画面で CustomUser を管理できるようにするには、CustomUserAdmin で上記のフィールドを指定しないように CustomUserAdmin を実装する必要があります。

やり方は単純で、先ほど示した UserAdmin の各クラス変数の定義を CustomUserAdmin のクラス変数として定義し、さらにそのクラス変数に指定する値やタプル等から上記で挙げたフィールドの削除を行います。

CustomUserAdmin のクラス変数の定義が優先して使用されるため、これによって上記のフィールドが管理画面作成時に利用されないようになります。

具体的な手順としては、まず、CustomUserAdmin に対し、UserAdmin でフィールドの指定を行なっている各クラス変数の定義をコピペしてきます。そうすると、admin.py は下記のようになると思います。

CustomUserAdminでのクラス変数の定義
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin


class CustomUserAdmin(UserAdmin):
    fieldsets = (
        (None, {"fields": ("username", "password")}),
        (_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_active",
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
    )
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("username", "password1", "password2"),
            },
        ),
    )
    list_display = ("username", "email", "first_name", "last_name", "is_staff")
    list_filter = ("is_staff", "is_superuser", "is_active", "groups")
    search_fields = ("username", "first_name", "last_name", "email")
    ordering = ("username",)

CustomUser = get_user_model()
admin.site.register(CustomUser)

さらに、上記の各クラス変数において、CustomUser に存在しないフィールドを削除 or 他のフィールドへの置き換えを行います。

とりあえず例外が発生しないようにするのであれば、admin.py を下記のように変更してやれば良いです。

削除したフィールドを利用しないように変更
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _

class CustomUserAdmin(UserAdmin):

    fieldsets = (
        (None, {"fields": ("email", "password")}),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login",)}),
    )
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("email", "password1", "password2"),
            },
        ),
    )

    list_display = ("email", "is_staff")
    list_filter = ("is_staff", "is_superuser", "groups")
    search_fields = ("email", "email")
    ordering = ("email",)
    filter_horizontal = (
        "groups",
        "user_permissions",
    )

CustomUser = get_user_model()
admin.site.register(CustomUser, CustomUserAdmin)

各クラス変数の変更によって管理画面がどのように変化するのかについては、CustomUserAdmin の実装 の冒頭で説明した各クラス変数の説明を参照していただければと思います。

例えばですが、上記のように add_fieldsets を変更することにより、管理画面でユーザーを追加する際の入力フォームは次の図のように変化します。

add_fieldsets変更による入力フォームの変化を示す図

このように、add_fieldsets に指定したフィールドの入力受付が行えるようユーザー追加フォームが変化していることが確認できると思います(最初の入力フィールドが username ではなく email に変わっている)。

また、上図のユーザー追加フォームにおいては、パスワードがマスクされて他の人から見られないようになっています。このパスワードのマスクに関しては、前述の通り UserAdmin を継承することで実現される機能となります。

パスワードがマスクされて表示される様子

ただ、ページの冒頭に下記のような文言があり、ここに username という文言が残っているので気持ち悪いですね…。今回は説明は省略させていただきますが、このようなページの表示文字列等を変更する場合は使用するテンプレートの変更が必要になります。

First, enter a username and password. Then, you’ll be able to edit more user options.

追加したフィールドを利用するように変更

さて、前述のように CustomUserAdmin を変更することで、管理画面表示時に CustomUser に存在しないフィールドが利用されないようになったことになります。

ただ、まだ問題点があります。

これは実際にユーザーの追加を行なってみれば分かるのですが、現状の CustomUserAdmin を利用して管理画面からユーザーの追加を行うと、下記のような例外が発生することになります。

django.db.utils.IntegrityError: NOT NULL constraint failed: login_app_customuser.height

なんとなく、この例外から現象を推測していただけると思うのですが、現状の CustomUserAdmin ではユーザー追加時に height、さらには weight の入力受付が行われないため、これらの設定を行うことができずに上記のような例外が発生してしまうことになります。

もう少し詳しく説明しておくと、現状の CustomUserAdminadd_fieldsets では "email""password1""password2" のみが指定されているため、ユーザー追加フォームにおいては、この3つのフィールドの入力受付のみが行われるようになっています。

そして、ユーザー追加時には、これらの入力値が CustomUser のインスタンスに設定され、そのインスタンスの情報がレコードとしてデータベースに保存されるようになっています(実際には password1password2 ではなく、password のフィールドに値が設定されることになります)。

ユーザーの情報をレコードとして保存する様子

ただし、CustomUser では User に比較して heightweight のフィールドを追加しており、さらにこれらのフィールドは設定必須項目として扱われるようになっています。

そのため、heightweight のフィールドが設定されていないレコードをデータベースに保存しようとすると、設定必須項目が設定されていないことを理由に上記のような例外が発生することになります。

必須フィールドが足りなくてレコード保存に失敗する様子

この例外は、ユーザー追加フォームで heightweight の入力受付を行うようにすれば解決することができますので、この方法で例外を解決していきたいと思います(他にも heightweight を “設定任意項目” に設定して解決するようなこともできます)。

具体的には、ユーザー追加フォームで入力受付を行うフィールドの指定を行う add_fieldsets に  "height""weight" の指定を追加してやれば良いです。

また、add_fieldsets に対してだけでなく、heightweight のような CustomUser に独自に追加したフィールドを他のクラス変数に指定することもできます。そして、これによって管理画面でのユーザー管理の利便性を向上させることができます。

例えば下記のように admin.py を変更してやれば、ユーザーの追加フォームや編集フォームに heightweight の入力受付が行われるようになり、ユーザー一覧リスト等にも heightweight が表示されるようになります。

追加したフィールドを利用するように変更
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _

class CustomUserAdmin(UserAdmin):

    fieldsets = (
        (None, {"fields": ("email", "password")}),
        (_("Personal info"), {"fields": ("height", "weight")}),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login",)}),
    )
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("email", "height", "weight", "password1", "password2"),
            },
        ),
    )

    list_display = ("email", "height", "weight", "is_staff")
    list_filter = ("is_staff", "is_superuser", "groups")
    search_fields = ("email", "email")
    ordering = ("height",)
    filter_horizontal = (
        "groups",
        "user_permissions",
    )

CustomUser = get_user_model()
admin.site.register(CustomUser, CustomUserAdmin)

例えば、上記の CustomUserAdmin を利用して表示されるユーザー一覧リストは次の図のようになります。

カスタマイズ後のユーザー一覧リスト

heightweight が表示されていることが確認できると思いますし、ordering = ("height",) としているため、height に対して昇順にユーザーが並んで表示されていることも確認できると思います。

UserAdmin のサブクラス実装時のポイント

こんな感じで、管理画面でカスタムユーザーを管理するためには、作成したカスタムユーザーに合わせて UserAdmin のサブクラスの実装を行うことが必要になります。

重要なのは、ここまで説明してきたように、カスタムユーザーの持つフィールドに合わせて管理画面で利用するフィールドを設定することになります。

ここが上手く実現できていないとユーザー管理画面で例外が発生し、上手くユーザーを管理することができなくなるので注意してください。

特に下記の2点については最低限実現できるように、UserAdmin のサブクラスを実装するようにしてください。

  • カスタムユーザーが持たないフィールドはユーザー管理画面からも利用しない
  • カスタムユーザーに設定必須のフィールドはユーザー管理画面から設定できるようにする

カスタムユーザーの管理画面の実装例

以上が UserAdmin のサブクラスの実装方法の解説になります。

最後に、ここまでの復習の意味を込めて、カスタムユーザーを管理画面で管理するための手順および実装例をまとめて示しておきたいと思います。

特にここまでは admin.py に焦点を当てて解説をしてきましたが、ここでは他のファイルの変更例も含めてカスタムユーザーを管理画面で管理するために最低限必要な実装を紹介していきます。

ただし、ここでは詳細な説明は省略させていただきますので、特にカスタムユーザーの作り方やアプリでのカスタムユーザーの利用の仕方については下記のページを必要に応じて参考にしていただければと思います。

【Django】カスタムユーザー(独自のユーザー)の作り方【AbstractUser編】 カスタムユーザーをAbstractBaseUserを継承して作成する手順の解説ページアイキャッチ 【Django】カスタムユーザー(独自のユーザー)の作り方【AbstractBaseUser編】

スポンサーリンク

プロジェクトとアプリの作成

では、まずはプロジェクトとアプリを作成していきたいと思います。

プロジェクト名は my_project、アプリ名は accounts にしたいと思います。

最初に、下記コマンドを実行してプロジェクト my_project を作成します。

% django-admin startproject my_project

続いて、上記コマンドで作成される my_project フォルダの中に移動し、さらに startapp を実行してアプリ accounts を作成します。

% cd my_project
% python manage.py startapp accounts

次に、my_project フォルダの中にある settings.py 内の INSTALLED_APPS を下記のように変更し、プロジェクトに対してアプリを登録します。

アプリの登録
# Application definition

INSTALLED_APPS = [
    'accounts', # 追加
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

カスタムユーザーの作成

続いてカスタムユーザーを作成していきたいと思います。

今回は、カスタムユーザーとして UserAdmin のサブクラスの実装 で示した CustomUser をそのまま利用したいと思います。

ということで、accounts フォルダの中にある models.py を開き、下記のように変更してください。

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."),
    )

    height = models.FloatField()
    weight = models.FloatField()

    objects = UserManager()

    EMAIL_FIELD = "email"
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["height", "weight"]

    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)

AUTH_USER_MODEL の設定

カスタムユーザーが作成できましたので、次はプロジェクトで認証に利用するモデルを先ほど作成した CustomUser に設定していきます。

そのために、settings.py の最後に下記を追記します。

AUTH_USER_MODELの設定
AUTH_USER_MODEL = 'accounts.CustomUser'

これにより、アプリ内での認証や管理画面での認証に CustomUser が利用されるようになります。

スポンサーリンク

マイグレーションの実行

続いてマイグレーションを実行しましょう!

startproject で作成された my_project フォルダ内で下記の2つのコマンドを実行してみてください。おそらくエラーが発生することなく正常にマイグレーションが実行できるはずです。

% python manage.py makemigrations
% python manage.py migrate

このマイグレーションによってデータベースに accounts.customuser というテーブルが作成されることになります。

スーパーユーザーの追加

ここから本題の管理画面に関する変更を行なっていくのですが、管理画面にログインするためにはスーパーユーザーが必要です。

ということで、まずはスーパーユーザーを追加するために、startproject で作成された my_project フォルダ内で下記コマンドを実行してみてください。

% python manage.py createsuperuser

上記コマンドを実行すれば、作成するスーパーユーザーのメールアドレス・身長・体重・パスワード(確認用も含めて2回)の入力受付が順次行われますので作成したいスーパーユーザーに合わせて入力を行なっていってください。

% python manage.py createsuperuser
Email address: hanako@yamada.jp
Height: 167.2
Weight: 56.2
Password: 
Password (again): 
Superuser created successfully.

この入力によって、スーパーユーザーが作成されるはずです。

管理画面の設定後、このスーパーユーザーで管理画面へのログインを行うため、上記で入力したメールアドレスとパスワードはメモしておいてください。

管理画面の設定

最後に管理画面の設定を行ない、models.py に作成したカスタムユーザーに合わせたユーザー管理画面を実現していきます。

この設定に関しても、UserAdmin のサブクラスの実装 で紹介した内容と同じ設定を行うことにしたいと思います。

要は、UserAdmin のサブクラスを定義し、そのクラスを CustomUser の持たないフィールドを利用しないように、さらに CustomUser に追加したフィールドを利用しないように実装していきます。

これは、結局は accounts フォルダの下の admin.py を下記のように変更することで実現できます。

追加したフィールドを利用するように変更
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _

class CustomUserAdmin(UserAdmin):

    fieldsets = (
        (None, {"fields": ("email", "password")}),
        (_("Personal info"), {"fields": ("height", "weight")}),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login",)}),
    )
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("email", "height", "weight", "password1", "password2"),
            },
        ),
    )

    list_display = ("email", "height", "weight", "is_staff")
    list_filter = ("is_staff", "is_superuser", "groups")
    search_fields = ("email", "email")
    ordering = ("height",)
    filter_horizontal = (
        "groups",
        "user_permissions",
    )

CustomUser = get_user_model()
admin.site.register(CustomUser, CustomUserAdmin)

スポンサーリンク

動作確認

変更は以上になります。最後に動作確認を行なっておきましょう!

管理画面にアクセスするためには、まずは下記コマンドで Django の開発用ウェブサーバーを起動しておく必要があります。

% python manage.py runserver

Django の開発用ウェブサーバー起動後、ウェブブラウザで下記 URL にアクセスすれば、管理画面へのログインフォームが表示されるはずです。ここには、先ほど作成したスーパーユーザーのメールアドレスとパスワードを入力してください。

http://localhost:8000/admin/

ログインに成功すれば、あとはいつも通りの感覚でユーザーの管理を行なってみてください。

例えば管理画面のトップページにおける Users リンクをクリックすればユーザー一覧リストが表示されます。まだユーザーは一人だと思いますが、heightweight といった CustomUser 独自のフィールドの情報が表示されていることが確認できると思います。

表示されるユーザー一覧リスト

また、ユーザー一覧リスト表示ページにおける右上の ADD USER + ボタンをクリックすればユーザー追加フォームが表示され、ここでも heightweight といった CustomUser 独自のフィールドの入力が可能であることを確認できると思います。

表示されるユーザー追加フォーム

このような動作結果より、admin.py の変更によってカスタムユーザーに応じた管理画面のカスタマイズを行うことができることを確認していただけるのではないかと思います。

最後に、実際にユーザーを新規追加し、そのユーザーでログインできることを確認しておきましょう!

まずは、上の図で示す追加フォームの各種フォームに入力を行い、ページ下部の SAVE ボタンをクリックしてください。ユーザーの追加に成功すれば、そのユーザーの編集フォームが表示されることになります。

まず、この編集フォームの先頭部分で、パスワードがしっかり暗号化されていることが確認できるはずです。これは、CustomUserAdminUserAdmin のサブクラスとして定義したことによる効果となります。

CustomUserAdminの追加フォームから追加したユーザーのパスワードが暗号化されている様子

さらに、編集フォームで Staff statusSuperuser status の両方のチェックを ON に変更し、ページ下部の SAVE ボタンをクリックしてください。これにより、この追加したユーザーに管理画面での全管理の権限が付与されたことになります。当然管理画面へのログインも可能です。

ということで、ログインの確認を行なっておきましょう!まずページ上部の LOG OUT リンクをクリックしてログアウトを行い、さらに遷移後のページから Log in again リンクをクリックしてログインフォームを表示してください。

そして、表示されたログインフォームに、先ほど追加フォームから追加したユーザーのメールアドレスとパスワードを入力し、Log in ボタンをクリックしてください。おそらく、ログインに成功するはずです。

このログインは、ModelAdmin で User を管理した時の動作 で実際に確認したように、ModelAdmin では実現できなかったことになります。この点からも、カスタムユーザーを管理する際には ModelAdmin ではなく UserAdmin のサブクラスを利用する必要があることを理解していただけると思います。

まとめ

このページでは、Django での管理画面(admin)でカスタムユーザーを管理する方法について解説しました!

管理画面は基本的にはモデルクラス毎に ModelAdmin のサブクラスを定義してカスタマイズすることになりますが、カスタムユーザーの場合は ModelAdmin のサブクラスでは不十分です。パスワードの暗号化等が行われません。

そのため、カスタムユーザーの管理画面に関しては UserAdmin のサブクラスを定義してカスタマイズする必要があります。

ただし、あくまでも UserAdminUser を管理するためのクラスであり、そのまま使用すると User の持つフィールドに合わせて管理画面が表示されることになるので注意してください。User の持つフィールドをカスタムユーザーが持たないような場合、例外が発生する恐れがあります。

管理画面を利用することで、管理者専用のユーザーの管理なども行うことができるようになるため、ぜひ今回説明した内容は覚えておいてください!

同じカテゴリのページ一覧を表示

1 COMMENT

現在コメントは受け付けておりません。