How can I remove or change the verbose name of the default admin action "delete selected X item" in the Django admin panel?
8 Answers
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():
o.delete()
delete_model.short_description = 'Delete flow'
admin.site.register(Flow, FlowAdmin)
-
1`delete_model` shouldn't be used, since this method is used when deleting one instance from the admin. In that case the queryset argument is not a queryset, it's a single instance, and it makes the loop crash. – jul Jul 12 '12 at 18:49
-
This one should be selected as the accepted answser. – Ryu_hayabusa Feb 15 '14 at 14:09
-
I'm using django 1.4.16 and `delete_model` seems to work in both cases (no exceptions or loops). – Paolo Dec 22 '14 at 13:08
You can disable the action from appearing with this code.
from django.contrib import admin
admin.site.disable_action('delete_selected')
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]

- 1,638
- 1
- 18
- 34
-
Also using 'delete_selected' instead of admin.actions.delete_selected works fine in Django 1.6. – Gravity Grave Oct 01 '14 at 15:58
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)

- 97,747
- 36
- 197
- 212
-
seems a clever way for me it doesn't work. It simply removes the action box. – Hellnar Oct 14 '09 at 12:23
-
1Huh. I tested it with Django 1.1 and it works fine. I put it right at the top (below the other import statements) of my admin.py. – Dominic Rodger Oct 14 '09 at 12:34
-
-
1Dominic & Hellnar: make sure to upgrade past 1.1.0, there was an urgent security fix with exploits in the wild. The problem was fixed in 1.1.1 and 1.0.4. Details here: http://www.djangoproject.com/weblog/2009/oct/09/security/ – Oct 14 '09 at 12:40
-
1I doubt it - I guessed at that idea by looking at the source for trunk (specifically http://code.djangoproject.com/browser/django/trunk/django/contrib/admin/actions.py#L85). – Dominic Rodger Oct 14 '09 at 12:40
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)
i=i+1
# 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)
obj.delete()
modeladmin.log_deletion(request, obj, obj_display)
#queryset.delete()
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,
"admin/reservation_bulk_delete_confirmation.html"
], context, context_instance=template.RequestContext(request))
As you can see I commented out
queryset.delete()
and rather use:
obj.delete()
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:
admin.site.disable_action('delete_selected')
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
self.save()

- 30,738
- 21
- 105
- 131

- 2,815
- 2
- 22
- 13
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.disable_action('delete_selected')
self.add_action(self._delete_selected, 'delete_selected')
@staticmethod
def _delete_selected(modeladmin, request, queryset):
_delete_qs = queryset.delete
def delete():
for obj in queryset:
modeladmin.delete_model(request, obj)
_delete_qs()
queryset.delete = delete
return delete_selected(modeladmin, request, queryset)

- 104
- 4
- 10
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)
else:
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)

- 853
- 1
- 10
- 18