Login

Add buttons in admin forms

Author:
mamat
Posted:
September 3, 2008
Language:
Python
Version:
1.0
Score:
6 (after 6 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

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 1 week ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

donspaulding (on October 17, 2008):

Just what I was looking for! I'm definitely adding this to my toolbelt.

Two things:

  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.

Thanks for this!

#

caesarmv (on May 22, 2010):

Rechecked on django 1.2. It needs additional string here.

from django.utils.functional import update_wrapper

Work code below:

class ButtonableModelAdmin(admin.ModelAdmin):

buttons=[]

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)

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'])

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

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)

   urlpatterns = patterns('',
       url(r'^(.+)/$',
           wrap(self.button_view_dispatcher),)
   ) + super(ButtonableModelAdmin, self).get_urls()
   return urlpatterns

#

arvid (on July 6, 2010):

Last corrections. Tested with Django 1.2.1.

from django.contrib import admin from django.http import HttpResponseRedirect

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, 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()

#

dodgyville (on November 1, 2011):

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()".

My quick workaround:

In FooAdmin: Instead of:<br /> buttons = [bar]<br /> use:<br /> buttons = [(bar.func_name, bar.short_description), ]

In arvid's get_urls use but[0] instead of but.func_name

And in the template use button.0 and button.1 instead of button.func_name and button.short_description

#

nanvel (on December 5, 2013):

For django v 1.4 (form_url argument was added to change_view):

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()

#

kontextify (on February 15, 2018):

Updated for Django 1.8 and incorporated the workarounds above.

Admin class:

class ButtonableModelAdmin(admin.OSMGeoAdmin):
  def bar(self, request, obj):
     print obj
     print request
  bar.short_description='Example button'

  buttons = [ (bar.func_name, bar.short_description) ]

  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 import patterns, url
      from functools 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.model_name

      return patterns('',
          *(url(r'^(\d+)/(%s)/$' % but[0], wrap(self.button_view_dispatcher)) for but in self.buttons)
      ) + super(ButtonableModelAdmin, self).get_urls()

admin/my_app/change_form.html:

<ul class="object-tools">
  {% block object-tools-items %}
  {% for button in buttons %}
      <li><a href="{{ button.0 }}/">{{ button.1 }}</a></li>
  {% endfor %}

#

Please login first before commenting.