【Django】ユーザー管理画面(admin)のカスタマイズ

Djangoでのユーザー管理画面のカスタマイズ方法の解説ページアイキャッチ

このページでは、Django における管理画面(admin)のカスタマイズ方法について解説していきます。管理画面の中でも、特に「ユーザー管理画面」のカスタマイズについて解説していきます。

このカスタマイズを行うことで、アプリ開発者が独自で作成したユーザー(カスタムユーザー)に合わせたユーザー管理画面を実現することができるようになります。

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

http://localhost:8000/admin/

実際の画面としては下図のようなものになり、この画面からユーザーを管理することが可能です。この、特にユーザーを管理する画面のことを「ユーザー管理画面」と呼ばせていただいています。

管理画面を示す図

このユーザー管理画面では、例えばユーザー一覧リストを表示したり、

ユーザー一覧リスト

さらにはユーザーの追加を行ったりすることが可能です。他にもユーザーの情報の変更や削除なども行えます。

ユーザー追加フォームを示す図

また、当然と言えば当然ですが、このユーザー管理画面で入力したパスワードは下の図のようにマスクされており、他の人からは見えないようになっています。

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

また、入力されたパスワードに対しては妥当性の検証が行われ、パスワードとして相応しくない場合(例えば文字数が少ない・名前等の他の情報と似ている場合など)にはパスワードの再入力が促されるようになっており、セキュリティ的にも強化されています。

ただし、ここで示したユーザー管理画面は、認証に利用するユーザーモデル(ユーザー管理モデル)が User である場合の画面となります(User は Django に標準で用意されているユーザー管理モデルとなります)。

その一方で、下記ページでも解説しているとおり、Django ではカスタムユーザーを作成することができ、それを User の代わりに利用することも可能です。

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

そして、これは上記ページでも解説していますが、アプリフォルダ内の admin.py で下記を実行することにより、自身で作成したカスタムユーザーのインスタンス(つまり各ユーザー)をユーザー管理画面で管理することも可能です。

管理画面へのカスタムユーザーの登録
from django.contrib import admin

admin.site.register(カスタムユーザーのモデル名)

ですが、この場合、別途ユーザー管理画面のカスタマイズを行わないと、ユーザー一覧に表示されるフィールドが不足していたり、ユーザーを追加する場合に不要なフィールドの入力受付も行われて冗長だったりします。

ユーザー一覧リストに表示される情報が不足している様子

さらに、ユーザー管理画面で入力したパスワードはそのまま表示されてしまいます。

パスワードがそのまま表示されてしまう様子

また、パスワードの妥当性検証も行われないため、極端に短い文字列やユーザー名等の他のフィールドと似ている文字列を設定した場合でもパスワードの登録を行うことが可能になってしまっており、セキュリティ的にイマイチです。

こんな感じで、カスタムユーザーを利用している場合、User を利用している時に比較して管理画面が不便なものになってしまっています。

ここからは、この管理画面が不便なものになってしまっている理由、すなわち、User を利用している場合とカスタムユーザーを利用している場合とでユーザー管理画面が大きく異なる理由と、User を利用しているときと同様にユーザー管理画面での利便性を向上させるためのカスタマイズ方法について解説していきたいと思います。

カスタムユーザー利用時に管理画面が不便になる理由

では、まずはカスタムユーザーを利用している場合と User を利用している場合とでユーザー管理画面に違いが生じる理由について解説していきます。

この根本的な理由は「ユーザー管理画面作成時に動作するクラスが違うため」になります。

ModelAdminUserAdmin の関係

まず、カスタムユーザーを利用している場合、ユーザー管理画面は ModelAdmin というクラスが動作することで作成されます。

それに対し、User を利用している場合、ユーザー管理画面は UserAdmin というクラスが動作することで作成されます。

このように、ユーザー管理画面を作成する際に動作するクラスが異なります。

そして、この2つのクラスには下の図のような継承関係があります。つまり、UserAdminModelAdmin を継承して作成されたクラスとなります。

ModelAdminとUserAdminの関係性

ModelAdminUserAdmin の違い

さらに、UserAdminModelAdmin を「User 管理用にカスタマイズしたクラス」となります。User を管理するのですから、パスワードなどは当然他の人に見られないようにマスクして表示されるようなカスタマイズが行われています。

また、ユーザー一覧リストにおいては User にとって重要なフィールドが表示され、ユーザー追加フォームにおいては User を管理するために必須となる情報のフィールドに対してのみ入力受付が行われるようにカスタマイズされています。

ユーザーの情報が表示される様子

それに対し、UserAdmin の継承元の ModelAdminUser に特化したものではなく、一般的なモデルを管理するためのクラスとなっています。

一般的なモデルにおいてはパスワード入力が不要なものも多く、パスワード入力エントリーも通常のエントリーとして扱われるのでマスクされません。

また、ユーザー一覧リストにおいてはモデルのインスタンスを識別するための情報のみが、さらにユーザー追加フォームにおいてはモデルに用意されたすべてのフィールドの入力受付が行われるようになっています。

ユーザー一覧リストに表示される情報が不足している様子

このような違いがあるため、カスタムユーザーを利用している場合と User を利用している場合とでユーザー管理画面の違いが生じます。

そして、カスタムユーザーに関しては一般的なモデルと同様の管理が行われるようにユーザー管理画面が作成されるため、ユーザー管理を行う上では不便な管理画面になってしまっています。

カスタムユーザーに応じた管理画面を作成する方法

ここまで聞くと、カスタムユーザー利用時もユーザー管理画面を UserAdmin から作成させるようにすれば良いのでは?と感じる方も多いと思います。

方向性は合っています。

ただ、前述の通り、UserAdminUser モデルのインスタンスを管理するためのクラスであり、そのままではカスタムユーザーのインスタンスを管理するのが困難です。

カスタムユーザーは開発者が独自で作成するモデルであるため、User モデルに存在しないフィールドを持っていたり、User モデルの持つフィールドを持っていなかったりします。

それに対し、UserAdmin は管理するモデルが User であることを前提に作られているため、User モデルの持つフィールドに合わせて表示や入力受付が行われています。

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

そのため、UserAdmin でカスタムユーザーを管理しようとすると、存在しないフィールドを表示しようとして例外が発生する、設定が必須のフィールドの入力受付が行われなくて情報が足りなくて例外が発生する、などといった現象が発生することになります。

例えばカスタムユーザーに username フィールドを持たせなかった場合でも、ユーザー一覧リストで各ユーザーの username を表示しようとするため、そこで例外が発生することになります。

上記のような理由から、UserAdmin をそのまま利用してカスタムユーザーを管理するのは難しいです。

ですので、単に UserAdmin をそのまま利用するのではなく、UserAdmin を継承するクラスを定義し、そのクラスをカスタムユーザー向けにカスタマイズすることが必要になります。

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

もしくは、UserAdmin ではなく ModelAdmin を継承するクラスを定義し、そのクラスをカスタムユーザー管理用に作成していくのでも良いと思います。

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

前者の場合、 UserAdmin を継承するためユーザーを管理する基本的な機能も継承することができ、必要な変更は過不足しているフィールドの対応くらいに抑えることができます。

それに対し、後者の場合は、一般的なモデルの管理からユーザー管理するためのクラスに仕立てていくことが必要になるため必要な変更が多くなります。例えばパスワードをマスクするなどの処理も実装が必要になります。

今回は、前者のやり方、すなわち、UserAdmin を継承してクラスを定義し、そのクラスをカスタムユーザーに合わせてカスタマイズすることで、カスタムユーザーに応じた管理画面の作成を行なっていきたいと思います。

カスタムユーザーに応じた管理画面の実現

それでは、カスタムユーザーに応じた管理画面の実現手順について解説していきます。

前述の通り、UserAdmin を継承してクラスを定義し、そのクラスのカスタマイズを行うことでカスタムユーザーに応じた管理画面を実現していきます。この定義するクラス名は 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

前述の通り、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 の管理画面への登録

カスタマイズを行う前に、まずは 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 を実行することで、CustomUserAdmin クラスによって作成される管理画面で CustomUser のインスタンスを管理することができるようになります。

ただし、前述の通り、現状の 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 に対して昇順にユーザーが並んで表示されていることも確認できると思います。

管理画面のカスタマイズにおけるポイント

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

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

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

特に下記の2点については最低限実現できるように、ユーザー管理画面のカスタマイズを行うようにしてください。

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

スポンサーリンク

ユーザー管理画面のカスタマイズ例

以上がユーザー管理画面のカスタマイズ手順の解説になります。

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

特にここまでは 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',
]

カスタムユーザーの作成

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

今回は、カスタムユーザーとして カスタムユーザーに応じた管理画面の実現 で示した 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'

マイグレーションの実行

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

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 を継承するクラスを定義し、そのクラスを 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 の変更によって、管理するユーザーのモデルに合わせたユーザー管理画面のカスタマイズを行うことができることを確認していただけるのではないかと思います。

まとめ

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

Django ではユーザー管理画面をカスタマイズすることが可能であり、これによってカスタムユーザー(独自に作成したユーザー)を管理画面から管理することができるようになります。

具体的には、UserAdmin を継承するクラスを定義し、そのクラスをカスタムユーザーに合わせてカスタマイズすることによって管理画面からのカスタムユーザーの管理を実現することができます。

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

また、デフォルトでもユーザーを管理することは可能ではあるのですが、パスワードがそのまま表示されたり不要なフィールドの入力受付が行われたりして管理画面としてはイマイチなので、今回紹介した手順で管理画面をカスタマイズしてやった方が良いと思います。

カスタムユーザーを利用することで実現可能なアプリの幅広がるのですが、今回説明した管理画面等の設定も必要になるので注意してください!

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

コメントを残す

メールアドレスが公開されることはありません。