Переопределить метод удаления модели 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)

Это отлично работает, когда я удаляю отдельные объекты из администратора, но когда я выбираю несколько объектов и удаляю их, это, похоже, не вызвано. Я давно искал поисковые запросы, но не использовал правильные ключевые слова, чтобы получить ответ на этот вопрос, и официальная документация, похоже, не говорит об этой теме.

Ответ 1

Он делает:

Метод delete() делает массовое удаление и не вызывает никаких методов delete() на ваших моделях. Однако он излучает сигналы pre_delete и post_delete для всех удаленных объектов (включая каскадные удаления).

Для этого вы можете переопределить метод удаления на QuerySet, а затем применить этот 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)

Ответ 2

Метод удаления queryset работает непосредственно в базе данных. Он не вызывает Model.delete(). Из документов:

Имейте в виду, что это, по возможности, будет выполняться исключительно в SQL, и поэтому методы delete() отдельных экземпляров объекта не обязательно будут вызываться во время процесса. Если вы предоставили пользовательский метод delete() для класса модели и хотите убедиться, что он вызывается, вам нужно будет "вручную" удалить экземпляры этой модели (например, путем итерации по QuerySet и вызова delete() для каждого объекта). индивидуально) вместо использования метода массового удаления() объекта QuerySet.

Если вы хотите переопределить поведение интерфейса администрирования Django по умолчанию, вы можете написать собственное действие delete:

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

Другой метод - переопределить post_delete (или pre_delete) вместо метода delete:

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

Как и pre_delete, но отправляется в конце метода delete() моделей и метода delete() наборов запросов.

Ответ 3

Я считаю, что эта проблема решена в документации

где сказано:

Переопределенные методы модели не вызываются в массовых операциях

Обратите внимание, что метод delete() для объекта не обязательно вызывается при массовом удалении объектов с использованием QuerySet или в результате каскадного удаления. Чтобы обеспечить выполнение настроенной логики удаления, вы можете использовать сигналы 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

Примечание к предыдущим ответам. В некоторых предыдущих публикациях предлагается переопределить метод delete QuerySet, что может повлиять на производительность и другое непреднамеренное поведение. Возможно, эти ответы были написаны до внедрения Django Signals, но я думаю, что использование Signals - более чистый подход.