Модель Django: delete() не запущена

У меня есть модель:

class MyModel(models.Model):
 ...
    def save(self):
        print "saving"
        ...
    def delete(self):
        print "deleting"
        ...

Метод save()- запускается, а delete() - нет. Я использую последнюю версию svn (Django версия 1.2 pre-alpha SVN-11593), и относительно документации на http://www.djangoproject.com/documentation/models/save_delete_hooks/ это должно работать. Есть идеи?

4 ответа

Решение

Я думаю, что вы, вероятно, используете функцию массового удаления администратора и сталкиваетесь с тем, что метод массового удаления администратора не вызывает delete() (см. соответствующий билет).

В прошлом я решил эту проблему, написав специальное действие администратора для удаления моделей.

Если вы не используете метод массового удаления администратора (например, вы нажимаете кнопку удаления на странице редактирования объекта), тогда происходит что-то еще.

Смотрите предупреждение здесь:

Действие "Удалить выбранные объекты" использует QuerySet.delete() по соображениям эффективности, что имеет важное предостережение: ваша модель delete() метод не будет вызван.

Если вы хотите переопределить это поведение, просто напишите настраиваемое действие, которое выполняет удаление вашим предпочтительным способом - например, вызывая Model.delete() для каждого из выбранных предметов.

Для получения дополнительной информации о массовом удалении см. Документацию по удалению объектов.

Моя пользовательская модель администратора выглядит следующим образом:

from photoblog.models import PhotoBlogEntry
from django.contrib import admin    

class PhotoBlogEntryAdmin(admin.ModelAdmin):
    actions=['really_delete_selected']

    def get_actions(self, request):
        actions = super(PhotoBlogEntryAdmin, self).get_actions(request)
        del actions['delete_selected']
        return actions

    def really_delete_selected(self, request, queryset):
        for obj in queryset:
            obj.delete()

        if queryset.count() == 1:
            message_bit = "1 photoblog entry was"
        else:
            message_bit = "%s photoblog entries were" % queryset.count()
        self.message_user(request, "%s successfully deleted." % message_bit)
    really_delete_selected.short_description = "Delete selected entries"

admin.site.register(PhotoBlogEntry, PhotoBlogEntryAdmin)

Я знаю, что этот вопрос древний, но я снова столкнулся с этим и хотел добавить, что вы всегда можете переместить свой код в сигнал pre_delete или post_delete, например так:

from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver

@receiver(pre_delete, sender=MyModel)
def _mymodel_delete(sender, instance, **kwargs):
    print "deleting"

Он работает с действием массового удаления администратора (по крайней мере, начиная с 1.3.1).

Массовое действие админ звонков queryset.delete(),

Вы можете переопределить .delete() метод набора запросов, поэтому он всегда выполняет удаление объектов 1: 1. Например:

в Manager.py:

from django.db import models
from django.db.models.query import QuerySet

class PhotoQueryMixin(object):
    """ Methods that appear both in the manager and queryset. """
    def delete(self):
        # Use individual queries to the attachment is removed.
        for photo in self.all():
            photo.delete()

class PhotoQuerySet(PhotoQueryMixin, QuerySet):
    pass

class PhotoManager(PhotoQueryMixin, models.Manager):
    def get_query_set(self):
        return PhotoQuerySet(self.model, using=self._db)

В models.py:

from django.db import models

class Photo(models.Model):
    image = models.ImageField(upload_to='images')

    objects = PhotoManager()

    def delete(self, *args, **kwargs):
        # Note this is a simple example. it only handles delete(),
        # and not replacing images in .save()
        super(Photo, self).delete(*args, **kwargs)
        self.image.delete()

Используя django v2.2.2, я решил эту проблему с помощью следующего кода

models.py

class MyModel(models.Model):
    file = models.FileField(upload_to=<path>)

    def save(self, *args, **kwargs):
        if self.pk is not None:
            old_file = MyModel.objects.get(pk=self.pk).file
            if old_file.path != self.file.path:
                self.file.storage.delete(old_file.path)

        return super(MyModel, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        ret = super(MyModel, self).delete(*args, **kwargs)
        self.file.storage.delete(self.file.path)
        return ret

admin.py

class MyModelAdmin(admin.ModelAdmin):

    def delete_queryset(self, request, queryset):
        for obj in queryset:
            obj.delete()

Для DefaultAdminSite вызывается delete_queryset, если у пользователя есть правильные разрешения, единственная разница в том, что исходная функция вызывает queryset.delete() который не запускает модель deleteметод. Это менее эффективно, так как это больше не массовая операция, но она сохраняет файловую систему в чистоте =)

Основная проблема заключается в том, что при массовом удалении администратора Django используется SQL, а не instance.delete(), как отмечено в другом месте. Для решения только для администратора, следующее решение сохраняет админы Django "Вы действительно хотите удалить их"? Однако решение vdboor является наиболее общим.

from django.contrib.admin.actions import delete_selected

class BulkDeleteMixin(object):
    class SafeDeleteQuerysetWrapper(object):
        def __init__(self, wrapped_queryset):
            self.wrapped_queryset = wrapped_queryset

        def _safe_delete(self):
            for obj in self.wrapped_queryset:
                obj.delete()

        def __getattr__(self, attr):
            if attr == 'delete':
                return self._safe_delete
            else:
                return getattr(self.wrapped_queryset, attr)

        def __iter__(self):
            for obj in self.wrapped_queryset:
                yield obj

        def __getitem__(self, index):
            return self.wrapped_queryset[index]

        def __len__(self):
            return len(self.wrapped_queryset)

    def get_actions(self, request):
        actions = super(BulkDeleteMixin, self).get_actions(request)
        actions['delete_selected'] = (BulkDeleteMixin.action_safe_bulk_delete, 'delete_selected', ugettext_lazy("Delete selected %(verbose_name_plural)s"))
        return actions

    def action_safe_bulk_delete(self, request, queryset):
        wrapped_queryset = BulkDeleteMixin.SafeDeleteQuerysetWrapper(queryset)
        return delete_selected(self, request, wrapped_queryset)


class SomeAdmin(BulkDeleteMixin, ModelAdmin):
    ...
Другие вопросы по тегам