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

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

このページでは、Django での管理画面のカスタマイズについて解説していきます。

前回の連載となる下記ページでは管理画面の使い方について解説しました。

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

今回は、この管理画面をカスタマイズする方法について解説していきます。

管理画面のカスタマイズの基本

まずは、この管理画面のカスタマイズについて、基本的な内容を解説していきたいと思います。

管理画面のカスタマイズが必要な理由

最初に、この管理画面のカスタマイズが必要である理由について解説しておきます。

前回の連載となる下記ページでも解説しましたが、管理画面ではモデルクラスのインスタンスの管理、より具体的にはインスタンスの追加・表示・編集・削除を行うことが出来ます。

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

ただし、デフォルトでは、管理画面でカスタマイズ可能なモデルクラスは UserGroup のみとなっています。これらは auth というアプリから提供されるモデルクラスとなります。これらは権限管理の機能を持つモデルクラスであり、この権限管理によって管理画面で行える管理がユーザー or グループごとに設定可能となっています。さらには User には認証に必要なフィールドや機能が用意されているため、この User を利用して管理画面へのログインも行うことが可能となっています。

ですが、前述の通り、デフォルトでは管理画面でカスタマイズ可能なモデルクラスは UserGroup のみとなっています。そのため、自身が開発するウェブアプリで定義したモデルクラスのインスタンスを管理画面で管理できるようにするためには、管理画面のカスタマイズを行う必要があります。

スポンサーリンク

管理画面のカスタマイズで行うこと

このカスタマイズとは、大きく分けると管理画面で管理したいモデルクラスの登録を行うことと、管理画面の詳細設定を行うことの2つに分けることができると思います。

モデルクラスの登録

管理画面で管理したいモデルクラスを登録すれば、そのモデルクラスのインスタンスが管理画面から管理できるようになります。もちろん、その管理ができるのは、その権限を持つユーザーのみとなります。

管理画面の詳細設定

モデルクラスの登録を行えば、そのモデルクラスのインスタンスの管理を実現することはできるのですが、追加フォームや編集フォームに表示されるフィールドの項目を変更したり、

管理画面の詳細設定例1

インスタンスの一覧表に表示される項目を変更したりするなど、管理画面の詳細設定を行うためには別途設定が必要になります。

管理画面の詳細設定例2

これらの設定を行うことで、よりインスタンスの管理を行いやすい管理画面を実現することが出来ます。

admin.py

これらの管理画面のカスタマイズについては admin.py というファイルで設定を行なっていくことになります。

この admin.pystartapp コマンド実行時に自動的に生成されるファイルになります。startapp コマンドはアプリを作成するときに実行するコマンドなので、admin.py はアプリ毎に用意されるファイルということになります。

アプリ内の models.py で定義したモデルクラスを管理画面で管理できるようにするためには、別途この admin.py の変更が必要となります。ただし、admin.py の変更は基本的には管理画面に対する設定を反映するためのものであり、ウェブアプリ本体に影響を与えるものではありません。したがって、管理画面での管理が不要である場合などは、admin.py の変更は不要となります。

管理画面のカスタマイズの仕方

次は管理画面のカスタマイズの仕方について解説していきます。

先ほどの説明の中で、管理画面のカスタマイズでは大きく分けて下記の2つを行うことになると説明しました。そして、この2つの両方とも admin.py の変更によって実現していくことになります。

  • モデルクラスの登録
  • 管理画面の詳細設定

そして、この2つの両方とも admin というモジュールから提供される関数やクラスを利用することで実現していくことになります。この admin は、下記のように初期状態の admin.py で既に import されているモジュール(Django フレームワークに用意されたアプリ)になります。

admin.pyの初期状態
from django.contrib import admin

# Register your models here.

スポンサーリンク

モデルクラスの登録の仕方

まず、モデルクラスの登録に関しては admin.site.register という関数を利用して行うことになります。この関数の第1引数に「管理画面で管理したいモデルクラス」を指定して実行することで、このモデルクラスが管理画面に管理対象として登録され、以降、このモデルクラスをのインスタンスが管理画面で管理可能となります。

モデルクラスの登録
from django.contrib import admin
from .models import モデルクラス

# Register your models here.
admin.site.register(モデルクラス)

例えば、models.pyStudent というモデルクラスを定義しているのであれば、下記のように admin.site.register を実行することで Student のインスタンスが管理画面で管理可能となります。

モデルクラスの登録例
from django.contrib import admin
from .models import Student

# Register your models here.
admin.site.register(Student)

デフォルトでは models.py で定義されたモデルクラスは管理画面で管理不可となっているため、管理画面で管理を行いたい場合は、上記のように admin.site.register 関数を実行する必要があります。

管理画面の詳細設定の仕方

また、フォームや一覧表の表示項目等の管理画面の詳細設定に関しても admin.site.register 関数を利用して行います。ただし、管理画面の詳細設定を行う場合、admin.site.register 関数には第1引数と第2引数の指定が必要となります。そして、第2引数には「フォームや一覧表の詳細設定を行なったクラス」を指定します。

これにより、第1引数で指定したモデルクラスのインスタンスを管理する際、例えば一覧表を表示したり追加フォームを表示したりする際には、第2引数で指定したクラスに従った表示が行われるようになります。

そして、この第2引数に指定するクラスは admin.ModelAdmin というクラスのサブクラスとなります。以降、文章中では admin.ModelAdmin を単に ModelAdmin と呼びます。

フォームや一覧表の詳細設定
from django.contrib import admin
from .models import モデルクラス

class ModelAdminのサブクラス(admin.ModelAdmin):
    # 略

admin.site.register(モデルクラス, ModelAdminのサブクラス)

ModelAdmin とサブクラス

ModelAdmin では、一覧表に表示する項目やフォームに表示する項目等が定義されています。そして、admin.site.register の第2引数を指定しなかった場合、ModelAdmin の定義に基づいて一覧表やフォームが表示されることになります。

それに対し、admin.site.register の第2引数に ModelAdmin のサブクラスを指定した場合、第1引数に指定したモデルクラスのインスタンスの管理画面は、その第2引数に指定したサブクラスに従って表示されることになります。そのため、第2引数に指定する ModelAdmin のサブクラスで、スーパークラスである ModelAdmin のクラス変数を上書きしたり、メソッドを上書きしたり新規作成したりしておけば、通常とは異なる管理画面の表示を実現することができることになります。

例えば、ModelAdmin では list_display というクラス変数が list_display = ('__str__', ) と定義されていますが、ModelAdminのサブクラス側で list_display を定義してやれば、list_display が上書きされて一覧表に表示されるフィールドを変化させることができます。

list_displayの上書き
class SubModelAdmin(admin.ModelAdmin):
    # 一覧表に__str__の実行結果とage・height・weightフィールドを表示する
    list_display = ('__str__', 'age', 'height', 'weight')

ただ、この上書きを行うためには、ModelAdmin で定義されるクラス変数やメソッド等を知っておく必要があります。次のカスタマイズの例の章でも少し例は示しますが、数が多くて本ページでは全てを紹介することができないため、ModelAdmin の定義を直接確認していただくのが良いかと思います。

この ModelAdmin は Django のインストールフォルダからの相対パスで下記の位置のファイルで定義されているため、定義を直接確認したいという方は是非参考にしてください。VSCode などを利用していれば、右クリックメニューから定義を調べるようなこともできます。

contrib/admin/options.py

カスタムユーザーを利用する場合

また、基本的には ModelAdmin のサブクラスを定義して一覧表やフォームの詳細設定を行なっていくことになるのですが、ユーザーを管理するモデル、例えば AbstractUserAbstractBaseUser を継承して定義したカスタムユーザーに対する管理画面の詳細設定を行いたい場合は、auth アプリから提供される admin.UserAdmin のサブクラスを定義して詳細設定を行うことをオススメします。

このカスタムユーザー向けの管理画面の詳細設定の行い方や、admin.UserAdmin を利用する理由については専用のページを用意して解説していますので、詳しく知りたい方は下記ページを参照していただければと思います。

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

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

 では、先ほど説明したカスタマイズの仕方を踏まえ、次は実際の管理画面のカスタマイズ例を示していきます。

スポンサーリンク

カスタマイズを行うための準備

まずはカスタマイズを行うための準備を行なっていきます。

前回の連載で管理画面の使うためには、最低限プロジェクトの作成、マイグレーション、スーパーユーザーの作成、開発用サーバーの起動が必要であると説明しました。さらに、今回はアプリ内で定義したモデルクラスを管理画面で管理できるようにする必要があるため、アプリの作成や models.py の変更を事前に行なっていきます。

プロジェクトの作成

まずは、適当な作業フォルダに移動してから下記コマンドで customadmin プロジェクトを生成します。

% django-admin startproject customadmin

これにより、customadmin というフォルダが作成されることになります。次は、cd コマンドで作成された customadmin フォルダに移動します。

% cd customadmin

アプリの作成

さらに、移動後のフォルダで下記コマンドを実行して management アプリを作成します。このアプリは生徒の管理を行うアプリを想定するものになります。

% python manage.py startapp management

続いて、今いるフォルダの中にある customadmin フォルダの settings.py を開き、下記のように INSTALLED_APPS の最初の要素に 'management', を追加して保存します。

settings.py
INSTALLED_APPS = [
    'management',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

これにより、先ほど作成したアプリ management がプロジェクトに登録され、開発用サーバー起動時等に managementmodels.pyadmin.py が読み込まれるようになります。逆に、プロジェクトにアプリの登録を行わなければ、models.pyadmin.py をどれだけ変更しても無視されることになるので注意してください。

モデルクラスの定義

次は、models.py を変更してモデルクラスの定義を行います。そして、今後の変更により、これらのモデルクラスを管理画面で管理できるようにカスタマイズを行なっていくことになります。

今回は下記の ClubStudentScore の3つのクラスを models.py で定義したいと思います。

models.py
from django.db import models

class Club(models.Model):
    name = models.CharField(max_length=256) # 名前

    def __str__(self):
        return self.name

class Student(models.Model):
    name = models.CharField(max_length=256) # 名前
    grade = models.IntegerField() # 学年
    is_staff = models.BooleanField() # 学級委員
    club = models.ForeignKey(Club, on_delete=models.CASCADE, null=True, blank=True) # 所属するクラブ

    def __str__(self):
        return self.name

class Score(models.Model):
    math = models.IntegerField() # 数学の点数
    science = models.IntegerField() # 理科の点数
    english = models.IntegerField() # 英語の点数
    average = models.FloatField() # 平均点
    student = models.OneToOneField(Student, on_delete=models.CASCADE) # 誰の点数か

    def __str__(self):
        return self.student.name + '\'s score'

Club はクラブ活動を管理するモデルクラス、Score はテストの点数を管理するモデルクラス、Student は生徒を管理するモデルクラスとなります。ClubStudent には ForeignKey でリレーションを設定しており、これにより各生徒が所属するクラブが管理できるようにしています。また、StudentScoreOneToOneField でリレーションを設定しており、各 Score がどの Student のものであるかを管理できるようにしています。

また、各モデルクラスで __str__ を定義している点は、管理画面のカスタマイズを行う上で結構重要なポイントとなります。後述でも解説しますが、管理画面からは __str__ メソッドが実行される機会が多く、管理画面をより使いやすくするためには __str__ メソッドを定義しておくほうが良いです。

マイグレーションの実施

モデルクラスの定義が済んだので、次はマイグレーションを実施して定義されているモデルクラスに対するテーブルの作成を行なっておきます。

まずは下記コマンドを実行し、先ほど定義したモデルクラスからマイグレーションの設定ファイルの作成を行います。これを忘れるとアプリ内で定義したモデルクラスに対するテーブルが作成されないので注意してください。

% python manage.py makemigrations

続いて、下記コマンドでマイグレーションを実行します。これにより、先ほど定義したモデルクラスだけでなく、settings.pyINSTALLED_APPS に指定されている各種アプリで定義されているモデルクラスに対応するテーブルがデータベースに作成されることになります。

% python manage.py migrate

今回は、models.py で定義したモデルクラスを管理画面で管理できるようにカスタマイズを行なっていくわけですから、当然 models.py で定義されたモデルクラスのテーブルが作成されることは重要となります。それに加え、管理画面へログインするためには authUser のテーブルを作成し、そのテーブルの中にスーパーユーザーのレコードを作成しておく必要があるため、上記の migrate によって authUser のテーブルが作成されることも重要となります。

スーパーユーザーの作成

次は、そのスーパーユーザーの作成を行います。

スーパーユーザーの作成は、下記コマンドを実行することで行うことができます。下記コマンドを実行するとユーザー名とメールアドレスとパスワード(2回)の入力が促されるため、それに従って情報の入力を行ってください。

% python manage.py createsuperuser

スーパーユーザーに関しては下記ページで解説していますので、スーパーユーザーについて詳しく知りたい方は下記ページを参照していただければと思います。

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

現状の管理画面の確認

続いて、現状の管理画面の確認を行なっておきます。

まず、下記コマンドで Django の開発用サーバーを起動します。これにより、ウェブアプリや管理画面にウェブブラウザからアクセス可能な状態となります。

% python manage.py runserver

続いて、ウェブブラウザから下記 URL を開いてください。

http://localhost:8000/admin/

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

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

これにより、管理画面へのログインが行われ、下図のようなページが表示されると思います。そして、そこには管理対象として GroupsUsers のみが表示されていることが確認できるはずです。

管理画面で管理可能なモデルクラスがGroupとUserのみであることを示す図

この GroupUserauth アプリで定義されているモデルクラスであるため、先ほど自身で models.py に定義したモデルクラスに関しては1つも管理対象となっていないということになります。

このように、モデルクラスを定義しても、そのままでは管理画面で管理を行うことができません。管理画面で管理を行えるようにするためには、まずそのモデルクラスを管理画面での管理対象として登録する必要があります。

ということで、現状の確認は以上となります。次は、そのモデルクラスの登録を行なっていきますが、その前に一旦開発用サーバーは終了しておいてください。開発用サーバーが起動しているターミナルやコマンドプロンプト上で ctrl + c を入力すれば、開発用サーバーは終了するはずです。

モデルクラスの登録

ということで、次は models.py で定義したモデルクラスを管理画面での管理対象として登録していきたいと思います。

これに関しては、モデルクラスの登録の仕方 で解説したように、第1引数に登録したいモデルクラスを指定して admin.site.register 関数を実行することで実現することができます。

まずは management フォルダの下にある admin.py を開き、下記のように変更して保存してください。

モデルクラスの登録
from django.contrib import admin
from .models import Student, Score, Club

admin.site.register(Student)
admin.site.register(Score)
admin.site.register(Club)

このように、モデルクラスを管理画面の管理対象として登録するだけであれば admin.site.register の第1引数に登録したいモデルクラスを指定すれば良いだけなので、非常に簡単な手順で実現することができます。

スポンサーリンク

モデルクラス登録後の管理画面の確認

admin.py の変更によってモデルクラスの登録が行われるようになったため、この登録後の管理画面の確認を行なっていきたいと思います。

まず、再度下記コマンドを実行して開発用サーバーを起動し、

% python manage.py runserver

再度ウェブブラウザから下記 URL を開いてください。

http://localhost:8000/admin/

先ほどログアウトを行なっていない場合、管理画面のトップページが直接開かれると思います。ログアウトされている場合は、再度スーパーユーザーのユーザー名とパスワードを入力してログインして管理画面のトップページを開いてください。

まず、管理画面のトップページで注目していただきたいのが、管理対象のモデルとして management アプリの models.py で定義した ClubScoreStudent の3つが表示されるようになっている点になります(リンクとしては、それぞれの名前に s が付加された複数形で表示されることになります。)。

管理対象のモデルクラスが増えている様子

そして、これらの登録されたモデルクラスのインスタンスに関しては、下記ページで説明した手順と同様の手順で追加・表示・編集・削除が行えるようになっています(表示されるフォームや一覧表は異なります)。

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

例えば、Clubs の右側にある Add リンクをクリックすれば、Club の追加フォームが表示され、Club のインスタンスの追加を行うことができます。

Clubの追加フォーム

また、Club のインスタンスの追加後に Students の右側にある Add リンクをクリックすれば Student の追加フォームが表示され、Student のインスタンスの追加を行うことができます。また、この追加フォームでは Club フィールドからリレーション構築相手となる Club のインスタンスが選択可能であることも確認できます。

Studentの追加フォーム

このように、モデルクラスの登録を行なっておけば、それらのモデルクラスのインスタンスの管理を管理画面から行うことができるようになります。まだ登録しただけでフォーム等の詳細設定は行っていませんが、それなりにしっかりした管理画面が構成されており、これでも十分インスタンスの管理を行うことができることを確認できると思います。

ただ、完璧な管理画面かといえばそうでもないです。

例えば、下記は Student のインスタンスの一覧表であり、この一覧画面には生徒の名前(name)しか表示されないようになっています。Studentname 以外にも gradeclub 等のフィールドも持っていますので、これらの情報も一覧表に表示されるとより便利になります。

変更前のStudentのインスタンスの一覧表

また、下図は Score の追加フォームとなります。Score の持つ全フィールドの情報が入力可能なフォームとなっていることが確認できると思います。もちろん、この追加フォームからも Score のインスタンスを追加することは可能ではありますが、このフォームの場合、平均点を示す Average をわざわざユーザーが計算して入力する必要があり不便です。他の科目の点数から自動的に算出するようにしてあげたほうが便利ですね。

変更前のScoreの追加フォーム

このように、モデルクラスを登録することで、そのモデルクラスのインスタンスの管理を行うことは可能です。ですが、そのままの画面だと不便な場合がありますので、そういった場合は詳細設定を行い、より便利な管理画面を実現していくことをオススメします。

ModelAdmin のサブクラスの定義

では、そういった詳細設定を行う例を示していきたいと思います。

管理画面の詳細設定の仕方 でも説明しましたが、各モデルクラスに対する管理画面(フォームやインスタンスの一覧表)は ModelAdmin というクラスに従って表示されるようになっています。つまり、これらが表示される際には ModelAdmin が参照されることになります。

そして、この参照先のクラスは、ModelAdmin のサブクラスに変更することが可能です。

したがって、ModelAdmin のサブクラスを定義し、そのサブクラスでクラス変数やメソッドを定義してスーパークラスのものを上書き、さらに、そのサブクラスの管理画面表示時の参照先のクラスとして設定してやれば、そのサブクラスに従って各モデルクラスに対するフォームやインスタンスの一覧表が表示されるようになります。

ここでは、まずは ModelAdmin のサブクラスの定義のみを行い、さらにそのサブクラスを管理画面表示時の参照先のクラスに設定していきたいと思います。

ModelAdmin のサブクラスの定義

これらに関しても admin.py で実装していくことになります。今回は、StudentScore のフォームやインスタンス一覧表の変更を行うため、まずは下記のように admin.py を変更し、StudentAdminScoreAdmin を定義します。 

ModelAdminのサブクラスの定義
from django.contrib import admin
from .models import Student, Score, Club

class StudentAdmin(admin.ModelAdmin):
    pass

class ScoreAdmin(admin.ModelAdmin):
    pass

admin.site.register(Student)
admin.site.register(Score)
admin.site.register(Club)

ひとまず、StudentAdminScoreAdmin とは ModelAdmin を継承するのみとなっています。これらは後述の説明の中で変更を行なっていきます。

各モデルクラスに対する ModelAdmin の設定

また、上記のように admin.py で StudentAdminScoreAdmin を定義したものの、これだけではこれらのクラスは管理画面表示時には参照されません。管理画面表示時に参照させるためには、そのクラスを第2引数に指定して admin.site.register を実行する必要があります。

今回は、Student に対する管理画面(フォームやインタンスの一覧表の表示画面)を StudentAdmin に従って表示されるようにし、さらに Score に対する管理画面(フォームやインタンスの一覧表の表示画面)を ScoretAdmin に従って表示されるようにしたいため、下記のように admin.py を変更します。

ModelAdminのサブクラスの定義
from django.contrib import admin
from .models import Student, Score, Club

class StudentAdmin(admin.ModelAdmin):
    pass

class ScoreAdmin(admin.ModelAdmin):
    pass

admin.site.register(Student, StudentAdmin)
admin.site.register(Score, ScoreAdmin)
admin.site.register(Club)

このように、admin.site.register の第1引数にモデルクラスを指定することで、そのモデルクラスが管理画面での管理対象として追加されることになります。さらに、admin.site.register の第2引数に ModelAdmin のサブクラスを指定することで、第1引数に指定したモデルクラスを管理する画面が第2引数に指定したクラスに基づいて表示されるようになります。

register関数の引数の意味を示す図

フォームの変更

ということで、先ほどの admin.py の変更によって、Student に対する管理画面と Score に対する管理画面が、それぞれ StudentAdminScoreAdmin に従って表示されるようになります。なので、StudentAdmin や ScoreAdmin を変更してやれば、それに従って表示される管理画面が変化することになります。

その変更の実例を、ここからいくつか紹介していきたいと思います。

まず、ScoreAdmin を変更し、今まで下図のように追加フォームや編集フォームで表示されていた average を削除するようにしたいと思います。

Scoreの追加フォーム

そして、その代わりに、フォームから SAVE ボタンがクリックされてフォームの各種フィールドの値がレコードとしてデータベースに保存される際に、average のフィールドの値を自動的に算出して average の値も保存されるようにしたいと思います。

これにより、average のフィールドの値入力時にユーザーは平均値を計算する手間がなくなり、Score のインスタンスの追加が楽になります。

フォームからのフィールドの削除

まず、average フィールドが表示されないように、フォームから average フィールドの削除を行います。

ModelAdmin クラスでは、追加フォームや編集フォームで基本的にモデルクラスの持つ全フィールドの入力を受け付けるようになっています。Score モデルクラスは models.py で下記のように定義しているため、上の図で示すように、追加フォームや編集フォームでは mathscienceenglishaveragestudent の全てのフィールドの入力を受け付けるようになっています。

Score
class Score(models.Model):
    math = models.IntegerField() # 数学の点数
    science = models.IntegerField() # 理科の点数
    english = models.IntegerField() # 英語の点数
    average = models.FloatField() # 平均点
    student = models.OneToOneField(Student, on_delete=models.CASCADE) # 誰の点数か

    def __str__(self):
        return self.student.name + '\'s score'

全てのフィールドの入力受付を行うのではなく、入力受付を行うフィールドを限定したい場合、ModelAdmin のサブクラス側で fields もしくは exclude というクラス変数のいずれかを定義する必要があります。

fields では、モデルクラスの持つフィールドのうち、入力受付を行いたいフィールドをリスト or タプル形式で指定します。exclude では入力受付を行いたくなフィールドをリスト or タプル形式で指定します。今回は average の入力受付を行いたくないのですから、下記のいずれかを指定すれば良いことになります。

  • fields = ('math', 'science', 'english', 'student')
  • exclude = ('average',)

ということで、今回は exclude を利用して下記のように ScoreAdmin を変更したいと思います。これにより、フォームから average フィールドが消えます。

averageフィールドの削除
class ScoreAdmin(admin.ModelAdmin):
    exclude = ('average',)

フィールドが消える理由をもう少し詳しく説明しておくと、excludeModelAdmin の持つクラス変数であり、exclude = None として定義されています。なので、基本的にはモデルクラスの持つフィールドが全てフォームに表示されるようになっています。

ですが、スーパークラス側、つまり ModelAdmin 側の持つクラス変数をサブクラス側でも定義すれば、サブクラス側の定義によってクラス変数が上書きされ、それを利用して処理が実行されることになります。今回の場合は、その上書きされたクラス変数に従って管理画面が表示されることになります。ですので、ModelAdmin のサブクラス側で exclude を定義してやれば、その exclude の定義に従って管理画面が表示されることになり、このクラス変数の場合は指定されたフィールドがフォームから削除されることになります。

この excludeModelAdmin の持つクラス変数の一例であり、他のクラス変数も同様に上書きを行うことで管理画面の表示を変更することが可能となります。その例は インスタンスの一覧表の変更 でも紹介します。

保存時の処理の上書き

上記の ScoreAdmin の変更によって、フォームから average フィールドが消えることになります。これにより、ユーザーは average フィールドの値の入力が不要となります。ですが、当然 Score モデルクラスには average フィールドが存在しています。

したがって、例えば Score の追加フォームで Score のインスタンスの追加を行おうとしても、現状のフォームには average フィールドが存在しないため、インスタンスの average フィールドには値がセットされないままデータベースへの保存が行われることになります。average フィールドは必須フィールドとなっていますので、値がセットされないままデータベースに保存しようとすると例外が発生することになります。つまり、現状ではフォームで SAVE ボタンがクリックされた際には必ず例外が発生します。

これを防ぐためには、SAVE ボタンがクリックされた際に Score のインスタンスの average フィールドに各教科の平均値をセットするように処理を変更してやる必要があります。つまり、SAVE ボタンがクリックされた際に実行されるメソッドを変更する必要があります。

SAVE ボタンがクリックされた際に実行されるメソッドはいくつかあるのですが、インスタンスの保存が行われるのは ModelAdmin クラスの save_model というメソッドになります。

Modelminのsave_model
def save_model(self, request, obj, form, change):
    """
    Given a model instance save it to the database.
    """
    obj.save()

したがって、ModelAdmin のサブクラスである ScoreAdminsave_model というメソッドを追加してやれば、save_model メソッドのオーバーライド、つまり上書きが行われ、save_model が実行された際には ModelAdmin のものではなく ScoreAdmin で定義された save_model が実行されることになります。

save_modelメソッドを上書きする様子

なので、ScoreAdmin に save_model メソッドを定義し、その中で average フィールドの値をセットするようにしてやれば、上記の例外が発生する問題は解決することができます。

具体的には、下記のような save_model メソッドを ScoreAdmin クラスに定義してやれば良いです。

save_modelのオーバーライド
class ScoreAdmin(admin.ModelAdmin):
    exclude = ('average',)

    def save_model(self, request, obj, form, change):
        obj.average = (obj.math + obj.science + obj.english) / 3 
        super().save_model(request, obj, form, change)

ポイントは、引数をスーパークラス、すなわち ModelAdminsave_model メソッドに合わせる必要がある点になります。また、保存するインスタンスは引数 obj として渡されてくることになりますので、objaverage に平均値の算出結果をセットする必要があります。

このように、メソッドのオーバーライドの仕組みを利用すれば、フォームからの操作時の処理を上書きして変更するようなことも可能です。

補足しておくと、今回は ScoreAdmin でのオーバーライドを利用しましたが、実は Score モデルクラスに save メソッドを追加してオーバーライドを行い、そこで average に値をセットするのでも良いです。むしろそちらの方が適切だと思います。が、今回は管理画面のカスタマイズを行う上でオーバーライドも利用できるという点を説明したかったため、あえて ScoreAdmin でのオーバーライドを利用した例を示しています。

スポンサーリンク

インスタンスの一覧表の変更

続いて、インスタンスの一覧表の変更を行なっていきたいと思います。例えば、現状の Student のインスタンスの一覧表は下の図のようになっており、生徒の名前のみが表示されるようになっています。

現状のStudentのインスタンスの一覧表

Student には、名前だけでなく学年を示す grade フィールドが存在しますし、学級委員かどうかを示す is_staff フィールド、所属するクラブを示す club フィールドも存在しています。また、StudentScore との間にリレーションが設定されているため、生徒のテストの点数も管理されていることになります。これらの情報も表示されると、一覧表から読み取れる情報が多くなって、より便利になります。ということで、まずは一覧表に表示されるフィールドを追加していきます。

また、前回の連載で User のインスタンス一覧を確認しましたが、User の一覧表のページには、下の図の上側部分のようにサーチバーが存在していたり、右側部分のようにフィルター機能が存在したりしています。これについても Student の一覧表のページに追加を行なっていきたいと思います。

Userのインスタンスの一覧表

一覧表の表示フィールドの追加(フィールドの表示)

まずは、一覧表の表示フィールド(表示項目・表示列)の追加を行なっていきたいと思います。

フォームの変更 ではフォームに表示するフィールドの削除を行うために、ModelAdmin のサブクラスで exclude というクラス変数の定義を行いました。

同様に、ModelAdmin のサブクラスでクラス変数を定義することで、一覧表の表示フィールドの変更を行うことが可能となります。この場合は、list_display というクラス変数を定義することになります。list_display に表示したいフィールド名を要素とするリスト or タプルを指定してやれば、そのフィールドが一覧表に表示されるようになります。

ここで、ModelAdmin での list_display の定義を確認してみましょう!ModelAdmin は list_display が下記のように定義されています。

ModelAdminでのlist_displayの定義
list_display = ('__str__',)

この '__str__' の意味合いについては次の 一覧表の表示フィールドの追加(メソッドの実行) で解説しますが、これはモデルクラスの__str__ メソッドの実行結果を表示することを指示する指定になります。上記のように list_display に指定されているタプルには '__str__'  の要素しか存在しませんので、現状の一覧表には、各インスタンスに対して __str__ メソッドの実行結果のみが表示されるようになっていることになります。

一覧表に表示されているフィールドがlist_displayの指定に従っていることを示す図

このように、一覧表で表示されるフィールドは list_display の指定によって決まります。そのため、ModelAdmin のサブクラス側で list_display を定義してやれば、list_display の定義が上書きされ、その list_display への指定に基づいて一覧表が表示されるようになります。

今回は、まずは Student の持つ全フィールドを表示するように list_display を定義したいと思います。Student は モデルクラスの定義 で定義を行なっており、namegradeis_staffclub のフィールドを持っています。そのため、これらのフィールドを要素とするタプルを list_display に指定するように、下記のように StudentAdmin の定義を変更したいと思います。

一覧表への表示フィールドの追加
class StudentAdmin(admin.ModelAdmin):
    list_display = ('name', 'grade', 'is_staff', 'club')

このように定義を変更すれば、Student のインスタンスの一覧表が下の図のように変化します。list_display に指定したリストに従って表示されるフィールドが変化していることが確認できると思います。

list_displayにフィールド追加後の一覧表

ただ、上の図を見てみると、以前に示したものに比べて1列目のフィールド名(列名)が STUDENT から NAME に変わっていることが確認できると思います。

これは、list_display の最初の要素を '__str__' から name に変更したためです。フィールド名(列名)としては、基本的に list_display に指定したものが表示されることになるのですが、'__str__' を指定した場合はモデルクラス名がそのまま出力されることになります。そして、各フィールド(セル)には __str__ メソッドの実行結果が表示されることになります。

Student__str__ メソッドはインスタンスの name フィールドを返却するようになっていますので、list_display'__str__' を指定した場合と name を指定した場合とでは、各フィールド(セル)の表示結果は同じことになります。が、フィールド名(列名)は異なることになるので注意してください。

また、'__str__' を指定した場合は、モデルクラスに __str__ が用意されていないと、下図のようなオブジェクト名がそのまま表示されることになるので注意してください。

__str__メソッドが定義されていない場合の表示結果

また、club フィールドに関しては Club とのリレーションを設定する ForeignKey となっているのですが、このような場合でも、それぞれの Student のインスタンスとリレーションが構築されている Club の情報が表示されるようになっています。そして、この場合も、Club の情報としては Club__str__ メソッドの実行結果が表示されることになります。前述の通り、Club__str__  メソッドを定義していない場合はオブジェクト名がそのまま表示されることになるので注意してください。

ここでの説明でも分かるように、管理画面ではモデルクラスの __str__  メソッドが利用されることが多いです。管理画面を使いやすくするためにも __str__ メソッドをモデルクラスに定義しておくことをオススメします。

一覧表の表示フィールドの追加(メソッドの実行)

さて、先ほど Student のインスタンス一覧表に追加したフィールドは全て、Student の持つフィールドとなります。モデルクラスの持つフィールドを list_display に指定した場合、各インスタンスのフィールドにセットされている値がそのまま一覧表に表示されることになります(is_staff のように CSS などによって装飾されたりするものもありますが)。

各種フィールドにセットされている値が出力される様子

では、モデルクラスの持たないフィールドを要素に持つリストやタプルを list_display に指定した場合はどうなるでしょうか?

この場合、そのモデルクラスの管理画面を定義する  ModelAdmin のサブクラス or そのモデルクラスに定義された、その要素の文字列と一致する名前のメソッドが呼ばれることになります。

この文章で内容が伝わるか不安なので、実例を踏まえて説明します。例えば、下記のように StudentAdmin を定義したとします。

存在しないフィールドの表示
class StudentAdmin(admin.ModelAdmin):
    list_display = ('name', 'grade', 'is_staff', 'club', 'rank')

    def rank(self, obj):
        if not hasattr(obj, 'score'):
            return 'N/A'
        
        scores = Score.objects.order_by('average').reverse()
        i = 0
        for score in scores:
            if score.student.grade == obj.grade:
                i += 1
                if score.student.id == obj.id:
                    break

        return i

この場合、list_display には Student のフィールドには存在しない 'rank' が指定されています。そのため、Student のインスタンスの一覧表示時には StudentAdminrank メソッドが実行され、その実行結果(return した値)が一覧表の rank フィールドに表示されることになります。

この実行されるメソッドの引数は selfobj となり、self が StudentAdmin のインスタンス、obj が Student のインスタンスとなります。特に、Student のインスタンスが引数で渡されて利用可能となるため、Student のインスタンスの持つデータ属性等を利用して様々な処理を実行することが可能となります。上記の rank では、obj と同じ学年(grade)の中でのテストの点数の平均点(average)の順位を返却するようにしています。メソッドの処理の書き方がイマイチな気もしますが、obj の情報や他のモデルクラスを利用した様々な処理が実現可能であることは理解していただけるのでは無いかと思います。

実際の rank フィールドを追加した Student のインスタンスの一覧表の表示結果は下の図のようになります。前述の通り、RANK の列の各フィールドには rank メソッドの return した値が表示されています。

rankフィールドを追加した一覧表

このように、list_display にモデルクラスのフィールドに存在しないフィールド名が指定された場合、そのフィールド名と同じ名前のメソッドが実行され、その実行結果がそのフィールドに表示されるようになります。このメソッドは、ModelAdmin のサブクラス側に用意しても良いですし、モデルクラス側に用意しても良いです(モデルクラス側に用意する場合は、引数 obj が不要となります)。

この両方にフィールド名と名前が一致するメソッドが存在する場合、ModelAdmin のサブクラス側のものが優先して実行されることになります。逆に、その両方にフィールド名と名前が一致するメソッドが存在しない場合は例外が発生することになります。

基本的には、ビュー等からも利用される可能性のあるメソッドであればモデルクラス側に用意してやれば良いと思います。例えば、上記の rank メソッドは StudentAdmin に用意しているため、基本的には管理画面からのみ利用されるメソッドとなります。ビュー等から利用したい場合は、Studen 側にメソッドを移動してあげた方が良いです。

モデルクラスへのメソッドの移動
class Student(models.Model):
    # 略
    
    def rank(self):
        obj = self
        if not hasattr(obj, 'score'):
            return 'N/A'
        
        scores = Score.objects.order_by('average').reverse()
        i = 0
        for score in scores:
            if score.student.grade == obj.grade:
                i += 1
                if score.student.id == obj.id:
                    break

        return i

フィルターの追加

続いてフィルター機能の追加を行なっていきます。フィルター機能を利用することにより、一覧表に表示されるインスタンスを「指定した条件に当てはまるもののみ」に絞り込むことができます。例えば Student の例であれば、一覧表に is_staffTrue のインスタンスのみを表示したり、grade2 のインスタンスのみを表示したりするようなことができます。

このフィルター機能についても、今まで通り ModelAdmin のサブクラスにクラス変数を定義することで実現することができます。フィルター機能を実現するために定義が必要なクラス変数は list_filter となります。この list_filter に関してもリスト形式 or タプル形式のデータを指定する必要があり、各要素には、一覧表に表示するインスタンスの「表示条件となるフィールド」の名前を指定します。

例えば下記のように StudentAdmin を変更すれば、is_staff フィールドと grade フィールドの2つが表示条件として追加されることになります。

list_filterの定義
class StudentAdmin(admin.ModelAdmin):
    list_display = ('name', 'grade', 'is_staff', 'club', 'rank')
    list_filter = ('is_staff', 'grade')

    def rank(self, obj):
        # 略

このように、list_filter の定義を行なった場合、下の図で示すように一覧表の右側に FILTER というセクションが表示されるようになります。

list_filterを定義した場合の一覧表

この FILTER というセクションで、例えば By is staff の下側に表示されている Yes をクリックすれば、一覧表に表示される Student のインスタンスは is_staff フィールドが True のもののみに絞り込まれることになります。

フィルター機能の利用例

同様に、By grade の下側に表示さている 2 をクリックすれば、一覧表に表示される Student のインスタンスは grade フィールドが 2 のもののみに絞り込まれることになります。

このように、ModelAdmin のサブクラスで list_filter を定義して表示条件となるフィールド名を指定しておけば、一覧表の右側に表示する条件となる選択肢がリンクとして表示されるようになります。そして、そのリンクをクリックすることで一覧表に表示されるインスタンスを絞り込むことができるようになります。

サーチバーの追加

最後にサーチバーの追加を行なっていきます。このサーチバーを追加することで、一覧表から特定のインスタンスを検索できるようになります。

例の如く実現方法は今までと同様で、ModelAdmin のサブクラスに search_fields を定義してやれば良いだけです。これも今までと同様で、search_fields には検索対象とするフィールド名を要素とするリスト or タプルを指定します。

例えば下記のように StudentAdmin を変更すれば、一覧表の上側にサーチバーが追加され、そのサーチバーで name フィールドに対する検索を行うことができるようになります。

search_fieldsの定義
class StudentAdmin(admin.ModelAdmin):
    list_display = ('name', 'grade', 'is_staff', 'club', 'rank')
    list_filter = ('is_staff', 'grade')
    search_fields = ('name',)

    def rank(self, obj):
        # 略

実際に Student のインスタンスの一覧表を表示した結果が下の図となり、一覧表の上側にサーチバーが追加されていることが確認できます。

サーチバーが追加された様子

さらに、サーチバーに yamada と入力して Search ボタンをクリックすれば、name フィールドに yamada という文字列が含まれるインスタンスのみが一覧表に表示されることになります。

サーチバーの利用例

カスタマイズ例のまとめ

ここまで説明してきた内容を反映した admin.py は下記のようになります。基本的に、今まで紹介したソースコードを統合したものになりますが、ScoreAdminlist_display の定義を追加したりもしています。いずれにしても、ここまで解説を読んでくださった方であれば、各クラス変数やメソッドの意味合いは理解していただけると思います。

admin.py
from django.contrib import admin
from .models import Student, Score, Club

class StudentAdmin(admin.ModelAdmin):
    list_display = ('name', 'grade', 'is_staff', 'club', 'rank')
    list_filter = ('is_staff', 'grade')
    search_fields = ('name',)

    def rank(self, obj):
        if not hasattr(obj, 'score'):
            return 'N/A'
        
        scores = Score.objects.order_by('average').reverse()
        i = 0
        for score in scores:
            if score.student.grade == obj.grade:
                i += 1
                if score.student.id == obj.id:
                    break

        return i

class ScoreAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'math', 'science', 'english', 'average')
    exclude = ('average',)

    def save_model(self, request, obj, form, change):
        obj.average = (obj.math + obj.science + obj.english) / 3 
        super().save_model(request, obj, form, change)


class ClubAdmin(admin.ModelAdmin):
    pass
    
admin.site.register(Student, StudentAdmin)
admin.site.register(Score, ScoreAdmin)
admin.site.register(Club, ClubAdmin)

admin.py を上記のように変更した後に開発用サーバーを起動して管理画面にログインすれば、最初にログインした時とは各モデルクラスのインスタンスの一覧表の見た目や、特に Score に対するフォームで average の入力が不要になったことが確認できると思います。そして、このように管理画面が変化したのは、ModelAdmin のサブクラスの定義の変更を行なったからになります。

このように、ModelAdmin のサブクラスの定義によって各モデルクラスに対する管理画面を自分好みのものに変更することが可能です。いくつか例を示してきましたが、これらは変更例のほんの一部であり、ModelAdmin のサブクラスの変更によって様々な管理画面を実現することができます。

基本的には、この管理画面の変更は ModelAdmin のサブクラス側で ModelAdmin のクラス変数・メソッドの上書きによって実現することができますので、もっと色んな変更をしてみたいという方は、是非 ModelAdmin に存在するクラス変数やメソッドを調べてみていただければと思います。

また、このスーパークラス側のクラス変数やメソッドの上書きに関しては、Django では管理画面のカスタマイズだけでなく様々な場面で使える考え方になりますので、是非この点については覚えておいてください。

掲示板アプリで管理画面をカスタマイズしてみる

最後に、いつも通りの流れで、この Django 入門の連載の中で開発してきている掲示板アプリに対し、管理画面のカスタマイズを行なっていきたいと思います。

この Django 入門に関しては連載形式となっており、ここでは以前に下記ページの アプリへのログイン機能の搭載 で作成したウェブアプリに対して管理画面のカスタマイズを行う例を示していきたいと思います。

Djangoのウェブアプリに対するログイン機能搭載手順の説明ページアイキャッチ 【Django入門9】アプリにログイン機能を搭載する

といっても、ここまでの解説でカスタマイズの例は十分示せたと思っていますので、今回はサラッと簡単にカスタマイズを行なっていきたいと思います。

スポンサーリンク

admin.py の変更

今まで管理画面のカスタマイズを customadmin プロジェクトで実施してきましたが、ここからは掲示板アプリで管理画面のカスタマイズを行なっていくことになるため、customadmin プロジェクトで起動した開発用サーバーを終了し(runserver コマンド実行中のターミナル等で ctrl + c を入力)、掲示板アプリを開発しているプロジェクトである testproject のフォルダに移動してください。

さらに、その移動後に、forum フォルダの下にある admin.py を開き、下記のように変更を行なってください。

admin.py
from django.contrib import admin
from .models import Comment, CustomUser
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _

class CommentAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'user')

class CustomUserAdmin(UserAdmin):
    list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff', 'age')

    fieldsets = (
        (None, {'fields': ('username', 'age', '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', 'age', 'password1', 'password2'),
        }),
    )

admin.site.register(CustomUser, CustomUserAdmin)
admin.site.register(Comment, CommentAdmin)

CommentAdmin に関しては、今までの例に比べるとかなり簡単な定義になっているので説明は不要だと思います。

それに対し、CustomUserAdmin の定義は非常に複雑に思えます。が、実はこれも基本的な考え方は今までの説明と同様になります。要は、スーパークラスのクラス変数をサブクラス側で上書きしているだけです。

CustomUserAdminUserAdmin のサブクラスであり、UserAdmin では list_displayfieldsetsadd_fieldsets の定義は下記のようになっています。

UserAdminでのクラス変数の定義
class UserAdmin(admin.ModelAdmin):

    list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
    
    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'),
        }),
    )

つまり、CustomUserAdmin では、UserAdminlist_displayfieldsetsadd_fieldsets それぞれに対して age フィールドを追加しているだけです。そして、これに伴い、インスタンスの一覧表・編集フォーム・追加フォームそれぞれに age フィールドが表示されるようになります。

要は、ModelAdmin のサブクラス側でスーパークラス側のクラス変数を上書きするという考え方に変わりはありません。UserAdminModelAdmin のサブクラスですので、UserAdmin のサブクラスも ModelAdmin のサブクラスと考えられます。

そして、この UserAdminauth というアプリで定義されたクラスであり、User の管理専用にカスタマイズされたクラスとなります。それを継承する形で CustomUserAdmin を定義することで、User ではなく CustomUser の管理専用のクラスにカスタマイズしています。具体的には、CustomUser では User に比べて age フィールドを追加で定義しているため、この age フィールドも扱えるように list_displayfieldsetsadd_fieldsets'age' を追加しているというわけです。

UserAdmin では ModelAdmin に比べて追加フォームの表示項目を設定するクラス変数として add_fieldsets が追加されているという違いはあるものの、基本的には継承するサブクラスのクラス変数を上書きするという考え方に基づいて実装すれば良いだけになります。

また、この UserAdmin に関しては下記ページで解説していますので、詳しくは下記ページを参照していただければと思います。

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

models.py の変更

以上により、管理画面のカスタマイズに関しては完了となります。

ということで、管理画面にログインしてカスタマイズ後の管理画面の確認をしていきましょう!… と言いたいところですが、実は現状の掲示板アプリでは管理画面にログインするための準備がまだ不十分です。

なぜかというと、それは、「現状の掲示板アプリではスーパーユーザーが作成できない」からになります。

実際に createsuperuser コマンドでスーパーユーザーを作成しようとすると、下記のような例外が発生することになります。

% python manage.py createsuperuser
ユーザー名: adminuser
メールアドレス: admin@example.com
Password: 
Password (again): 

〜略〜

django.db.utils.IntegrityError: NOT NULL constraint failed: forum_customuser.age

発生する例外の意味は下記となります。

age フィールドが NULL であることは許されないよ!

現状の models.py を確認していただければ分かると思いますが、CustomUser モデルクラスでは age フィールドを定義しています。そして、この age フィールドは必須フィールドとなっています。そのため、age フィールドに何かしらの値を入力してからインスタンスを保存するようにしないと上記のような例外が発生することになります。

CustomUser
class CustomUser(AbstractUser):
    age = models.IntegerField(validators=[MinValueValidator(min_age), MaxValueValidator(max_age)])

ですが、createsuperuser コマンドでは age フィールドの入力受付が行われないため、現状どう足掻いても createsuperuser コマンドでスーパーユーザーが作成できないことになります。

ポイントは、この createsuperuser コマンドは、作成するユーザーが auth アプリの User モデルクラスのものであることを前提に作られているという点になります。User モデルクラスには age フィールドなど存在しないため、createsuperuser コマンドで age フィールドの入力受付は行われないのです…。

ただ、回避策はいくつかあって、1つは User モデルクラスに存在しないフィールドを必須項目ではなく任意項目に設定してやることになります。これは、Field のサブクラスのコンストラクタ(age の場合は IntegerField())に null=True を指定してやることで実現できます。これだけで createsuperuser コマンドが成功するようになります。

もう1つの方法は createsuperuser コマンドで入力受付を行うフィールドに age を追加してやることになります。createsuperuser コマンドでは REQUIRED_FIELDS というクラス変数にリスト形式で指定されたフィールドの入力受付が行われるようになっています。そのため、age を追加したリストを REQUIRED_FIELDS に指定してやれば、age の入力受付が行われるようになり、入力した値がセットされた状態のレコードがデータベースに保存されるようになります。そのため、上記のような例外の発生を防ぐことができます。

今回は後者の方法で例外を防ぐこととし、models.py を下記のように変更したいと思います。AbstractUser では REQUIRED_FIELDS = ['email'] が定義されているため、右辺のリストに 'age' を追加するようにしています。

変更後のmodels.py
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.contrib.auth.models import AbstractUser

max_age = 200
min_age = 0

class CustomUser(AbstractUser):
    age = models.IntegerField(validators=[MinValueValidator(min_age), MaxValueValidator(max_age)])

    REQUIRED_FIELDS = ['email', 'age']

class Comment(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, related_name='comments')
    text = models.CharField(max_length=256)
    date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.text[:10]

どちらかというと、これは AbstractUser を継承するモデルクラス利用時の注意点の話となるので、このページの本題とは逸れる解説となりますが、前述の UserAdmin 同様に、Django フレームワークから提供される機能はユーザー管理モデルとして  auth の User が利用されることを前提としたものが多いです。この点をまとめて説明するため、今回の createsuperuser コマンドの注意点に関してもこのページで説明させていただくことにしました。

動作確認

ということで、これで管理画面のカスタマイズ結果を確認可能な準備が整ったことになります。

次は、実際にカスタマイズ後の管理画面を利用して動作確認を行なっていきましょう!基本的な管理画面の使い方は下記ページで解説していますので、まだ使ったことがない方は下記ページも参考にしながら使ってみてください。

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

スーパーユーザーの作成

まず、管理画面へログインするためにスーパーユーザーを作成しましょう!testproject フォルダの中に移動し、そのフォルダにある manage.py を利用して下記のように createsuperuser コマンドを実行してください。既に存在しているユーザー名を指定するとエラーとなるので注意してください。

% python manage.py createsuperuser                           
ユーザー名: YamadaAdm
メールアドレス: adm@example.com
Age: 18
Password: 
Password (again): 
Superuser created successfully.

REQUIRED_FIELDS の追加により、今回は age フィールドの入力受付が行われ、正常にスーパーユーザーを作成することができると思います。

開発用サーバーの起動

次は、下記コマンドを実行して開発用サーバーを起動してください。他のプロジェクトの開発用サーバーを起動していると、ポートが既に使用されていて起動に失敗することになりますので注意してください。

% python manage.py runserver

管理画面へのログイン

続いてウェブブラウザを起動し、下記 URL を開いてください。

http://localhost:8000/admin/

これにより、下図のような管理画面のログインフォームが表示されるはずです。ユーザー名とパスワードには、先ほどスーパーユーザー作成時に指定したものを入力してください。そして、入力後に ログイン ボタンをクリックしてください。

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

ログインに成功すれば、下の図のような管理画面のトップページに遷移するはずです。

掲示板アプリにおける管理画面のトップページ

この画面は、管理画面のカスタマイズ例 で確認した管理画面と比較して大きく異なる点が2つあります。

1つ目の違いは、表示される言語が異なる点になります。上の図のように testproject の管理画面は日本語化されています。この日本語化が行われている理由は settings.py で LANGUAGE_CODE を下記のように指定しているからになります。

LANGUAGE_CODE
LANGUAGE_CODE = 'ja'

このように、LANGUAGE_CODE の指定によって管理画面の大部分は指定された言語に翻訳されて表示されることになります。独自で定義したモデルクラスの名前などは一部翻訳されていませんが、これらも日本語で表示させようと思えば可能です。

2つ目の違いは、日本語化されているので少し分かりにくいですが、ユーザー  モデルクラス (Users) が 管理画面のカスタマイズ例 では AUTHENTICATION AND AUTHORIZATION に存在していたのに対し、上図においては 認証と認可 から消え、FORUM に移動している点となります。認証と認可AUTHENTICATION AND AUTHORIZATION を日本語化した結果であり、両方とも auth アプリであることを示しています。

authアプリにUsersが存在する例

forum アプリの admin.pyadmin.site.register(CustomUser, CustomUserAdmin) を実行しているため、FORUMユーザー が存在していることに関しては、まぁ納得できるのではないかと思います。

では、auth から ユーザー が消えている理由は何なのでしょうか?

結論としては、これも settings.py による設定が主な理由となります。ログイン機能を掲示板アプリに搭載する際、下記のように settings.pyAUTH_USER_MODEL の指定を行い、AUTH_USER_MODEL のデフォルト値  'auth.User' から 'forum.CustomUser' への変更を行なっています。

AUTH_USER_MODEL
AUTH_USER_MODEL = 'forum.CustomUser'

このように、AUTH_USER_MODEL の変更を行なった場合、auth アプリ側では ユーザー の管理画面への登録が行われないようです。なので、auth アプリには ユーザー モデルクラスは表示されません。

逆に、AUTH_USER_MODEL を変更しなかった場合は、auth アプリ側で ユーザー の管理画面への登録が行われることになり、auth アプリには ユーザー が存在することになります。管理画面のカスタマイズ例 では AUTH_USER_MODEL の変更を行なっていないため、auth アプリには ユーザー (Users) が存在していたということになります。

CustomUserAdmin の確認

話が少し逸れましたが、次は管理画面のトップページにおける ユーザー の右側にある 追加 リンクをクリックしてみてください。

クリックすれば、下の図のような画面に遷移し、ここで表示されるフォームから CustomUser のインスタンスの追加を行うことができます。

CustomUserの追加フォーム

このインスタンスの追加フォームに age フィールドが存在するのは CustomUserAdminadd_fieldsets を追加し、そこで 'age' を指定しているからななります。

また、インスタンスを追加して一覧表を表示すれば下図のように AGE フィールド表示されていることが確認できると思います。これも CustomUserAdminlist_display の定義によるものになります。

CustomUserのインスタンス一覧表

同様に編集フォームでも age フィールドの変更が可能であることが確認できると思います。

このように、CustomUser を管理するページやフォームは CustomUserAdmin に従って表示されるようになっています。今回の場合は age フィールドが表示されるようになっているのは CustomUserAdmin をそのように定義したからになります。もし CustumUserAdmin ではなく、UserAdmin をそのまま利用した場合は追加フォームに age フィールドが存在しないため、追加時に例外が発生することになるので注意してください。

CommentAdmin の確認

次は CommentAdmin 側の確認を行なっていきましょう!

まずトップページから Comments リンクの右側にある 追加 リンクをクリックしてください。

クリックすれば追加フォームが表示され Comment のインスタンスの追加が行えることが確認できると思います。

Commentの追加フォーム

このフォームのポイントは、user がプルダウンメニューになっており、そこから CustomUser のインスタンスを選択する形式になっている点になると思います。リレーションを設定するフィールドの場合、このようにプルダウンメニューでインスタンスを選択する形式の入力フィールドが自動的に用意されることになります。

リレーションの構築相手となるCustomUserのインスタンスをプルダウンメニューから選択可能になっている様子

また、インスタンスの追加後に一覧表を表示すれば、下の図のように COMMENT (モデルクラス名) と USER のフィールドが表示されていることが確認できると思います。これも CommentAdmin の定義に従って表示された結果ですね!

Commentのインスタンス一覧表

以上で動作確認は完了となります。いきなりカスタマイズ後の管理画面を利用したのでカスタマイズの効果が分かりにくかったかもしれないですね…。

そういった方は、是非 admin.py を自身で変更してみて、変更前後の管理画面の違いを確認してみていただければと思います。

スポンサーリンク

まとめ

このページでは Django における管理画面のカスタマイズについて解説しました。

デフォルトの管理画面で管理可能なモデルクラスは authUserGroup のみとなりますが、admin.site.register の実行により、自身で定義したモデルクラスも管理画面での管理対象として追加することができます。そして、管理対象として追加されたモデルクラスの管理画面は、基本的に ModelAdmin の定義に従って表示されるようになっています。

また、ModelAdmin のサブクラスを定義することで、各モデルクラスを管理する画面の詳細設定、例えばフォームに表示するフィールドやインスタンスの一覧表に表示するフィールドを追加したり削除したりするようなことも可能となっています。

基本的には、自身で定義したモデルクラスを管理したいだけであれば、ModelAdmin のサブクラスの定義は不要です。この場合、admin.site.register の第1引数に管理したいモデルクラスを定義すれば良いだけなので手順は非常に簡単だと思います。ModelAdmin のサブクラスの定義が必要なのは、管理画面をより使いやすくしたい場合くらいになると思います。

ただし、AbstractUser を継承するモデルを扱う場合は admin.site.register の第2引数に UserAdmin のサブクラスを指定し、ユーザーの扱いに適した管理画面に設定してあげた方が良いです。さらに、AbstractUser にフィールドを追加したりしている場合は、そのフィールドの変更に応じた UserAdmin のサブクラスを定義する必要がある点にも注意してください。UserAdmin はあくまでも User を管理するためのクラスとなります。

この辺りについては下記ページで解説していますので、詳しくは下記ページを参照してください。

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

次の Django 入門の連載では「ページネーション」について解説を行います!下記ページから次の連載を読むことができますので、是非読んでみてください!

Djangoにおけるページネーションの解説ページアイキャッチ 【Django入門12】ページネーションの基本

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