もう悩まない! Django admin のインラインフォーム表示数を max_num 属性でスマートに制御


django.contrib.admin モジュール内の admin.InlineModelAdmin.max_num 属性は、Django 管理サイトにおいて、インラインフォーム内に表示される最大フォーム数を制御します。

詳細

  • max_num 属性を設定すると、以下の影響があります。
    • 管理サイトで親オブジェクトの編集画面を開いた際、max_num 個分のインラインフォームが表示されます。
    • max_num 個を超えるインラインフォームが存在する場合は、それらは非表示になります。
    • 非表示になったインラインフォームは、Add another ボタンをクリックすることで表示できます。
    • max_num 個のインラインフォームがすべて使用されている場合は、Add another ボタンは非表示になります。
  • 0 以上の値を設定すると、その値が最大フォーム数として設定されます。
  • デフォルト値は 0 であり、制限はありません。
  • max_num 属性は、InlineModelAdmin クラス内に定義されます。

from django.contrib import admin

class BookInline(admin.TabularInline):
    model = Book
    max_num = 3

class AuthorAdmin(admin.ModelAdmin):
    inlines = [BookInline,]

上記例では、BookInline インラインフォーム内に最大3個のフォームが表示されます。

  • max_num 属性は、パフォーマンスにも影響を与える可能性があります。
    • 多くのインラインフォームを表示する場合は、パフォーマンスが低下する可能性があります。
    • 必要に応じて max_num 属性を調整することをおすすめします。
  • max_num 属性と extra 属性は併用できます。
    • extra 属性は、追加で表示する空のインラインフォーム数を指定します。
    • max_num 属性と extra 属性を組み合わせることで、常に特定数のインラインフォームを表示することができます。
  • 詳細については、Django 公式ドキュメントを参照してください。
  • 上記以外にも、django.contrib.admin モジュールには様々な機能が用意されています。


max_num 属性と extra 属性の併用

from django.contrib import admin

class BookInline(admin.TabularInline):
    model = Book
    max_num = 3
    extra = 2

class AuthorAdmin(admin.ModelAdmin):
    inlines = [BookInline,]

さらに、2個の空のインラインフォームも追加で表示されます。

結果

  • ユーザーは必要に応じて、空のフォームにデータをを入力することができます。
  • 最初は 3個 のフォームが表示され、2個 の空のフォームが追加で表示されます。
  • 管理サイトで親オブジェクトの編集画面を開いた際、最大5個のインラインフォームが表示されます。

max_num 属性と can_delete 属性の組み合わせ

from django.contrib import admin

class BookInline(admin.TabularInline):
    model = Book
    max_num = 3
    can_delete = False

class AuthorAdmin(admin.ModelAdmin):
    inlines = [BookInline,]

上記例では、BookInline インラインフォーム内に最大3個のフォームが表示されます。

さらに、削除ボタンが非表示になります。

結果

  • ユーザーはフォームを編集することはできますが、削除することはできません
  • 管理サイトで親オブジェクトの編集画面を開いた際、最大3個のインラインフォームが表示されます。
from django.contrib import admin

class BookInline(admin.TabularInline):
    model = Book
    max_num = 3

    def save_formset(self, request, formset, commit=True):
        if formset.saved_forms and len(formset.saved_forms) > self.max_num:
            # 最大フォーム数を超えた場合は、最後のフォームを削除する
            formset.saved_forms.pop()

        super().save_formset(request, formset, commit=commit)

class AuthorAdmin(admin.ModelAdmin):
    inlines = [BookInline,]

上記例では、BookInline インラインフォーム内に最大3個のフォームが表示されます。

さらに、最大フォーム数を超えた場合、最後のフォームが自動的に削除されます。

結果

  • しかし、4個目 のフォームを作成すると、1個目 のフォームが自動的に削除されます。
  • ユーザーは 3個 以上のフォームを作成することができます。


以下、いくつかの代替方法と、それぞれの利点と欠点をご紹介します。

JavaScript による非表示

  • 欠点:
    • JavaScript を無効にしているユーザーには表示されてしまう
    • SEO に影響を与える可能性がある
  • 利点:
    • シンプルで実装しやすい
    • 柔軟な制御が可能
$(document).ready(function() {
    $('.inline-deletable').slice(3).hide();
});

カスタムインラインフォーム

  • 欠点:
    • 複雑な実装が必要
    • コード量が増える
  • 利点:
    • より柔軟な制御が可能
    • コードをカプセル化できる
from django.contrib import admin

class MyInlineFormSet(admin.BaseInlineFormSet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.queryset.count() > 3:
            for form in self.forms[:self.queryset.count() - 3]:
                form.helper.add_hidden_field('DELETE', 'False')

class BookInline(admin.TabularInline):
    formset_class = MyInlineFormSet
    model = Book
    max_num = 0  # max_num を 0 に設定しても、影響を与えない

class AuthorAdmin(admin.ModelAdmin):
    inlines = [BookInline,]

シグナルによる非表示

  • 欠点:
    • 複雑な実装が必要
    • コード量が増える
  • 利点:
    • 再利用可能なコードを記述できる
    • 他のアプリケーションとの統合が容易
from django.dispatch import receiver
from django.contrib import admin

@receiver(admin.ModelAdmin.get_formsets, sender=AuthorAdmin)
def limit_inline_formsets(request, model_admin, queryset):
    if model_admin.model == Book:
        return {
            'inlines': [
                BookInline(model_admin, queryset, max_num=3),
            ],
        }

class BookInline(admin.TabularInline):
    model = Book
    max_num = 0  # max_num を 0 に設定しても、影響を与えない

class AuthorAdmin(admin.ModelAdmin):
    inlines = [BookInline,]

サードパーティ製ライブラリ

  • 欠点:
    • ライブラリのメンテナンス状況によっては、将来的に利用できなくなる可能性がある
  • 利点:
    • 既存の機能を利用できる
    • 開発時間を短縮できる
from django.contrib import admin
from admin_tools.tools.tabularinline import TabularInlineWithMax


class BookInline(TabularInlineWithMax):
    model = Book
    max_num = 3

class AuthorAdmin(admin.ModelAdmin):
    inlines = [BookInline,]

最適な代替方法は、状況によって異なります。

上記以外にも、様々な方法で admin.InlineModelAdmin.max_num の機能を代替することができます。