【Django】複数のモデルを同一の管理ページで編集する方法

※本サイトにはプロモーション・広告が含まれています。

(最終更新月:2021年12月)

「関連のある2つのモデルクラスを同一ページで編集できるようにしたい!」

「Adminページをカスタマイズしたい!」

というDjangoユーザーの方向けの記事となります

当記事を通じて、

  • ユーザーモデルクラスのための「UserAdmin」でオリジナルのフォームを適用する方法
  • 編集フォームのカスタマイズ実例

について解説します

✔YouTube解説動画

当記事の内容は動画を見ながら進めると、約15分程度で完了します。

動画ならではの情報も解説しているので、記事と一緒にご覧ください。

動画の概要欄には、単元ごとのコードを紹介しているgithubページも載せています。

筆者プロフィール

筆者プロフィールアイコン

【現職】プロダクトマネージャー

【副業】ブログ(月間17万PV)/YouTube/Web・アプリ制作

「プログラミング × ライティング × 営業」の経験を活かし、30後半からのIT系職へシフト。当サイトでは、実際に手を動かせるWebアプリの開発を通じて、プログラミングはもちろん、IT職に必要な情報を提供していきます。

【当ブログで紹介しているサイト】

当サイトチュートリアルで作成したデモ版日報アプリ

Django × Reactで開発したツール系Webアプリ

✔人に見せても恥ずかしくないコードを書こう

「リーダブルコード」は、わかりやすく良いコードの定義を教えてくれる本です。

  • 見るからにきれいなコードの書き方
  • コードの分割方法
  • 変数や関数の命名規則

エンジニアのスタンダートとすべき基準を一から解説しています。

何回も読むのに値する本なので、ぜひ手にとって読んでみてください。

UserAdminにカスタマイズしたフォームを適用する

ユーザーモデルクラスのカスタマイズするためのUserAdminでは、以下の変数に入れることで独自のフォームを使用できます。

  • add_form変数:新規登録フォーム
  • form変数:編集ページ

また、フィールドの表示のみカスタマイズしたい場合は以下の変数にそのラベルやフィールドを入力すればOKです。

  • add_fieldsets変数:新規登録のためのフィールド
  • fieldsets変数:編集のためのフィールド

変数への入れ方としてはこちらに詳しく書いています。

コードの中では下記のようになります

from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

class UserAdmin(BaseUserAdmin):
   #add_form = ここに新規登録フォームを格納しよう!
    #form = ここに編集フォームを格納しよう
    
    ##...割愛...

    #新規登録フォームの新たなフィールドはここで追加します
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2')}
        ),
    )

    #編集フォームの新たなフィールドはここで追加します
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        #("見出し", {"fields": (フィールド名1, ...)}),
        ('Permissions', {'fields': ('staff','admin',)}),
    )

admin.site.register(User, UserAdmin)

今回の変更については以下のとおり。

Userクラスとその拡張したクラス「Profile」を同ページに表示し編集できるようにします。

【変更前】

【変更後】

新規登録ページはデフォルトの通りとし、編集ページでのみ変更をしていきます。

【編集フォーム】accounts > forms.py

フォームを作成していきます

from django import forms
from django.forms.fields import DateField
from django.utils.translation import gettext, gettext_lazy as _
from django.contrib.auth.forms import UserChangeForm

from .models import User, Profile, GENDER_CHOICE

#GENDER_CHOICE = [(None, "--"), ("m", "男性"), ("f", "女性")]はmodels.pyで定義してます

class CustomAdminChangeForm(UserChangeForm):
#Profileクラスのフィールドを追記します
    username = forms.CharField(max_length=100)
    department = forms.CharField(max_length=100, required=False)
    phone_number = forms.IntegerField(required=False) 
    gender = forms.ChoiceField(choices=GENDER_CHOICE, required=False)
    birthday = DateField(required=False)

    class Meta:
        model = User
        fields =('email', 'password', 'active', 'admin')

#Profileが存在する場合は、初期値にデータを格納する
    def __init__(self, *args, **kwargs):
        user_obj = kwargs["instance"]
        if hasattr(user_obj, "profile"):
            profile_obj = user_obj.profile
            self.base_fields["username"].initial = profile_obj.username
            self.base_fields["department"].initial = profile_obj.department
            self.base_fields["phone_number"].initial = profile_obj.phone_number
            self.base_fields["gender"].initial = profile_obj.gender
            self.base_fields["birthday"].initial = profile_obj.birthday
        super().__init__(*args, **kwargs)

#保存機能の定義
    def save(self, commit=True):
        user_obj = super().save(commit=False)
        username = self.cleaned_data.get("username")
        department = self.cleaned_data.get("department")
        phone_number = self.cleaned_data.get("phone_number")
        gender = self.cleaned_data.get("gender")
        birthday = self.cleaned_data.get("birthday")
        if hasattr(user_obj, "profile"):
            profile_obj = user_obj.profile
        else:
            profile_obj = Profile(user=user_obj)
        if username is not None:
            profile_obj.username = username
        if department is not None:
            profile_obj.department = department
        if phone_number is not None:
            profile_obj.phone_number = phone_number
        if gender is not None:
            profile_obj.gender=gender
        if birthday is not None:
            profile_obj.birthday = birthday
        profile_obj.save()
        if commit:
            user_obj.save()
        return user_obj

ポイントは下記のとおりです

  • 継承しているUserChangeFormは、本来はユーザーモデルクラスのみを編集するためのフォーム
  • Meta内ではUserモデルクラスを指定すること
  • Profileクラスのフィールドを追記します
  • __init__メソッドで、データが存在する場合は.initial(初期値)として設定してます
  • saveメソッド内で、値を取り出し保存する

【カスタムフォームを適用する】accounts > admin.py

フォームが完成したら以下の流れで完成します。

  • 「form」変数へ作成したフォームクラスを格納
  • 「fieldsets」で新たな項目”プロフィール”を作成

まずは抜粋したコードをご覧ください

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User, Profile

from .forms import CustomAdminChangeForm

class UserAdmin(BaseUserAdmin):
    form = CustomAdminChangeForm
    #...
    fieldsets = (
        #..., 
        ('プロフィール', {'fields': (
            'username',
            'department',
            'phone_number',
            'gender',
            'birthday',
        )}),
        #...,
    )

admin.site.register(User, UserAdmin)

コード全体はこの通り。

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

from .models import User, Profile

from .forms import CustomAdminChangeForm

class UserAdmin(BaseUserAdmin):
    form = CustomAdminChangeForm

    list_display = (
        "email",
        "active",
        "staff",
        "admin",
    )
    list_filter = (
        "admin",
        "active",
    )
    filter_horizontal = ()
    ordering = ("email",)
    search_fields = ('email',)

    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('プロフィール', {'fields': (
            'username',
            'department',
            'phone_number',
            'gender',
            'birthday',
        )}),
        ('Permissions', {'fields': ('staff','admin',)}),
    )

    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2')}
        ),
    )

admin.site.register(User, UserAdmin)
#Profileクラスは不要になったのでコメントアウト
# admin.site.register(Profile)

まとめ

当記事では、Userクラスとその拡張クラス「Profileクラス」を使っての実例でした。

ポイントとして以下を覚えておきましょう。

  • フォームクラスでは、ModelFormをベースとし、メソッドなどを引継ぎ記述すること
  • adminクラスでは、対応している変数に正しい値を定義すること

ただしこのままだと、ユーザーのプロフィールを編集するページがありません。

ユーザーのプロフィールページを作るために、Djangoのsignalsを使って、ユーザーモデル保存後に自動でProfileクラスを生成する方法を解説します。

タイトルとURLをコピーしました