Django admin: override delete method

2019-03-15 03:25发布

问题:

I have admin.py as follows:

  class profilesAdmin(admin.ModelAdmin):
     list_display = ["type","username","domain_name"]

Now i want to perform some action before deleting the object:

  class profilesAdmin(admin.ModelAdmin):
     list_display = ["type","username","domain_name"]

     @receiver(pre_delete, sender=profile)
     def _profile_delete(sender, instance, **kwargs):
        filename=object.profile_name+".xml"
        os.remove(os.path.join(object.type,filename))

If i use delete signal method like this I get an error saying self should be the first parameter.

How can I modify the above function?
And I want to retrieve the profile_name of the object being deleted. How can this be done?

I also tried overriding delete_model method:

def delete_model(self, request, object):
    filename=object.profile_name+".xml"
    os.remove(os.path.join(object.type,filename))
    object.delete()

But this dosn't work if multiple objects have to be deleted at one shot.

回答1:

You're on the right track with your delete_model method. When the django admin performs an action on multiple objects at once it uses the update function. However, as you see in the docs these actions are performed at the database level only using SQL.

You need to add your delete_model method in as a custom action in the django admin.

def delete_model(modeladmin, request, queryset):
    for obj in queryset:
        filename=obj.profile_name+".xml"
        os.remove(os.path.join(obj.type,filename))
        obj.delete()

Then add your function to your modeladmin -

class profilesAdmin(admin.ModelAdmin):
    list_display = ["type","username","domain_name"]
    actions = [delete_model]


回答2:

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.

The most general solution is to override the queryset returned by the model's manager to intercept delete.

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, admin.ModelAdmin):
    ...


回答3:

you try overriding delete_model method failed because when you delete multiple objects the django use QuerySet.delete(),for efficiency reasons your model’s delete() method will not be called.

you can see it there https://docs.djangoproject.com/en/1.9/ref/contrib/admin/actions/
watch the beginning Warning

Admin delete_model() is same as the model's delete() https://github.com/django/django/blob/master/django/contrib/admin/options.py#L1005

so when you delete multiple objects, you custom the delete method will never be call.

you have two way.

1.custom delete action.
action calling Model.delete() for each of the selected items.

2.use signal.
you can use signal alone, not inside the class.

you also can watch this question Django model: delete() not triggered



回答4:

Your method should be

class profilesAdmin(admin.ModelAdmin):
    #...

    def _profile_delete(self, sender, instance, **kwargs):
        # do something

    def delete_model(self, request, object):
        # do something

You should add a reference to current object as the first argument in every method signature (usually called self). Also, the delete_model should be implemented as a method.