[Fixed]-The default "delete selected" admin action in Django


Not sure if this sort of monkey-patching is a good idea, but shoving this in one of my admin.py works for me:

from django.contrib.admin.actions import delete_selected
delete_selected.short_description = u'How\'s this for a name?'

This will change the verbose name for all your admin sites. If you want to change it just for one particular model’s admin, I think you’ll need to write a custom admin action.

Tested with Django version 1.1:

>>> import django
>>> django.VERSION
(1, 1, 0, 'beta', 1)


Alternatively to Googol’s solution, and by waiting for delete_model() to be implemented in current Django version , I suggest the following code.

It disables the default delete action for current AdminForm only.

class FlowAdmin(admin.ModelAdmin):
    actions = ['delete_model']

    def get_actions(self, request):
        actions = super(MyModelAdmin, self).get_actions(request)
        del actions['delete_selected']
        return actions

    def delete_model(self, request, obj):
        for o in obj.all():
    delete_model.short_description = 'Delete flow'

admin.site.register(Flow, FlowAdmin)


You can disable the action from appearing with this code.

from django.contrib import admin

If you chose, you could then restore it on individual models with this:

class FooAdmin(admin.ModelAdmin):
    actions = ['my_action', 'my_other_action', admin.actions.delete_selected]


In order to replace delete_selected I do the following:

Copy the function delete_selected from contrib/admin/actions.py to your admin.py and rename it. Also copy the template contrib/admin/templates/delete_selected_confirmation.html to your template directory and rename it. Mine looks like this:

def reservation_bulk_delete(modeladmin, request, queryset):
    Default action which deletes the selected objects.
    This action first displays a confirmation page whichs shows all the
    deleteable objects, or, if the user has no permission one of the related
    childs (foreignkeys), a "permission denied" message.

    Next, it delets all selected objects and redirects back to the change list.
    opts = modeladmin.model._meta
    app_label = opts.app_label

    # Check that the user has delete permission for the actual model
    if not modeladmin.has_delete_permission(request):
        raise PermissionDenied

    # Populate deletable_objects, a data structure of all related objects that
    # will also be deleted.

    # deletable_objects must be a list if we want to use '|unordered_list' in the template
    deletable_objects = []
    perms_needed = set()
    i = 0
    for obj in queryset:
        deletable_objects.append([mark_safe(u'%s: <a href="%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), obj.pk, escape(obj))), []])
        get_deleted_objects(deletable_objects[i], perms_needed, request.user, obj, opts, 1, modeladmin.admin_site, levels_to_root=2)

    # The user has already confirmed the deletion.
    # Do the deletion and return a None to display the change list view again.
    if request.POST.get('post'):
        if perms_needed:
            raise PermissionDenied
        n = queryset.count()
        if n:
            for obj in queryset:
                obj_display = force_unicode(obj)


                modeladmin.log_deletion(request, obj, obj_display)
            modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % {
                "count": n, "items": model_ngettext(modeladmin.opts, n)
        # Return None to display the change list page again.
        return None

    context = {
        "title": _("Are you sure?"),
        "object_name": force_unicode(opts.verbose_name),
        "deletable_objects": deletable_objects,
        'queryset': queryset,
        "perms_lacking": perms_needed,
        "opts": opts,
        "root_path": modeladmin.admin_site.root_path,
        "app_label": app_label,
        'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,

    # Display the confirmation page
    return render_to_response(modeladmin.delete_confirmation_template or [
        "admin/%s/%s/reservation_bulk_delete_confirmation.html" % (app_label, opts.object_name.lower()),
        "admin/%s/reservation_bulk_delete_confirmation.html" % app_label,
    ], context, context_instance=template.RequestContext(request))

As you can see I commented out


and rather use:


That’s not optimal yet – you should apply something to the entire queryset for better performance.

In admin.py I disable the default action delete_selected for the entire admin site:


Instead I use my own function where needed:

class ReservationAdmin(admin.ModelAdmin):
    actions = [reservation_bulk_delete, ]

In my model I define the delete() function:

class Reservation(models.Model):
    def delete(self):
        self.status_server = RESERVATION_STATUS_DELETED


For globally changing delete_selected’s short_description Dominic Rodger‘s answer seems best.

However for changing the short_description on the admin for a single model I think this alternative to Stéphane‘s answer is better:

def get_actions(self, request):
    actions = super().get_actions(request)
    actions['delete_selected'][0].short_description = "Delete Selected"
    return actions



from django.contrib.admin import sites
from django.contrib.admin.actions import delete_selected

class AdminSite(sites.AdminSite):
    Represents the administration, where only authorized users have access.
    def __init__(self, *args, **kwargs):
        super(AdminSite, self).__init__(*args, **kwargs)
        self.add_action(self._delete_selected, 'delete_selected')

    def _delete_selected(modeladmin, request, queryset):
        _delete_qs = queryset.delete

        def delete():
            for obj in queryset:
                modeladmin.delete_model(request, obj)

        queryset.delete = delete
        return delete_selected(modeladmin, request, queryset)


class FooAdmin(sites.AdminSite):
        not_deleted = ['value1', 'value2']
        actions = ['delete_selected_values']

    def delete_selected_values(self, request, queryset):
        # my custom logic
        exist = queryset.filter(value__in=self.not_deleted).exists()
        if exist:
            error_message = "Error"
            self.message_user(request, error_message, level=messages.ERROR)
            delete_action = super().get_action('delete_selected')[0]
            return delete_action(self, request, queryset)
    delete_selected_values.short_description = 'delete selected'

admin.site.register(Foo, FooAdmin)

Leave a comment