【保存の極意】Django admin で関連オブジェクトを柔軟に扱う:save_related() とその代替方法


admin.ModelAdmin.save_related() は、Django 管理画面において、モデルインスタンスを保存した際に、関連するオブジェクトも自動的に保存するメソッドです。これは、多くの場合、インラインフォームで関連オブジェクトを編集する場合に使用されます。

役割

save_related() メソッドは、以下の役割を果たします。

  1. 関連オブジェクトの取得
    モデルインスタンスに関連するオブジェクトをすべて取得します。
  2. 関連オブジェクトの保存
    取得した関連オブジェクトをそれぞれ保存します。
  3. M2M フィールドの処理
    モデルインスタンスと M2M 関係にあるオブジェクトの関連付けを更新します。

使用方法

save_related() メソッドは、ModelAdmin クラスのメソッドとして定義されます。引数として、request オブジェクトと、保存対象のモデルインスタンスが渡されます。

def save_related(self, request, form, formset, instance):
    # 関連オブジェクトを取得
    related_objects = self.get_related_objects(request, instance)

    # 関連オブジェクトを保存
    for obj in related_objects:
        obj.save()

    # M2M フィールドの処理
    self.save_for_many_to_many(request, form, instance)

カスタマイズ

save_related() メソッドは、必要に応じてカスタマイズすることができます。例えば、以下のことができます。

  • M2M フィールドの処理を独自に行う
  • 関連オブジェクトを保存する前に処理を行う
  • 関連オブジェクトをフィルタリングする

以下の例では、Book モデルと Author モデルが M2M 関係にある場合、Book モデルを保存した際に、関連する Author オブジェクトも自動的に保存するように save_related() メソッドをカスタマイズしています。

class BookAdmin(admin.ModelAdmin):
    def save_related(self, request, form, formset, instance):
        # 関連オブジェクトを取得
        related_objects = self.get_related_objects(request, instance)

        # 関連オブジェクトを保存
        for obj in related_objects:
            if obj.is_published:
                obj.save()

        # M2M フィールドの処理
        self.save_for_many_to_many(request, form, instance)

この例では、is_published 属性が True である Author オブジェクトのみを保存しています。



from django.contrib import admin
from .models import Book, Author

class BookAdmin(admin.ModelAdmin):
    inlines = [AuthorInline]

    def save_related(self, request, form, formsets, change):
        super(BookAdmin, self).save_related(request, form, formsets, change)

        # Get the related Author objects
        related_authors = form.instance.authors.all()

        # Save the related Author objects
        for author in related_authors:
            if author.is_published:
                author.save()

class AuthorInline(admin.TabularInline):
    model = Author
    extra = 0

In this example, the BookAdmin class has an inlines attribute that specifies the AuthorInline inline form. This inline form allows users to add and edit related Author objects when they are editing a Book object.

The save_related() method is overridden in the BookAdmin class to save the related Author objects. The method first gets all of the related Author objects for the current Book instance. Then, it iterates over the related Author objects and saves each one that is published.

This code ensures that only published Author objects are saved when a Book object is saved.

Here is a breakdown of the code:

  • The extra = 0 attribute specifies that there should be no extra empty rows in the inline form.
  • The model = Author attribute specifies the Author model.
  • The class AuthorInline(admin.TabularInline) statement defines a TabularInline class for the Author model.
  • The author.save() statement saves the current Author object.
  • The if author.is_published: statement checks if the current Author object is published.
  • The for author in related_authors: loop iterates over the related Author objects.
  • The related_authors = form.instance.authors.all() statement gets all of the related Author objects for the current Book instance.
  • The super(BookAdmin, self).save_related(request, form, formsets, change) statement calls the original save_related() method.
  • The def save_related(self, request, form, formsets, change) method overrides the save_related() method from the ModelAdmin class.
  • The inlines = [AuthorInline] attribute specifies the AuthorInline inline form.
  • The class BookAdmin(admin.ModelAdmin) statement defines a ModelAdmin class for the Book model.
  • The from .models import Book, Author statement imports the Book and Author models from the current app.
  • The from django.contrib import admin statement imports the admin module, which contains the ModelAdmin class.


代替方法

save_related() の代替方法として、以下の方法が考えられます。

手動で関連オブジェクトを保存する

最も単純な方法は、モデルインスタンスを保存した後に、関連オブジェクトを個別に保存することです。

def save_model(self, request, form, instance, change):
    instance.save()

    # 関連オブジェクトを保存
    for author in instance.authors.all():
        author.save()

save_m2m() メソッドを使用する

M2M 関係の場合、save_m2m() メソッドを使用することができます。このメソッドは、M2M 関係にあるオブジェクトの関連付けを更新します。

def save_model(self, request, form, instance, change):
    instance.save()

    # M2M 関係の関連付けを更新
    self.save_for_many_to_many(request, form, instance)

シグナルを使用する

モデルインスタンスが保存された後に、シグナルを送信し、関連オブジェクトを保存する処理を実行することができます。

from django.dispatch import receiver

@receiver(models.post_save, sender=Book)
def save_related_authors(sender, instance, created, **kwargs):
    if created:
        for author in instance.authors.all():
            author.save()

カスタムビューを使用する

def save_book(request):
    if request.method == 'POST':
        form = BookForm(request.POST)
        if form.is_valid():
            book = form.save()

            # 関連オブジェクトを保存
            for author in request.POST.getlist('authors'):
                author = Author.objects.get(pk=author)
                book.authors.add(author)

            return redirect('/books/')

最適な方法を選択する

どの方法が最適かは、状況によって異なります。

  • カスタムロジックが必要な場合は、カスタムビュー を使用するのが良いでしょう。
  • 関連オブジェクトの保存処理が複雑な場合は、シグナル を使用すると柔軟に処理することができます。
  • M2M 関係の場合は、save_m2m() メソッド を使用するのが効率的です。
  • シンプルな場合は、手動で関連オブジェクトを保存する 方法がおすすめです。