88
I think you’re probably using the admin’s bulk delete feature, and are running into the fact that the admin’s bulk delete method doesn’t call delete()
(see the related ticket).
I’ve got round this in the past by writing a custom admin action for deleting models.
If you’re not using the admin’s bulk delete method (e.g. you’re clicking the delete button on the object’s edit page) then something else is going on.
See the warning here:
The “delete selected objects” action
usesQuerySet.delete()
for efficiency
reasons, which has an important
caveat: your model’sdelete()
method
will not be called.If you wish to override this behavior,
simply write a custom action which
accomplishes deletion in your
preferred manner – for example, by
callingModel.delete()
for each of the
selected items.For more background on bulk deletion,
see the documentation on object
deletion.
My custom admin model looks like this:
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)
41
I know this question is ancient, but I just ran into this again and wanted to add that you can always move your code to a pre_delete or post_delete signal like so:
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")
It works with the admin’s bulk delete action (at least as of 1.3.1).
- [Django]-Django South – table already exists
- [Django]-Whats the difference between a OneToOne, ManyToMany, and a ForeignKey Field in Django?
- [Django]-What is the advantage of Class-Based views?
10
The bulk action of the admin calls queryset.delete()
.
You could override the .delete()
method of the queryset,
so it always does a 1-by-1 deletion of objects. For example:
in managers.py:
from django.db import models
from django.db.models.query import QuerySet
class PhotoQuerySet(QuerySet):
""" 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()
In models.py:
from django.db import models
class Photo(models.Model):
image = models.ImageField(upload_to='images')
objects = PhotoQuerySet.as_manager()
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]-Django character set with MySQL weirdness
- [Django]-Django optional URL parameters
- [Django]-How do I print out the contents of my settings in a django shell?
8
Using django v2.2.2, I solved this problem with the following code
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()
For the DefaultAdminSite the delete_queryset is called if the user has the correct permissions, the only difference is that the original function calls queryset.delete()
which doesn’t trigger the model delete
method. This is less efficient since is not a bulk operation anymore, but it keeps the filesystem clean =)
- [Django]-.filter() vs .get() for single object? (Django)
- [Django]-Celery: WorkerLostError: Worker exited prematurely: signal 9 (SIGKILL)
- [Django]-Loop backwards using django template
3
The main issue is that the Django admin’s bulk delete uses SQL, not instance.delete(), as noted elsewhere. For an admin-only solution, the following solution preserves the Django admin’s “do you really want to delete these” interstitial. vdboor’s solution is the most general, however.
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):
...
- [Django]-Testing nginx without domain name
- [Django]-Caching query results in django
- [Django]-How do I run another script in Python without waiting for it to finish?