How do I add custom actions to a change model form in Django Admin?

Viewed 19207

I'm working with Django admin, and I'd like to be able to use an existing instance of a model as a template for making a new object, so people using django admin don't need to re-key all the same properties on a object, when making new objects.

I'm picturing it a bit like this at the bottom of the Django admin form for updating a single object:

bottom bar of a django admin view

The django docs explain how to add bulk actions, by adding to the actions on a model, like so:

class ArticleAdmin(admin.ModelAdmin):

    actions = ['make_published']

    def make_published(self, request, queryset):
        queryset.update(status='p')

    make_published.short_description = "Mark selected stories as published"

However, it wasn't so clear to me how to do this for a single change model form on an object, for actions I only want to apply to model at a time.

How would I do this?

I'm guessing I probably need to hack around with the change_model form, but beyond that, I'm not so sure.

Is there a fast way to do this without overriding loads of templates ?

2 Answers

As pointed out, there is not a way and needs to be hacked. Here's I think an elegant hack for adding custom actions to both the list and change form views. It doesn't actually save the form just execute whatever custom action you want against the current object and return you back to the same change form page.

from django.db.models import Model

from django.contrib import admin, messages
from django.contrib.admin.options import (
    unquote,
    csrf_protect_m,
    HttpResponseRedirect,
)


class ArticleAdmin(admin.ModelAdmin):
    change_form_template = 'book/admin_change_form_book.html'

    actions = ['make_published']

    def make_published(self, request, queryset):
        if isinstance(queryset, Model):
            obj = queryset
            obj.status = 'p'
            obj.save()
            updated_count = 1
        else:
            updated_count = queryset.update(status='p')

        msg = "Marked {} new objects from existing".format(updated_count)
        self.message_user(request, msg, messages.SUCCESS)

    make_published.short_description = "Mark selected stories as published"

    @csrf_protect_m
    def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
        if request.method == 'POST' and '_make_published' in request.POST:
            obj = self.get_object(request, unquote(object_id))
            self.make_published(request, obj)
            return HttpResponseRedirect(request.get_full_path())

        return admin.ModelAdmin.changeform_view(
            self, request,
            object_id=object_id,
            form_url=form_url,
            extra_context=extra_context,
        )

Now you can add an <input> for the action to the custom template view (this example uses book/admin_change_form_book.html in change_form_template)

{% extends 'admin/change_form.html' %}

{% block form_top %}
<input
    type="submit"
    name="_make_published"            
    value="Mark Published"
    class="grp-button grp-default"
>
{% endblock %}

If you look at the admin/change_form.html (i.e. "django/contrib/admin/templates/admin/change_form.html") you can insert this custom <input> action anywhere between the <form...> </form> tags on the page. Including these blocks:

  • {% block form_top %}
  • {% block after_related_objects %}
  • {% block submit_buttons_bottom %}
Related