Implementing Admin Actions
To create and register bulk actions that can be performed on multiple objects in the Django admin, you define an action function and register it using the @admin.action decorator or the actions attribute of a ModelAdmin.
from django.contrib import admin
from django.http import StreamingHttpResponse
from io import StringIO
from wsgiref.util import FileWrapper
from django.contrib.admin.options import ActionLocation
@admin.action(
description="Download subscription",
description_plural="Download selected subscriptions",
location=(ActionLocation.CHANGE_LIST, ActionLocation.CHANGE_FORM),
)
def download(modeladmin, request, queryset):
"""
An action that generates a file download for selected objects.
Works on both the change list and individual change forms.
"""
if queryset.count() > 1:
buf = StringIO("This is the content of the file for multiple items")
else:
obj = queryset.get()
buf = StringIO(f"This is the content of the file written by {obj.name}")
return StreamingHttpResponse(FileWrapper(buf), content_type="text/plain")
class SubscriberAdmin(admin.ModelAdmin):
list_display = ["name", "email"]
actions = [download]
Specifying Action Locations
By default, actions are only displayed on the "Change List" (the table view of multiple objects). You can use the ActionLocation enum from django.contrib.admin.options to make actions available on the "Change Form" (the individual object edit page) as well.
ActionLocation.CHANGE_LIST: Displays the action in the dropdown menu above the object list.ActionLocation.CHANGE_FORM: Displays the action as a button or option on the individual object's edit page.
When an action is triggered from a CHANGE_FORM, the queryset passed to the action function will contain only the single object currently being viewed.
Customizing the Action Form
You can extend the interface of the action selection area by subclassing ActionForm from django.contrib.admin.helpers. This allows you to add extra fields (like confirmation checkboxes) or custom media.
from django import forms
from django.contrib.admin.helpers import ActionForm
class MediaActionForm(ActionForm):
# Add a custom field to the action selection area
confirm_action = forms.BooleanField(required=False, label="I confirm this action")
class Media:
js = ["admin/js/custom_action_logic.js"]
class SubscriberAdmin(admin.ModelAdmin):
actions = ["mail_admin"]
action_form = MediaActionForm
The base ActionForm provides two standard fields:
action: TheChoiceFieldcontaining the list of available actions.select_across: ABooleanField(rendered as a hidden input) used to track if the user has selected all objects across all pages.
Accessing Action Metadata
The Action class in django.contrib.admin.options is used to store metadata about registered actions. While older versions of Django used tuples to represent actions, this codebase uses the Action dataclass.
If you are programmatically inspecting actions, access attributes directly rather than using index-based access or unpacking:
# Recommended approach using Action attributes
def process_action(action_obj):
func = action_obj.func
name = action_obj.name
description = action_obj.description
locations = action_obj.locations
Troubleshooting and Gotchas
- Select Across Limitation: When an action is executed from the
CHANGE_FORM(individual object view), theselect_acrossfunctionality is automatically disabled (forced toFalse). - Deprecation Warning (Tuple Unpacking): Unpacking an
Actionobject as a tuple (e.g.,func, name, desc = action) is deprecated and will trigger aRemovedInDjango70Warning. Always use attribute access. - Custom get_actions Signature: If you override
get_actions()orget_action_choices()in yourModelAdmin, you must include theaction_locationparameter in the method signature to avoid deprecation warnings. - Response Handling: If your action function returns an
HttpResponse(or a subclass likeStreamingHttpResponse), the admin will return that response to the user. If it returnsNone, the admin will redirect the user back to the original view.