Difficulty: Intermediate
Admin actions allow you to perform bulk operations on selected objects from the list view. Django includes a built-in 'Delete selected' action, and you can create custom actions for any operation - publishing articles, approving comments, exporting data, sending notifications, and more.
An action is a function that takes three arguments: the ModelAdmin instance, the request, and a QuerySet of selected objects. You register it by adding it to the ModelAdmin's 'actions' list. The @admin.action decorator lets you set the action's description (the text shown in the dropdown).
Actions appear in a dropdown above the list view. Users select objects using checkboxes, choose an action from the dropdown, and click 'Go'. Django passes the selected objects as a QuerySet to your action function, which processes them and optionally returns a message.
The Django messages framework integrates with admin actions to provide feedback. Use self.message_user() to display success, warning, or error messages after an action completes. Messages appear at the top of the page after the action redirects back to the list view.
For destructive actions, you can add a confirmation step by returning an intermediate page instead of performing the action immediately. This prevents accidental bulk operations. You can also restrict actions to specific users by checking permissions in the action function.
You can also define actions as methods on the ModelAdmin class, which gives them access to the admin instance (self) for more complex operations. The @admin.action decorator sets the description and permissions required for the action.
from django.contrib import admin
from .models import Article
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'status', 'published_at']
actions = ['publish_articles', 'unpublish_articles', 'mark_as_featured']
@admin.action(description='Publish selected articles')
def publish_articles(self, request, queryset):
count = queryset.update(status='published')
self.message_user(
request,
f'{count} article(s) published successfully.',
)
@admin.action(description='Unpublish selected articles')
def unpublish_articles(self, request, queryset):
count = queryset.update(status='draft')
self.message_user(
request,
f'{count} article(s) set to draft.',
)
@admin.action(description='Mark selected as featured')
def mark_as_featured(self, request, queryset):
queryset.update(is_featured=True)
Actions are methods on ModelAdmin listed in the actions attribute. @admin.action sets the dropdown text. queryset.update() performs bulk updates efficiently. message_user() shows feedback to the admin.
from django.contrib import admin, messages
from .models import Order
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ['id', 'customer', 'status', 'total']
actions = ['mark_shipped', 'cancel_orders']
@admin.action(
description='Mark selected orders as shipped',
permissions=['change'],
)
def mark_shipped(self, request, queryset):
pending = queryset.filter(status='processing')
count = pending.update(status='shipped')
skipped = queryset.count() - count
self.message_user(
request,
f'{count} order(s) marked as shipped.',
messages.SUCCESS,
)
if skipped:
self.message_user(
request,
f'{skipped} order(s) skipped (not in processing state).',
messages.WARNING,
)
@admin.action(
description='Cancel selected orders',
permissions=['delete'],
)
def cancel_orders(self, request, queryset):
active = queryset.exclude(status__in=['cancelled', 'delivered'])
count = active.update(status='cancelled')
self.message_user(
request,
f'{count} order(s) cancelled.',
messages.SUCCESS,
)
Actions can check order status before updating and report different message levels (SUCCESS, WARNING). The permissions parameter restricts the action to users with specific model permissions.
import csv
from django.contrib import admin
from django.http import HttpResponse
from .models import Product
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'price', 'stock', 'category']
actions = ['export_to_csv']
@admin.action(description='Export selected to CSV')
def export_to_csv(self, request, queryset):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="products.csv"'
writer = csv.writer(response)
writer.writerow(['Name', 'Price', 'Stock', 'Category'])
for product in queryset:
writer.writerow([
product.name,
product.price,
product.stock,
product.category.name if product.category else '',
])
self.message_user(
request,
f'Exported {queryset.count()} products to CSV.',
)
return response
Actions can return HttpResponse objects. This CSV export action creates a downloadable file from selected products. The Content-Disposition header triggers a file download in the browser.
from django.contrib import admin
from .models import Comment
# Standalone function (not a method)
@admin.action(description='Approve selected comments')
def approve_comments(modeladmin, request, queryset):
queryset.update(is_approved=True)
modeladmin.message_user(
request,
f'{queryset.count()} comment(s) approved.',
)
@admin.action(description='Reject selected comments')
def reject_comments(modeladmin, request, queryset):
queryset.update(is_approved=False)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ['content_preview', 'author', 'is_approved']
actions = [approve_comments, reject_comments]
@admin.display(description='Comment')
def content_preview(self, obj):
return obj.content[:100]
Actions can be standalone functions defined outside the ModelAdmin. The first argument is modeladmin (the admin instance), not self. This is useful for reusable actions shared across multiple ModelAdmin classes.
actions, action Decorator, Bulk Operations, Message Framework, Confirmation