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

Django

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

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

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

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

当記事を通じて、

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

について解説します

【著者プロフィール】

profile_icon
【Python歴】11年 x 【Django歴】10年
HP作成、社内システムの構築、コンサルティング

当ブログを通じて、Webアプリの書き方やアプリの公開方法までを解説しています。

☆日報アプリ「D-Repo」(デモ版)→こちら

※Djangoをベースに作成したアプリです。

☆便利ツールアプリ「Tool Station」→こちら

※Django REST frameworkとReactで作成しているアプリです。

✔YouTube解説動画

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

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

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

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クラスを生成する方法を解説します。

✔当ブログは以下のような方に向けて書かれています

「Djangoでのアプリ開発を学びたい!」

「Djangoで開発したアプリをWebで公開するにはどうするの?」

✔当ブログ掲載の記事

  • Djangoで作る日報アプリ開発
  • WebアプリをWeb上に公開する方法
  • Webアプリ開発に必要なそのほかの情報
【Django】チュートリアル|日報アプリの開発から公開まで
Djangoのチュートリアルをお探しですか?具体的に「手を動かして作ってみたい!」という方へ向けて、誰でもできる簡易的な日報アプリの開発を通じて、Djangoの様々な機能に触れていくシリーズとなっています。PythonでWebアプリを作りたい方、必見の記事となります!

ITCブログにご協力いただける方は、以下もご検討いただけると嬉しいです。

ITCへ投げ銭をする

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