Модель 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/ это должно работать.
Любые идеи?
Ответы
Ответ 1
Я думаю, что вы, вероятно, используете функцию массового удаления admin, и сталкиваетесь с тем фактом, что метод группового удаления admin не вызывает delete()
(см. связанный ticket).
В прошлом я обошел это, написав собственное действие администратора для удаления моделей.
Если вы не используете метод массового удаления admin (например, вы нажимаете кнопку "Удалить" на странице редактирования объекта), происходит что-то еще.
См. предупреждение здесь:
Действие "удалить выбранные объекты" использует QuerySet.delete()
для эффективности причины, которые имеют важное caveat: ваши модели 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)
Ответ 2
Я знаю, что этот вопрос древний, но я снова столкнулся с этим и хотел добавить, что вы всегда можете переместить свой код в сигнал 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).
Ответ 3
Массовое действие admin вызывает queryset.delete()
.
Вы можете переопределить метод .delete()
для набора запросов,
поэтому он всегда выполняет удаление объектов 1 на 1. Например:
в менеджерах .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()
Ответ 4
Основная проблема заключается в том, что в 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):
...
Ответ 5
Используя 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
. Это менее эффективно, так как больше не является массовой операцией, но поддерживает чистоту файловой системы =)