class ButtonAdmin(admin.ModelAdmin): """ A subclass of this admin will let you add buttons (like history) in the change view of an entry. ex. class FooAdmin(ButtonAdmin): ... def bar(self, request, obj=None): if obj != None: obj.bar() return None # Redirect or Response or None bar.short_description='Example button' list_buttons = [ bar ] change_buttons = [ bar ] you can then put the following in your admin/change_form.html template: {% block object-tools %} {% if change %}{% if not is_popup %} {% endif %}{% endif %} {% endblock %} """ change_buttons=[] list_buttons=[] def button_view_dispatcher(self, request, url): # Dispatch the url to a function call if url is not None: import re res = re.match('(.*/)?(?P\d+)/(?P.*)', url) if res: if res.group('command') in [b.func_name for b in self.change_buttons]: obj = self.model._default_manager.get(pk=res.group('id')) response = getattr(self, res.group('command'))(request, obj) if response is None: from django.http import HttpResponseRedirect return HttpResponseRedirect(request.META['HTTP_REFERER']) return response else: res = re.match('(.*/)?(?P.*)', url) if res: if res.group('command') in [b.func_name for b in self.list_buttons]: response = getattr(self, res.group('command'))(request) if response is None: from django.http import HttpResponseRedirect return HttpResponseRedirect(request.META['HTTP_REFERER']) return response # Delegate to the appropriate method, based on the URL. from django.contrib.admin.util import unquote if url is None: return self.changelist_view(request) elif url == "add": return self.add_view(request) elif url.endswith('/history'): return self.history_view(request, unquote(url[:-8])) elif url.endswith('/delete'): return self.delete_view(request, unquote(url[:-7])) else: return self.change_view(request, unquote(url)) def get_urls(self): from django.conf.urls.defaults import url, patterns from django.utils.functional import update_wrapper # Define a wrapper view def wrap(view): def wrapper(*args, **kwargs): return self.admin_site.admin_view(view)(*args, **kwargs) return update_wrapper(wrapper, view) # Add the custom button url urlpatterns = patterns('', url(r'^(.+)/$', wrap(self.button_view_dispatcher),) ) return urlpatterns + super(ButtonAdmin, self).get_urls() def change_view(self, request, object_id, form_url='', extra_context=None): if not extra_context: extra_context = {} if hasattr(self, 'change_buttons'): extra_context['buttons'] = self._convert_buttons(self.change_buttons) if '/' in object_id: object_id = object_id[:object_id.find('/')] return super(ButtonAdmin, self).change_view(request, object_id, form_url, extra_context) def changelist_view(self, request, extra_context=None): if not extra_context: extra_context = {} if hasattr(self, 'list_buttons'): extra_context['buttons'] = self._convert_buttons(self.list_buttons) return super(ButtonAdmin, self).changelist_view(request, extra_context) def _convert_buttons(self, orig_buttons): buttons = [] for b in orig_buttons: buttons.append({ 'func_name': b.func_name, 'short_description': b.short_description }) return buttons