How to write custom Django admin actions

Aug 2018

Django admin actions are simple functions that provide an easy way to apply a given command to the selected objects in an admin change list view. By default Django ships with a “delete selected objects” action available to all models.

Django admin actions example

This action when applied will delete all the selected instances in the change list. Being simple functions, you can write your own admin actions to perform a given action on the selected objects. In this tutorial we will create a simple command to update the status of some fictional orders in a likewise fictitious e-commerce store.

Set up

To begin with, lets create a new Django project called shop and within this project a Django app which we’ll call main for simplicity.

$ django-admin startproject shop
$ cd shop
$ python manage.py startapp main

Next lets add the main app to the installed apps list in settings.py

# shop/settings.py
# ...
INSTALLED_APPS = [
    'main',

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Lets create a new Django model in main/models.py which will store some fictional order records.

# main/models.py
from django.db import models


class Order(models.Model):

    PENDING, CONFIRMED, PAID, CANCELLED = ('PE', 'CF', 'PD', 'CL')
    ORDER_STATUSES = (
        (PENDING, 'Pending'),
        (CONFIRMED, 'Confirmed'),
        (PAID, 'Paid'),
        (CANCELLED, 'Cancelled'),
    )

    order_status = models.CharField(max_length=2, choices=ORDER_STATUSES)
    amount = models.PositiveSmallIntegerField()
    date_placed = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return '{} {}'.format(self.order_status, self.amount)

    class Meta:
        ordering = ('-date_placed', )

Next lets run makemigrations and migrate to persist our changes in the database.

$ python manage.py makemigrations main
$ python manage.py migrate

This being a model storing fictitious orders, lets drop into the Django shell and generate some fictitious values. Using the bulk_create queryset method, lets create 100 random order instances.

>>> import random
>>> from main.models import Order
>>> choices = ('PE', 'CF', 'PD', 'CL')
>>> bulk = []
>>> for _ in range(100):
...     kwargs = {'order_status': random.choice(choices), 'amount': random.randint(100, 3000)}
...     bulk.append(Order(**kwargs))
... 
>>> Order.objects.bulk_create(bulk)

Lets also register the model in the Django admin:

from django.contrib import admin

from .models import Order


class OrderAdmin(admin.ModelAdmin):

    list_display = ('order_status', 'date_placed', 'amount')


admin.site.register(Order, OrderAdmin)

The cancel orders custom admin action

Consider the case where some pending orders were cancelled for one reason or another. By using a custom Django admin action, we can conveniently update these orders and set them to cancelled in the admin dashboard. Admin action functions are just regular functions that take three arguments:

  • The current ModelAdmin instance
  • A request object representing the current request,
  • A queryset containing the set of objects selected by the user.

By convention an admin action can either be a module level function in the admin.py where we want to register it, or as a method on the ModelAdmin class we are registering it. Defining actions as methods gives the action more straightforward access to the ModelAdmin itself, allowing the action to call any of the methods provided by the admin. Lets create a cancel_orders action as a method on the OrderAdmin instance.

from django.contrib import admin

from .models import Order


class OrderAdmin(admin.ModelAdmin):

    list_display = ('order_status', 'date_placed', 'amount')
    actions = ['cancel_orders', ]

    def cancel_orders(self, request, queryset):
        queryset.update(order_status=Order.CANCELLED)
    cancel_orders.short_description = "Mark selected orders as cancelled"


admin.site.register(Order, OrderAdmin)

On creating the action method, you have to tell the ModelAdmin of its existence by adding it a the list of actions on the ModelAdmin. You can have as many actions as your use case demands.

By default, this action would appear in the action list as “Cancel orders” – the function name, sentence case with underscores replaced by spaces. That’s fine, but we can provide a better, more human-friendly name by giving the cancel_orders function a short_description attribute.

Our action will look something like this in the admin:

Cancel orders action

When the action is applied, the selected orders will be set to cancelled.

We may also add messages to display to the user if the action performed was successful or an error occurred using the message_user method. Lets display a message to inform the user the operation was successful if indeed it so.

# ...
def cancel_orders(self, request, queryset):
    count = queryset.update(order_status=Order.CANCELLED)
    self.message_user(request, '{} orders cancelled.'.format(count)
cancel_orders.short_description = "Mark selected orders as cancelled"

You may take the pleasure of visiting the Django docs and read more on custom admin actions.

Happy coding!

Subscribe to our Newsletter

Receive updates when we add new content. No spam or some funny tricks.