Skip to content

Custom Actions In Django Admin Object Editor

by Paul Kenjora on October 7th, 2008

I’ve seen many posts asking for the simplest feature in Django admin… the ability to add custom actions next to History and View On Site in the Django admin form. The page where the actual object is edited, not the list pages. Imagine adding actions like:

  • Edit Next Item
  • Edit Previous Item
  • Send Thank You Email
  • Export Record As CSV

Thats just the tip of the iceberg. Basically any action can be performed. The concept is simple, create an buton for an action, redirect to a handler with model and id of object, execute any code, then redirect back to the admin page. This would turn Django admin into any application the developer chooses.

I first wrote on this topic in Adding Custom Actions To Django Admin Change Forms, back when Django was in version 0.95. I’m writing this post again to document the code port from 0.96 to 0.96 and above.

Changes To Django

First add code to Django to facilitate custom actions. Working backwards from the template is the simplest way to visualize the changes.

Step One: Add a list of custom actions to the Django admin change form template. I added the for loop…

contrib/admin/templates/admin/change_form.html

You may have noticed that the custom action will call a URL of the format /admin/handler/model/id/. I think its simple and provides enough information to identify the object being edited in all circumstances. There may be some overlap with admin URLs so please be careful or change the URL here.

Step Two: Add a member to the Django admin change form handler to be passed into the template. This is just a one liner, add form_action to the context dictionary.

contrib/admin/views/main.py

Step Three: Add the new custom form_actions field to the Admin class. You’re really adding it to the AdminOptions class, but you get the picture. I added the form_actions to the function signature and the list of member variables.

db/models/options.py

Those three files is all you have to edit. Now you have support for custom actions. I assume you’re OK with the custom action protocol I implemented above. If you want to change it then I leave the edits up to you. If you take me at my word, then all you have to do is add the proper code to your models file and your URL handlers.

Your Custom Handler

All the pieces are in place on the Django admin side. Now to create a custom handler.

Step One: Add a list of actions to your model definition under the Admin class.

models.py

If you refresh your Admin page here you will see the new custom action.

Step Two: Create a handler for your new action in the URL handler. Remember if you use admin as your prefix then place the handler regexp before the one for Django admin.

urls.py

You’re done. Add as many custom actions as please you. I recommend redirecting back to the Django admin page after executing each action. You have the model id and the object id, you can even create generic handlers for any object. In my case I typically only use the object id, each handler is model specific in my case.

Request To Readers

I would really like it if this feature was included in the official Django release. Please leave a comment in support of this code. I would like to approach the Django maintenance group and petition a feature request based on your comments. The more comments in support, the more likely we will see this in the official Django version.

  • sclaughl
    I realize this post is a year and a half old by now, but I just came across it today and found it useful, so thanks for that.

    In case anyone else stumbles across it like I did, I thought I'd add some info pertaining to Django 1.1.

    The good news is that in Django 1.1 you can do something like this with no modifications to django core / contrib. Here's how...

    Step one remains about the same. Of course you will want to copy the change_form.html into your particular project, rather than modifying the global default change_form template. Here's the line to add, updated for 1.1...

    {% for action in opts.form_actions %} <li>{{ action.1 }}</li>{% endfor %}

    Skip steps two and three, as well as the next step, i.e. step one of 'Your Custom Handler'. Now open your models.py and locate the model to which you want to add form actions. For that model create a Meta class and give it a form_actions attribute.

    class Meta:
    form_actions = (('action_name', 'action_title'), )

    In order to keep Django from objecting to this new and unfamiliar attribute, include this code near the top of your models.py...

    import django.db.models.options as options

    options.DEFAULT_NAMES = options.DEFAULT_NAMES + ('form_actions',)

    Now update your urls.py as desired and you're done!

    (r'^admin/action_name/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', 'myproj.myapp.views.action_view'),

    HTH,

    --Stuart</object_id></content_type_id>
  • sclaughl
    Replying to myself...

    I do wonder if this type of information should be specified on the model like I am doing. It would probably make more sense to specify it on the ModelAdmin. But the way I describe was the first workable way I could find to implement it without touching django core/contrib.

    --Stuart
  • tomovo
    +1 for this to be included in the official django code. Overriding the templates and adding stuff to urls.py seems a bit clunky for something so incredibly useful as this.

    Thanks a lot for the article.
  • Gert Steyn
    You have my vote!

    This is a very minor change to the admin code and adds a feature that I think at least 70% of Django sites would want to use at some stage.

    My suggestion for the URL would be:
    href="/admin/{{ content_type_id }}/{{ object_id }}/{{ action.0 }}/"

    That will give you:
    /admin/clients/123/update
    /admin/homes/233/update

    As apposed to:
    /admin/update/clients/123/
    /admin/update/homes/233/

    Makes more sense if you think about it and a gives you a much cleaner urls.py
  • Andreas E
    This is cool and useful stuff. Thanks!

    +1
  • Lars Holm Nielsen
    I'm definitely +1 for getting custom actions in the admin into the trunk of django.

    The counter argument might however be, that it already possible to do this by overriding the templates. But from my point of view, it's too cumbersome to override templates all the time compared to just adding an action in Admin options. It's very rarely I need more customization of the change form than what the admin can already give me (except for adding extra actions).

    I agree though with Waylan that the way URLs for action are included is not ideal. I guess something could be added to contrib/admin/sites.py in AdminSite.root or AdminSite.app_index to do routing.

    Following is just a quick brain dump of ideas, so feel free to shoot them down:

    I wouldn't put the action_title in the form_actions, but just use the same principles as when adding a column based on a model method instead of a model field to the admin list view. Also the action_name I would make reference a method in Admin class, a or a callable (e.g. a view in views.py).

    {{{
    class Admin:
    form_actions = ('action_name',)

    # Title of action name will become 'Action name'
    def action_name(request, id):
    ...
    # if default title is not good enough, it can
    # be overwritten by:
    action_name.description = 'Title override'
    }}}
  • This would be handy for things like comment moderation. I'm currently in the process of working out a simple, fairly generic way to add "train as spam" and "train as ham" to my django-spambayes[1] app. Custom actions on change forms would do the trick.

    The only concern I have is that I'm not a fan of how urls are added to urls.py. But, I don't have any ideas for something better either. It just feels a little hacky for a built-in feature. Like defining the action should automatically add a url pattern to the admin app's url patterns or something.

    [1]: http://code.google.com/p/django-spambayes/
blog comments powered by Disqus