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

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


person Kilian Perdomo Curbelo    schedule 06.03.2015    source источник


Ответы (3)


Да:

Метод 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)
person GwynBleidD    schedule 06.03.2015
comment
Кажется, я читал это в документации, но не знал, что это применимо к массовому удалению администратора. В любом случае, мне понравилось ваше решение, большое спасибо! - person Kilian Perdomo Curbelo; 06.03.2015
comment
В этом подходе есть одно предостережение, теперь вы можете сделать: Image.objects.delete() и это сотрет вашу таблицу. - person HTF; 06.07.2018
comment
Отлично, это сработало для меня. - person alfonsoolavarria; 28.05.2021

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

Имейте в виду, что это будет, когда это возможно, выполняться исключительно в SQL, поэтому методы удаления() отдельных экземпляров объекта не обязательно будут вызываться во время процесса. Если вы предоставили пользовательский метод 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() набора запросов.

person Selcuk    schedule 06.03.2015
comment
Спасибо за решение. Я попробую, но я отмечаю ответ GwynBleidD как принятый, потому что я предпочитаю его подход (кажется чище). - person Kilian Perdomo Curbelo; 06.03.2015

Я считаю, что эта проблема рассматривается в документах< /а>

где сказано:

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

Обратите внимание, что метод 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, но я думаю, что использование сигналов - более чистый подход.

person modulitos    schedule 21.05.2019
comment
Сигналы меня никогда не подводили. Я всегда предпочитаю это решение. - person Ebram Shehata; 29.02.2020