A Django app for adding object tools for models in the admin
npx @tessl/cli install tessl/pypi-django-object-actions@5.0.0A Django app for adding object tools for models in the admin interface. Django Object Actions extends Django's admin action framework to work on individual model instances in addition to querysets, enabling custom actions on both change and changelist views.
pip install django-object-actionsfrom django_object_actions import DjangoObjectActions, action, takes_instance_or_querysetFor advanced use cases:
from django_object_actions import BaseDjangoObjectActionsfrom django.contrib import admin
from django_object_actions import DjangoObjectActions, action
class ArticleAdmin(DjangoObjectActions, admin.ModelAdmin):
@action(label="Publish", description="Submit this article")
def publish_this(self, request, obj):
# Your custom logic here
obj.status = 'published'
obj.save()
self.message_user(request, f"Published {obj.title}")
change_actions = ('publish_this',)
changelist_actions = ()
admin.site.register(Article, ArticleAdmin)INSTALLED_APPS = [
# ... other apps
'django_object_actions',
# ... other apps
]DjangoObjectActions mixin in your ModelAdmin classes.Core mixin classes that extend Django ModelAdmin to support object actions.
Base mixin providing object action functionality without default templates.
class BaseDjangoObjectActions:
"""
ModelAdmin mixin to add new actions just like adding admin actions.
Attributes:
- change_actions: list of str - Action names for change view
- changelist_actions: list of str - Action names for changelist view
- tools_view_name: str - URL name for the actions view
"""
change_actions = []
changelist_actions = []
tools_view_name = None
def get_change_actions(self, request, object_id, form_url):
"""Override to customize what actions appear in change view."""
def get_changelist_actions(self, request):
"""Override to customize what actions appear in changelist view."""Main mixin that includes admin templates and extends BaseDjangoObjectActions.
class DjangoObjectActions(BaseDjangoObjectActions):
"""
Complete mixin with templates for Django admin object actions.
Attributes:
- change_form_template: str - Template for change form
- change_list_template: str - Template for changelist
"""
change_form_template = "django_object_actions/change_form.html"
change_list_template = "django_object_actions/change_list.html"Decorators for enhancing action functions with metadata and behavior.
Adds metadata and behavior configuration to action functions.
def action(
function=None,
*,
permissions=None,
description=None,
label=None,
attrs=None,
methods=('GET', 'POST'),
button_type='a'
):
"""
Add attributes to an action function.
Parameters:
- permissions: list of str - Required permissions
- description: str - Tooltip description for the button
- label: str - Display label for the button (defaults to function name)
- attrs: dict - HTML attributes for the button element
- methods: tuple - Allowed HTTP methods (default: ('GET', 'POST'))
- button_type: str - Button type ('a' for link, 'form' for form submission)
Returns:
Decorated function with added attributes
"""Usage Examples:
@action(label="Publish Article", description="Mark this article as published")
def publish_article(self, request, obj):
obj.status = 'published'
obj.save()
@action(
permissions=['publish'],
description='Submit for publication',
attrs={'class': 'btn-primary'},
methods=('POST',),
button_type='form'
)
def submit_for_publication(self, request, obj):
obj.submit_for_review()Converts admin actions to work with both individual objects and querysets.
def takes_instance_or_queryset(func):
"""
Make standard Django admin actions compatible with object actions.
Converts single model instances to querysets, allowing reuse of
existing admin actions as object actions.
Parameters:
- func: function - Admin action function that expects a queryset
Returns:
Decorated function that works with both instances and querysets
"""Usage Example:
@takes_instance_or_queryset
def mark_as_featured(self, request, queryset):
queryset.update(featured=True)
self.message_user(request, f"Marked {queryset.count()} items as featured")
# Can be used in both contexts:
change_actions = ['mark_as_featured'] # Works on single objects
actions = ['mark_as_featured'] # Works on querysetsActions that operate on individual model instances in the change view.
class MyModelAdmin(DjangoObjectActions, admin.ModelAdmin):
def my_change_action(self, request, obj):
"""
Action function for change view.
Parameters:
- request: HttpRequest - Django request object
- obj: Model instance - The object being acted upon
Returns:
- None: Redirects back to change view
- HttpResponse: Custom response (redirect, render, etc.)
"""
# Your logic here
obj.some_field = 'new_value'
obj.save()
# Optional: Send message to user
self.message_user(request, "Action completed successfully")
# Optional: Custom redirect
# return HttpResponseRedirect('/custom/url/')
change_actions = ('my_change_action',)Actions that operate on querysets in the changelist view.
class MyModelAdmin(DjangoObjectActions, admin.ModelAdmin):
def my_changelist_action(self, request, queryset):
"""
Action function for changelist view.
Parameters:
- request: HttpRequest - Django request object
- queryset: QuerySet - The selected objects
Returns:
- None: Redirects back to changelist view
- HttpResponse: Custom response
"""
# Bulk operation
updated = queryset.update(status='processed')
self.message_user(request, f"Updated {updated} items")
changelist_actions = ('my_changelist_action',)Control which actions are available based on context.
class MyModelAdmin(DjangoObjectActions, admin.ModelAdmin):
def get_change_actions(self, request, object_id, form_url):
"""
Dynamically determine available change actions.
Parameters:
- request: HttpRequest - Current request
- object_id: str - Primary key of the object
- form_url: str - URL of the change form
Returns:
list of str - Action names to display
"""
actions = list(super().get_change_actions(request, object_id, form_url))
# Example: Remove action based on user permissions
if not request.user.is_superuser:
actions = [a for a in actions if a != 'dangerous_action']
# Example: Remove action based on object state
obj = self.model.objects.get(pk=object_id)
if obj.status == 'published':
actions = [a for a in actions if a != 'publish_action']
return actions
def get_changelist_actions(self, request):
"""
Dynamically determine available changelist actions.
Parameters:
- request: HttpRequest - Current request
Returns:
list of str - Action names to display
"""
actions = list(super().get_changelist_actions(request))
# Example: Role-based action availability
if request.user.groups.filter(name='editors').exists():
actions.append('editor_special_action')
return actionsActions can return custom HTTP responses for complex workflows.
def complex_action(self, request, obj):
"""Action with custom response handling."""
from django.shortcuts import render
from django.http import HttpResponseRedirect
if request.method == 'POST':
# Process the action
obj.process_complex_operation()
self.message_user(request, "Operation completed")
return None # Redirect back to change view
# Show confirmation page
return render(request, 'admin/confirm_complex_action.html', {
'object': obj,
'title': 'Confirm Complex Action'
})Reuse existing admin actions as object actions.
class MyModelAdmin(DjangoObjectActions, admin.ModelAdmin):
def bulk_update_status(self, request, queryset):
"""Standard admin action."""
queryset.update(status='updated')
self.message_user(request, f"Updated {queryset.count()} items")
@takes_instance_or_queryset
def single_update_status(self, request, queryset):
"""Same logic, works for both single objects and querysets."""
return self.bulk_update_status(request, queryset)
# Available in both contexts
actions = ['bulk_update_status']
change_actions = ['single_update_status']
changelist_actions = ['bulk_update_status']Customize button appearance and behavior with HTML attributes.
@action(
label="Delete Safely",
description="Safely delete this item with confirmation",
attrs={
'class': 'btn btn-danger',
'onclick': 'return confirm("Are you sure?");',
'data-toggle': 'tooltip',
'data-placement': 'top'
}
)
def safe_delete(self, request, obj):
obj.delete()
self.message_user(request, "Item deleted successfully")The framework handles common error scenarios:
Http404 if action doesn't existHttpResponseNotAllowed for invalid methodsDjango Object Actions provides three templates that extend Django's admin templates:
django_object_actions/change_form.html - Adds action buttons to change viewdjango_object_actions/change_list.html - Adds action buttons to changelist viewdjango_object_actions/action_trigger.html - Renders individual action buttonsThese templates automatically integrate with your existing admin customizations and preserve all Django admin functionality.