web-dev-qa-db-ja.com

Djangoのモデル削除メソッドをオーバーライドして一括削除する

次のように、画像フィールドのディスク内の孤立ファイルを削除するために、Djangoのモデル削除メソッドをオーバーライドしています。

class Image(models.Model):
    img = models.ImageField(upload_to=get_image_path)
    ...
    def delete(self, *args, **kwargs):
        self.img.delete()
        super(Image, self).delete(*args, **kwargs)

これは、管理者から単一のオブジェクトを削除するとうまく機能しますが、複数のオブジェクトを選択して削除すると、呼び出されないようです。私はしばらくの間ググリングしてきましたが、これに対する答えを得るために適切なキーワードをヒットしていません。また、公式のドキュメントはこの主題について話しているようではありません。

する

Delete()メソッドは一括削除を行い、モデルのdelete()メソッドを呼び出しません。ただし、削除されたすべてのオブジェクト(カスケードされた削除を含む)に対してpre_deleteおよびpost_deleteシグナルを発行します。

これを機能させるには、QuerySetのdeleteメソッドをオーバーライドして、そのQuerySetをマネージャーとして適用します。

class ImageQuerySet(models.QuerySet):

    def delete(self, *args, **kwargs):
        for obj in self:
            obj.img.delete()
        super(ImageQuerySet, self).delete(*args, **kwargs)

class Image(models.Model):
    objects = ImageQuerySet.as_manager()
    img = models.ImageField(upload_to=get_image_path)
    ...
    def delete(self, *args, **kwargs):
        self.img.delete()
        super(Image, self).delete(*args, **kwargs)
33
GwynBleidD

QuerysetのDeleteメソッドは、データベースで直接機能します。 Model.delete()メソッドは呼び出しません。 docs から:

これは可能な限り純粋にSQLで実行されるため、個々のオブジェクトインスタンスのdelete()メソッドがプロセス中に呼び出されるとは限らないことに注意してください。モデルクラスにカスタムのdelete()メソッドを提供し、それが呼び出されるようにしたい場合は、そのモデルのインスタンスを「手動で」削除する必要があります(たとえば、QuerySetを反復してdelete()を呼び出すことにより、各オブジェクトを個別に)QuerySetの一括delete()メソッドを使用するのではなく。

Django管理インターフェースのデフォルトの動作をオーバーライドしたい場合は、カスタムdeleteアクションを作成できます。

https://docs.djangoproject.com/en/1.7/ref/contrib/admin/actions/

別の方法は、deleteメソッドの代わりにpost_delete(またはpre_delete)シグナルをオーバーライドすることです。

https://docs.djangoproject.com/en/1.7/ref/signals/#Django.db.models.signals.post_delete

Pre_deleteに似ていますが、モデルのdelete()メソッドおよびクエリセットのdelete()メソッドの最後に送信されます。

7
Selcuk

この問題は docs で対処されていると思います

それが言うところ:

オーバーライドされたモデルメソッドは一括操作では呼び出されません

QuerySetを使用して、またはカスケード削除の結果としてオブジェクトを一括で削除する場合、オブジェクトのdelete()メソッドは必ずしも呼び出されないことに注意してください。カスタマイズされた削除ロジックが確実に実行されるようにするには、pre_deleteおよび/またはpost_deleteシグナルを使用できます。

残念ながら、save()、pre_save、post_saveのいずれも呼び出されないため、オブジェクトを一括で作成または更新する場合の回避策はありません。

上記のドキュメントで示唆されているように、post_deleteシグナル。

from Django.db.models.signals import post_delete
from Django.dispatch import receiver

class Image(models.Model):
    img = models.ImageField(upload_to=get_image_path)
    ...

@receiver(post_delete, sender=Image)
def delete_image_hook(sender, instance, using, **kwargs):
    instance.img.delete()

deleteメソッドのオーバーライドとは異なり、delete_image_hook関数は、一括削除およびカスケード削除でも呼び出す必要があります。 Djangoのシグナルの使用に関する詳細は次のとおりです。 https://docs.djangoproject.com/en/1.11/topics/signals/#connecting-to-signals-sent-by-specific-senders

以前の回答に関する注意:以前の投稿の一部は、パフォーマンスに影響を与える可能性がある、およびその他の意図しない動作を行う可能性があるQuerySetのdeleteメソッドのオーバーライドを提案しています。おそらく、これらの回答はDjangoのシグナルが実装される前に書かれたものですが、シグナルを使用する方がよりクリーンなアプローチだと思います。

0
modulitos