Django admin のインラインフォームセット保存をもっと自由に! `admin.ModelAdmin.save_formset()` メソッドのオーバーライドで実現する高度なカスタマイズ


このメソッドは、親モデルの保存処理と連動して動作し、インラインフォームセット内の各子モデルインスタンスの保存、更新、削除を適切に行います。デフォルトでは、Django が提供する標準的な動作に従って処理されますが、開発者はこのメソッドをオーバーライドすることで、独自の保存ロジックを実装することができます。

メソッドの役割

admin.ModelAdmin.save_formset() メソッドは、以下の役割を担います。

    • 新規作成されたインスタンスの保存
    • 既存のインスタンスの更新
    • 削除フラグが設定されたインスタンスの削除
  1. 親モデルとの関連付け

    • 各子モデルインスタンスを適切な親モデルインスタンスに関連付け
  2. 保存処理のコミット

    • すべての処理が完了したら、データベースへの変更を確定

メソッドの引数

このメソッドは以下の引数を受け取ります。

  • change: 親モデルが新規作成されるか更新されるかを表すブール値
  • formset: インラインフォームセット
  • form: 親モデルのフォームインスタンス
  • request: 現在のHTTPリクエストオブジェクト

メソッドの処理内容

  1. インラインフォームセットの保存

    • formset.save() メソッドを呼び出し、各子モデルインスタンスを保存します。
    • この時点で、データベースへの変更はコミットされません。
  2. 削除フラグが設定されたインスタンスの処理

    • formset.deleted_objects 属性から削除フラグが設定されたインスタンスを取得します。
    • 各インスタンスに対して delete() メソッドを呼び出し、データベースから削除します。
  3. 親モデルとの関連付け

    • 各子モデルインスタンスに対して、適切な親モデルインスタンスを関連付けます。
    • 具体的には、instance.parent = parent_instance のように設定します。
  4. 保存処理のコミット

    • すべての処理が完了したら、formset.save_m2m() メソッドを呼び出し、ManyToManyフィールドの変更を保存します。
    • 最後に、formset.save(commit=True) メソッドを呼び出し、データベースへの変更を確定します。

メソッドのオーバーライド

開発者は、admin.ModelAdmin.save_formset() メソッドをオーバーライドすることで、独自の保存ロジックを実装することができます。例えば、以下のケースでオーバーライドが役立ちます。

  • 親モデルと子モデルの関連付け方法をカスタマイズしたい場合
  • 保存処理の前に、子モデルインスタンスに対して追加の処理を実行したい場合
  • 特定の条件に基づいて、子モデルインスタンスの保存を許可/拒否したい場合

メソッドをオーバーライドする際は、以下の点に注意する必要があります。

  • メソッド内でデータベースへの変更を行う場合は、必ずトランザクションを使用して、データ整合性を保つ必要があります。
  • メソッド内で formset.save() メソッドを呼び出す前に、必ず formset.cleaned_data 属性を使用して、フォームデータを取得する必要があります。
  • メソッドの引数と戻り値の型は、元のメソッドと一致させる必要があります。

上記の説明に加えて、以下の点にも注意が必要です。

  • admin.ModelAdmin.save_formset() メソッドは、インラインフォームセットの保存処理を制御するための主要なメソッドですが、他にも様々なメソッドが用意されています。これらの
  • Django 1.7 以降では、formset.save(commit=False) メソッドを呼び出しただけでは、削除されたオブジェクトは自動的に削除されません。削除されたオブジェクトを削除するには、formset.deleted_objects 属性から削除フラグが設定されたインスタンスを取得し、個別に削除する必要があります。


from django.contrib import admin

from .models import ParentModel, ChildModel


class ParentModelAdmin(admin.ModelAdmin):
    inlines = [ChildModelAdmin,]

    def save_formset(self, request, form, formset, change):
        instances = formset.save(commit=False)
        for instance in instances:
            # 新規作成されたインスタンスのみ処理
            if not instance.pk:
                instance.default_value = 10  # デフォルト値を設定
                instance.save()
        formset.save_m2m()

このコードでは、ChildModelAdmin クラスの save_formset() メソッドをオーバーライドし、新規作成された ChildModel インスタンスに default_value 属性にデフォルト値 10 を設定しています。

例2:特定の条件に基づいて、子モデルインスタンスの保存を許可/拒否する

from django.contrib import admin

from .models import ParentModel, ChildModel


class ParentModelAdmin(admin.ModelAdmin):
    inlines = [ChildModelAdmin,]

    def save_formset(self, request, form, formset, change):
        instances = formset.save(commit=False)
        for instance in instances:
            # 特定の条件に基づいて、保存を許可/拒否
            if instance.is_active and instance.price > 1000:
                instance.save()
            else:
                instance.delete()
        formset.save_m2m()

このコードでは、ChildModelAdmin クラスの save_formset() メソッドをオーバーライドし、is_active 属性が True かつ price 属性が 1000 を超える場合のみ、ChildModel インスタンスを保存しています。それ以外の場合は、インスタンスを削除します。

例3:親モデルと子モデルの関連付け方法をカスタマイズする

from django.contrib import admin

from .models import ParentModel, ChildModel


class ParentModelAdmin(admin.ModelAdmin):
    inlines = [ChildModelAdmin,]

    def save_formset(self, request, form, formset, change):
        instances = formset.save(commit=False)
        for instance in instances:
            # 親モデルインスタンスを取得
            parent_instance = ParentModel.objects.get(pk=request.POST.get('parent_id'))
            # 子モデルインスタンスを親モデルに関連付け
            instance.parent = parent_instance
            instance.save()
        formset.save_m2m()

このコードでは、ChildModelAdmin クラスの save_formset() メソッドをオーバーライドし、request.POST から parent_id を取得し、それに対応する ParentModel インスタンスを取得して、ChildModel インスタンスに関連付けています。

これらの例はあくまでも基本的なものであり、具体的な実装は、プロジェクトの要件に合わせてカスタマイズする必要があります。

  • Django のバージョンによって、admin.ModelAdmin クラスのメソッドや属性の仕様が異なる場合があります。使用している Django のバージョンに対応したドキュメントを参照してください。
  • 上記の例では、formset.save(commit=False) メソッドを使用して、データベースへの変更を一時的に保存しています。その後、個別に処理を行い、最後に formset.save_m2m() メソッドを使用して、ManyToManyフィールドの変更を保存し、formset.save(commit=True) メソッドを使用して、データベースへの変更を確定しています。


    • Create a custom save() method for your formset class.
    • Override the default behavior to handle formset data as per your requirements.
    • This method provides granular control over the saving process.
  1. Model Instance Iterations

    • Iterate through the formset's cleaned_data dictionary.
    • Create or update model instances as needed.
    • Handle deletions separately.
    • Offers more flexibility but requires manual management of instance creation/updates.
  2. Third-party Libraries

    • Explore third-party libraries like django-crispy-forms or django-rest-framework-admin.
    • These libraries may provide alternative approaches to inline formset handling.
    • Evaluate their features and compatibility with your project's requirements.

Choosing the Right Approach

  • Maintainability
    Evaluate the maintainability of your solution. Consider the long-term impact of code complexity and potential future modifications.

  • Control
    Assess the level of control you need. If you require fine-grained control over saving behavior, a custom formset or manual iterations might be preferable.

  • Complexity
    Consider the complexity of your custom logic. For simple modifications, save_formset() override might suffice. For intricate scenarios, a custom formset or third-party library could be better suited.

Additional Considerations

  • Documentation
    Document your chosen approach clearly for future reference and collaboration.

  • Testing
    Ensure thorough testing of your custom implementation to guarantee data integrity and avoid unexpected behavior.

  • Performance
    If performance is critical, benchmark different approaches to identify the most efficient one.