Library / SDK
crccheck/django-object-actions avatar
crccheck/django-object-actions

django-object-actions: per-object tools in the Django admin

A Django app for easily adding object tools in the Django admin

810 stars88 forksPythonApache-2.0

At a glance

What is it?
django-object-actions adds buttons that act on a single record in the Django admin, using the same decorator and the same admin action machinery, so a per-object tool and a bulk action can be the same function. The version 5 line adds a global setting to render action buttons as POST forms, with the default scheduled to change in the next major release.
Who is it for?
django-object-actions suits a Django project whose admin users need to act on one record at a time, such as publishing a single article or tightening one set of fasteners, and it removes a class of workarounds where the same logic is written twice.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 149 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem the package solves

The README opens with the complaint it exists to answer. If you have ever tried making admin object tools you may have thought, why can't this be as easy as making Django Admin Actions? Well now they can be.

That is the whole idea. Django's admin has a well known mechanism for bulk actions, functions on a ModelAdmin listed in an actions attribute, which the changelist view renders as a dropdown. What the admin does not have is a per-record version of that, a button on the change form that operates on the single object being edited. Teams that want one typically end up overriding the change form template, wiring up a custom view, and duplicating the permission checks and the message handling that admin actions give you for free.

django-object-actions makes the object action a first class citizen. The package provides a DjangoObjectActions mixin for your ModelAdmin, an action decorator, and a takes_instance_or_queryset decorator for reusing existing admin actions.

The framing in the README is worth noticing: this is described as a Django app for adding object tools, not a library of buttons. It installs templates and static CSS into the admin, it ships an example_project so you can see the behaviour, and it is packaged on PyPI as django-object-actions. The license is Apache-2.0 and the homepage is the PyPI project page.

Installing it and the first object action

The quick start is a pip install.

shell
$ pip install django-object-actions

Then django_object_actions goes into INSTALLED_APPS so Django can find the templates, and the admin class mixes in the behaviour.

python
from django_object_actions import DjangoObjectActions, action

class ArticleAdmin(DjangoObjectActions, admin.ModelAdmin):
    @action(label="Publish", description="Submit this article") # optional
    def publish_this(self, request, obj):
        publish_obj(obj)

    change_actions = ("publish_this", )
    changelist_actions = ("action_name", )

The shape is the same as a Django admin action, with one difference that the README highlights. The functions take an object instance instead of a queryset.

The two attributes are where the actions are registered. change_actions lists the tool buttons that appear on the change form, and changelist_actions lists the actions that appear on the main changelist view, where you get a queryset like a regular admin action.

The mixin must come first in the base class list, which is a detail that catches people. DjangoObjectActions has to be ahead of admin.ModelAdmin for its method resolution to win, and reversing the order silently gives you a ModelAdmin with no object actions and no error message.

Reusing one function for both a single object and a queryset

This is the feature that makes the package more than a button generator.

The takes_instance_or_queryset decorator converts object instances into a queryset and passes querysets, so a single function can serve as both a per-object tool and a bulk action.

python
from django_object_actions import DjangoObjectActions, takes_instance_or_queryset

class RobotAdmin(DjangoObjectActions, admin.ModelAdmin):
    # ... snip ...

    @takes_instance_or_queryset
    def tighten_lug_nuts(self, request, queryset):
        queryset.update(lugnuts=F('lugnuts') - 1)

    change_actions = ("tighten_lug_nuts", )
    actions = ("tighten_lug_nuts", )

The same function name appears in change_actions and in actions, and the decorator makes it work in both places. The implementation uses an F expression, so the decrement happens in the database rather than by loading and saving each object.

For a team that has an admin action and now also wants a per-record button for it, that is a one decorator change rather than a second implementation. The alternative, which is what most projects end up with, is a queryset action and a separate single-object action that happens to call the same helper, and the two drift apart over time.

The other thing the README notes is that you can send a message with self.message_user, exactly as with admin actions, and that returning an HttpResponse makes the tool follow it, in the same way an admin action does.

Labels, tooltips and button attributes

The action decorator carries three optional arguments, and each maps onto something the admin already knows about.

The label is the text on the button, the description is the tooltip, and attrs is a Django widget style attrs dictionary that goes straight onto the rendered element. That last one is the escape hatch, since the README points at the Django documentation for widget attrs and gives an example with a class of addlink, which is the admin's own link styling.

python
@action(label="Vote++")
def increment_vote(self, request, obj):
    obj.votes = obj.votes + 1
    obj.save()

If you prefer the older style, you can set the equivalent attributes on the function itself after the definition, which is the same mechanism admin actions use.

python
def increment_vote(self, request, obj):
    obj.votes = obj.votes + 1
    obj.save()
increment_vote.label = "Vote++"

And by default the package guesses the button label from the function name, which is why the README shows the label being set explicitly in most of its examples. The short_description attribute also works, since the README notes it is similar to how admin actions work.

The decorator form and the attribute form are both supported, and the decorator is the newer and more readable of the two. The attribute form exists because it is the pattern Django admin developers already have in their fingers.

Disabling actions programmatically per object

Static registration is not enough in practice, because a tool often only makes sense for some states of an object. The README's example is the case of not being able to close a company account if the account is already closed.

You override get_change_actions, call the superclass implementation, and prune the list.

python
def get_change_actions(self, request, object_id, form_url):
    actions = super(PollAdmin, self).get_change_actions(request, object_id, form_url)
    actions = list(actions)
    if not request.user.is_superuser:
        return []

    obj = self.model.objects.get(pk=object_id)
    if obj.question.endswith('?'):
        actions.remove('question_mark')

    return actions

Two things are worth noticing in that snippet. The superuser check returns an empty list, so the buttons disappear entirely rather than appearing and failing when clicked, and the list is copied with list() before being modified, because the superclass implementation may return a tuple.

The object is loaded from the database by primary key, and the condition is then evaluated against it, so the visibility rule can depend on the object's current state. The same applies to changelist actions through get_changelist_actions.

This is the feature that makes object actions safe to expose to non-administrator staff, since a button that is hidden is a button that cannot be triggered by a crafted request.

The GET to POST migration in the next major version

This is the most consequential thing in the README for anyone running this in production, and it is worth reading twice.

Since actions usually change data, for safety and semantics it would be preferable that actions use an HTTP POST instead of a GET. Today the buttons render as links, and a link that mutates state is a link that a browser prefetcher, a crawler or a user dragging it into a new tab can trigger.

You can opt into the new behaviour globally with a settings flag.

python
# settings.py
DJANGO_OBJECT_ACTIONS_DEFAULT_HTTP_METHOD = "POST"  # or "GET"

When set to POST, action buttons render as forms that submit via POST. When set to GET, or when the setting is absent, they render as regular links.

The migration note is explicit: the default will change from GET to POST in the next major version. So the setting lets you adopt the new behaviour now, and lets you opt out later if a specific integration depends on the link semantics.

Per-action configuration is also possible, with the methods argument and a button_type parameter.

python
@action(methods=("POST",), button_type="form")
def my_action(self, request, obj):
    ...

The button_type parameter chooses the DOM element. The value a, which is the default for GET, renders an anchor tag, and the value form renders a form that submits via POST.

Installation alternatives and the template dependency

The app entry in INSTALLED_APPS is the normal route, and the README explains what it is for. It is there so Django can find the templates, and if you do not intend to use the template customisations or the bundled CSS at all, you should not add django_object_actions to INSTALLED_APPS at all and should use BaseDjangoObjectActions instead of DjangoObjectActions.

There is a middle option too. You do not have to add the app at all; all you need to do is copy the template django_object_actions/change_form.html somewhere the Django template loader will find it.

The reason the CSS matters is that the base class needs the stylesheet, and if you use button_type form actions you need to add a Media declaration to your admin class.

python
class Media:
    css = {"all": ["django_object_ac

The template itself is shipped as package data, with the pyproject listing templates/django_object_actions/*.html and static/django_object_actions/css/*.css under the package data configuration, so the files ship inside the wheel rather than being generated.

One warning the README puts in the middle of the usage section is worth carrying into any project that already customises the admin. If your admin modifies get_urls, change_view or changelist_view, you will need to take extra care, because django-object-actions uses them too. That is a composition hazard rather than a limitation, and it is the one thing to check first if a custom admin stops behaving as expected after installing the package.

Where django-object-actions is the wrong choice

The first limitation is that it is an admin-only concern. If the operation you want to expose is not an administrative one, or if your users never use the Django admin, the package does nothing for you. The whole surface is the change form and the changelist.

The second is the setup cost for a small need. A mixin, an app entry, a template dependency and a decorator per action is a fair amount of ceremony for a project with one button, and for a single site-wide operation a Django admin action already covers the case without any of it.

The third is the dependency on the admin's internals. The package hooks get_urls, change_view and changelist_view, which are extension points with a history of changing between Django versions, so a Django upgrade is where this package is most likely to need attention. The pyproject classifiers list support through Python 3.14 and the package claims a development status of Production/Stable, but the coupling to admin internals is inherent to the approach rather than a defect in it.

The fourth is the HTTP method change. If your team has automation that follows admin action links, or a reporting system that scrapes the change page, the move to POST changes the shape of the URLs and the requests. The setting makes it opt-in now, but it is a change to plan for.

The fifth is the missing pieces. There is no confirmation dialog, no batch of per-object buttons, and no separation of the button from the permission on the underlying model field, so an action that changes a field has to do its own authorisation. The Django admin's own permission model applies to the model, not to the individual tool.

How it compares with a custom admin mixin and with django-admin-actions

The comparison with a hand written admin mixin is the one most teams have already attempted. The difference in approach is that a custom mixin usually overrides change_form_template or the change view and injects its own form, which means the button has to be wired to a URL, the URL has to a view, the view has to check permissions, and the response has to redirect back with a message. django-object-actions takes the machinery Django already has for admin actions, which already does the permission check, the message handling and the response, and adapts the object into a queryset so the same code path works.

The third comparison is with a custom admin site. Rebuilding the admin, or embedding a second interface such as Django REST Framework plus a custom frontend, gives you full control over the interaction, including confirmation dialogs and richer interfaces, and it costs you the whole admin that Django gives you for free.

The fourth is the practical one. If the tools you need are operations on one object, defined in code, shown as buttons in the admin, this package is a very small amount of code for a large reduction in work, and the reuse decorator is the part that pays for the setup most quickly.

Licence, releases and how the project is developed

The licence field on the repository records Apache-2.0, with the LICENSE file at the root, and the pyproject declares the same with a text licence entry. That is a permissive licence with an explicit patent grant, and for a package you install into your own project the obligations are minimal.

The project is not archived and the last push was on 2026-05-29, so it is being maintained. The recent releases are v5.1.2 from 2026-05-28, v5.1.1 from 2026-05-22 and v5.1.0 from 2026-05-22, a tight patch sequence, and the pyproject version is 5.1.2. The changelog is generated by python-semantic-release, with a custom changelog template in .github/workflows/release, and the version is written into both the pyproject and the package's __init__.

The package requires Python 3.9 or newer and the classifiers list Django as the framework and Python 3.9 through 3.14. The development dependencies are the ones you would expect for a Django package, with coverage, Django 4.2 or newer, django-extensions, factory-boy for test fixtures and dj-database-url for database configuration.

There is an example_project at the root and a Dockerfile that makes it easy to see the package in action. The image is python:3.14-alpine, it installs the package in editable mode with the dev extras, runs make resetdb, and then creates a superuser through the manage.py shell so the demo is usable immediately, before running the development server on port 8000.

The Makefile gives the development commands, with a quickstart target, a dev target that runs the example project, a tdd target that watches for changes with nodemon, a test target, a coverage target, lint with ruff, and docker build and docker run targets. A CI badge in the README points at the GitHub Actions workflow.

Editorial conclusion

django-object-actions suits a Django project whose admin users need to act on one record at a time, such as publishing a single article or tightening one set of fasteners, and it removes a class of workarounds where the same logic is written twice. It is the wrong tool for a project with no single-object workflows, since the setup is pure overhead in that case, and it is worth pausing over if you rely on GET semantics, because the next major version changes the default to POST. Before upgrading, read the migration note and set DJANGO_OBJECT_ACTIONS_DEFAULT_HTTP_METHOD explicitly, and if you override get_urls, change_view or changelist_view, check that your code still composes with what the package installs.

Frequently asked questions

What does django-object-actions add to the Django admin?

It adds tool buttons that act on a single object on the change form, in the same way Django admin actions act on a queryset. You mix DjangoObjectActions into your ModelAdmin, define functions with the action decorator, and register them in a change_actions attribute.

How do I install django-object-actions?

Run `pip install django-object-actions`, add django_object_actions to INSTALLED_APPS so Django can find the templates, and mix DjangoObjectActions into your ModelAdmin ahead of admin.ModelAdmin. The package requires Python 3.9 or newer.

Can one function serve as both an object action and a bulk admin action?

Yes. The takes_instance_or_queryset decorator converts object instances into a queryset and passes querysets, so the same function name can appear in both change_actions and actions and work in both places.

How do I hide an object action for some objects?

Override get_change_actions, call the superclass implementation, copy the result to a list, and remove the actions that should not appear based on the object's state or the request's user. The same applies to changelist actions through get_changelist_actions.

What is the GET to POST change in django-object-actions?

The next major version will change the default HTTP method for actions from GET to POST, so action buttons will render as forms rather than links. You can adopt it now by setting DJANGO_OBJECT_ACTIONS_DEFAULT_HTTP_METHOD to "POST" in your Django settings, or configure individual actions with the methods and button_type arguments on the action decorator.

Do I have to add django_object_actions to INSTALLED_APPS?

No. You can instead copy the template django_object_actions/change_form.html somewhere the Django template loader will find it, or use BaseDjangoObjectActions instead of DjangoObjectActions if you do not want the bundled CSS or template customisations. If you use button_type form actions you need to add the stylesheet to your admin class's Media.

Official sources

  1. crccheck/django-object-actions on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/crccheck-django-object-actions.svg)](https://hysenlabs.com/projects/crccheck-django-object-actions)

Was this useful?