【Django】複数のモデルクラスを同一ページで編集する|ユーザークラスのAdminフォームをカスタマイズ

Django

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

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

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

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

当記事を通じて、

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

について解説します

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

ユーザーモデルクラスのカスタマイズについては、「UserAdmin」を使用しています

UserAdminでは、

  • 新規登録フォームは「add_form」変数
  • 編集フォームは「form」変数

へ格納することで、独自のフォームを使用することが可能になります

また、フィールドの表示をカスタマイズするには、

  • 新規登録フォームでは、「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クラスとOneToOneFieldで拡張した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)
# admin.site.register(Profile)

まとめ

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

ポイントとして、

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

を覚えておきましょう

さて、次回はユーザーがログインをした際に、ユーザークラスを拡張したプロフィールクラスの有無によってログイン後の行き先を変える「ACCOUNT_ADAPTER」のカスタマイズ方法を解説していきます

当ブログでは、日報アプリ開発を通じて、Webアプリを一から開発し公開するまでを初学者の方でもわかるようにと記事を連載しています

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

という方は必見です!

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

関連書籍

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