Django admin: override delete method
Asked Answered
M

5

37

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.

Massage answered 4/3, 2013 at 7:9 Comment(1)
Still I get the error self not definedMassage
P
21

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]
Palais answered 4/3, 2013 at 7:27 Comment(4)
This throws an error if only one object is to be deleted. Gives the error'_profile' object is not iterable. Also the actions list will have 2 values. One is delete_model and the other delete selected profiles.Massage
Yes, you'll need one function to use in the modeladmin actions and one for individual objects (I think you already have that function). Is there a problem with having two actions in the actions list?Palais
Yeah, So I have written a function to delete the extra action from the list. So it works fine now. ThanxMassage
delete_model overrides delete action for a single object and for the query set the delete_queryset must be overriddenDetribalize
G
48

You can use the delete_queryset which is coming from Django 2.1 onward for bulk delete objects and the delete_model for single delete. Both methods will handle something before deleting the object.

ModelAdmin.delete_queryset(request, queryset)


This is the explanation about delete_queryset in release note of Django 2.1.

The delete_queryset() method is given the HttpRequest and a QuerySet of objects to be deleted. Override this method to customize the deletion process for the “delete selected objects”

Let's look at what delete_queryset does, you can override admin.ModelAdmin class in this way by including delete_queryset function. Here you'll get list of object(s), queryset.delete() mean delete all the object(s) at once or you can add a loop to delete one by one.

def delete_queryset(self, request, queryset):
    print('==========================delete_queryset==========================')
    print(queryset)

    """
    you can do anything here BEFORE deleting the object(s)
    """

    queryset.delete()

    """
    you can do anything here AFTER deleting the object(s)
    """

    print('==========================delete_queryset==========================')

So I'm going to delete 5 objects from "select window" and here is those 5 objects.

deleting 5 objects from "select window"

Then you'll redirect to the confirmation page like this,

going to delete those 5 objects

Keep it mind about "Yes, I'm sure" button and I'll explain it later. When you click that button you will see the below image after removing those 5 objects.

successfully deleted 5 objects

This is the terminal output,

terminal output of those 5 objects

So you'll get those 5 objects as a list of QuerySet and before deleting you can do anything what ever you want in the comment area.

ModelAdmin.delete_model(request, obj)


This is the explanation about delete_model.

The delete_model method is given the HttpRequest and a model instance. Overriding this method allows doing pre- or post-delete operations. Call super().delete_model() to delete the object using Model.delete().

Let's look at what delete_model does, you can override admin.ModelAdmin class in this way by including delete_model function.

actions = ['delete_model']

def delete_model(self, request, obj):
    print('============================delete_model============================')
    print(obj)

    """
    you can do anything here BEFORE deleting the object
    """

    obj.delete()

    """
    you can do anything here AFTER deleting the object
    """

    print('============================delete_model============================')

I just click my 6th object to delete from the "change window".

deleting 1 object from "change window"

There is another Delete button, when you click it you'll see the window which we saw earlier.

going to delete those 1 object

Click "Yes, I'm sure" button to delete the single object. You'll see the following window with the notification of that deleted object.

successfully deleted 1 object

This is the terminal output,

terminal output of those 1 object

So you'll get selected object as a single of QuerySet and before deleting you can do anything what ever you want in the comment area.


The final conclusion is you can handle the delete event by clicking "Yes, I'm sure" button in "select window" or "change window" in Django Admin Site using delete_queryset and delete_model. In this way we don't need to handle such a signals like django.db.models.signals.pre_delete or django.db.models.signals.post_delete.

Here is the full code,

from django.contrib import admin

from . import models

class AdminInfo(admin.ModelAdmin):
    model = models.AdminInfo
    actions = ['delete_model']

    def delete_queryset(self, request, queryset):
        print('========================delete_queryset========================')
        print(queryset)

        """
        you can do anything here BEFORE deleting the object(s)
        """

        queryset.delete()

        """
        you can do anything here AFTER deleting the object(s)
        """

        print('========================delete_queryset========================')

    def delete_model(self, request, obj):
        print('==========================delete_model==========================')
        print(obj)

        """
        you can do anything here BEFORE deleting the object
        """

        obj.delete()

        """
        you can do anything here AFTER deleting the object
        """

        print('==========================delete_model==========================')

admin.site.register(models.AdminInfo, AdminInfo)
Goad answered 16/5, 2019 at 9:44 Comment(4)
your answer really helped me ..ThanksIntensifier
@KushanGunasekera, I want to raise exception and show some error message before deleting object based on some condition. If I do so, I get both message: my error message + "object delete successfully". Can you please help me how we can do this?Tarrance
@MohammadMustaqeem can I see your tried example? Then I can help you.Goad
Thanks a lot. great and step by step :XHoleandcorner
P
21

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]
Palais answered 4/3, 2013 at 7:27 Comment(4)
This throws an error if only one object is to be deleted. Gives the error'_profile' object is not iterable. Also the actions list will have 2 values. One is delete_model and the other delete selected profiles.Massage
Yes, you'll need one function to use in the modeladmin actions and one for individual objects (I think you already have that function). Is there a problem with having two actions in the actions list?Palais
Yeah, So I have written a function to delete the extra action from the list. So it works fine now. ThanxMassage
delete_model overrides delete action for a single object and for the query set the delete_queryset must be overriddenDetribalize
P
6

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):
    ...
Paver answered 3/10, 2014 at 15:35 Comment(0)
S
1

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.

Snaggletooth answered 4/3, 2013 at 7:34 Comment(1)
No that's not possible. If I add self, I get the error _profile_delete takes 3 arguments(2 given)Massage
P
1

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

Ptarmigan answered 19/3, 2016 at 15:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.