Login

Add buttons in admin forms

Author:
mamat
Posted:
September 3, 2008
Language:
Python
Version:
1.0
Tags:
admin buttons
Score:
5 (after 5 ratings)

A subclass of this admin will let you add buttons (like history) in the change view of an entry.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class ButtonableModelAdmin(admin.ModelAdmin):
   """
   A subclass of this admin will let you add buttons (like history) in the
   change view of an entry.

   ex.
   class FooAdmin(ButtonableModelAdmin):
      ...

      def bar(self, obj):
         obj.bar()
      bar.short_description='Example button'

      buttons = [ bar ]

   you can then put the following in your admin/change_form.html template:

      {% block object-tools %}
      {% if change %}{% if not is_popup %}
      <ul class="object-tools">
      {% for button in buttons %}
         <li><a href="{{ button.func_name }}/">{{ button.short_description }}</a></li>
      {% endfor %}
      <li><a href="history/" class="historylink">History</a></li>
      {% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li>{% endif%}
      </ul>
      {% endif %}{% endif %}
      {% endblock %}

   """
   buttons=[]

   def change_view(self, request, object_id, extra_context={}):
      extra_context['buttons']=self.buttons
      return super(ButtonableModelAdmin, self).change_view(request, object_id, extra_context)

   def __call__(self, request, url):
      if url is not None:
         import re
         res=re.match('(.*/)?(?P<id>\d+)/(?P<command>.*)', url)
         if res:
            if res.group('command') in [b.func_name for b in self.buttons]:
               obj = self.model._default_manager.get(pk=res.group('id'))
               getattr(self, res.group('command'))(obj)
               return HttpResponseRedirect(request.META['HTTP_REFERER'])

      return super(ButtonableModelAdmin, self).__call__(request, url)

More like this

Comments

donspaulding (on October 17, 2008):
<p>Just what I was looking for! I'm definitely adding this to my toolbelt.</p> <p>Two things:</p>
  1. On line 44, I found that passing the request, and expecting an HttpResponse gave me more 'view-like' functionality inside my method.
  2. It seems to have 3-space indentation.
<p>Thanks for this!</p>

#

caesarmv (on May 22, 2010):
<p>Rechecked on django 1.2. It needs additional string here.</p> <p>from django.utils.functional import update_wrapper</p> <p>Work code below:</p> <p>class ButtonableModelAdmin(admin.ModelAdmin):</p> <p>buttons=[]</p> <p>def change_view(self, request, object_id, extra_context={}): extra_context['buttons']=self.buttons if '/' in object_id: object_id = object_id[:object_id.find('/')] return super(ButtonableModelAdmin, self).change_view(request, object_id, extra_context)</p> <p>def button_view_dispatcher(self, request, url): if url is not None: import re res=re.match('(./)?(?P<id>\d+)/(?P<command>.)', url) if res: if res.group('command') in [b.func_name for b in self.buttons]: obj = self.model._default_manager.get(pk=res.group('id')) getattr(self, res.group('command'))(obj) return HttpResponseRedirect(request.META['HTTP_REFERER'])</p> <pre> return super(ButtonableModelAdmin, self).__call__(request, url) </pre> <p>def get_urls(self):</p> <pre> from django.conf.urls.defaults import patterns, url from django.utils.functional import update_wrapper def wrap(view): def wrapper(*args, **kwargs): return self.admin_site.admin_view(view)(*args, **kwargs) return update_wrapper(wrapper, view) urlpatterns = patterns('', url(r'^(.+)/$', wrap(self.button_view_dispatcher),) ) + super(ButtonableModelAdmin, self).get_urls() return urlpatterns </pre>

#

arvid (on July 6, 2010):
<p>Last corrections. Tested with Django 1.2.1.</p> <p>from django.contrib import admin from django.http import HttpResponseRedirect</p> <p>class ButtonableModelAdmin(admin.ModelAdmin):</p> <pre>buttons=() def change_view(self, request, object_id, extra_context={}): extra_context['buttons']=self.buttons return super(ButtonableModelAdmin, self).change_view(request, object_id, extra_context) def button_view_dispatcher(self, request, object_id, command): obj = self.model._default_manager.get(pk=object_id) return getattr(self, command)(request, obj) \ or HttpResponseRedirect(request.META['HTTP_REFERER']) def get_urls(self): from django.conf.urls.defaults import patterns, url from django.utils.functional import update_wrapper def wrap(view): def wrapper(*args, **kwargs): return self.admin_site.admin_view(view)(*args, **kwargs) return update_wrapper(wrapper, view) info = self.model._meta.app_label, self.model._meta.module_name return patterns('', *(url(r'^(\d+)/(%s)/$' % but.func_name, wrap(self.button_view_dispatcher)) for but in self.buttons) ) + super(ButtonableModelAdmin, self).get_urls() </pre>

#

dodgyville (on November 1, 2011):
<p>I found this snippet (and arvid's 1.2 updates) very helpful. However, it does not work in 1.3 due to the the change to "Callables in templates" (see the 1.3 changelist docs). This means the items in buttons are being evaluated in the template for loop, so func_name and short_description are not available since "button" is now automatically "button()".</p> <p>My quick workaround:</p> <p>In FooAdmin: Instead of:<br/> buttons = [bar]<br/> use:<br/> buttons = [(bar.func_name, bar.short_description), ] </p> <p>In arvid's get_urls use but[0] instead of but.func_name </p> <p>And in the template use button.0 and button.1 instead of button.func_name and button.short_description</p>

#

nanvel (on December 5, 2013):
<p>For django v 1.4 (form_url argument was added to change_view):</p> <pre>class ButtonableModelAdmin(admin.ModelAdmin): buttons = [] def change_view(self, request, object_id, extra_context={}): extra_context['buttons'] = self.buttons return super(ButtonableModelAdmin, self).change_view( request=request, object_id=object_id, extra_context=extra_context) def button_view_dispatcher(self, request, object_id, command): obj = self.model._default_manager.get(pk=object_id) return getattr(self, command)(request, obj) \ or HttpResponseRedirect(request.META['HTTP_REFERER']) def get_urls(self): from django.conf.urls.defaults import patterns, url from django.utils.functional import update_wrapper def wrap(view): def wrapper(*args, **kwargs): return self.admin_site.admin_view(view)(*args, **kwargs) return update_wrapper(wrapper, view) info = self.model._meta.app_label, self.model._meta.module_name return patterns('', *(url(r'^(\d+)/(%s)/$' % but[0], wrap(self.button_view_dispatcher)) for but in self.buttons) ) + super(ButtonableModelAdmin, self).get_urls() </pre>

#

Please login first before commenting.