Модель 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):
...