Login

Button Admin

Author:
delfick
Posted:
December 28, 2009
Language:
Python
Version:
1.1
Score:
2 (after 2 ratings)

This is an improvement on other ButtonAdmin implementations.

A few features : It allows you to specify whether button appears on change form or change list; whether the form is to be saved before the resulting function is called; whether the button should be displayed

Look at the bottom of the snippet for usage.

And make sure you admin urls are enabled using (r'^admin/', include(admin.site.urls)) instead of the old method

  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
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
CHANGE TEMPLATES===============================================================
------------------------********************-----------------------------------
In a folder referenced in settings.TEMPLATE_DIRS create two files :
------------------------********************-----------------------------------

*******************************************************************************
    /admin/<app>/change_form.html
*******************************************************************************

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

{% load i18n admin_modify adminmedia %}

<!-- Object-Tools -->
{% block object-tools %}
{% if change %}
{% if not is_popup %}
<ul class="object-tools">
    {% for button in buttons %}{% if not button.forAll %}{% if button.show %}
        {% if not button.saveOnClick %}<li>{{button}}</li>{% endif %}
    {% endif %}{% endif %}{% endfor %}
    
    <li><a href="history/">{% trans "History" %}</a></li>
    {% if has_absolute_url %}
        <li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="focus" target="_blank">{% trans "View on site" %}</a></li>   
    {% endif%}
</ul>
{% endif %}
{% endif %}
{% endblock %}

{% block after_related_objects %}
    <div class="submit-row">
    {% for button in buttons %}{% if not button.forAll %}{% if button.show %}
    
        {% if button.saveOnClick %}
            {{button}}
        {% endif %}{% endif %}
    
    {% endif %}{% endfor %}
    </div>
{% endblock %}

*******************************************************************************
    /admin/<app>/change_list.html
*******************************************************************************

{%extends "admin/change_list.html" %}
{% load i18n %}

{% block object-tools %}
  {% if has_add_permission %}
    <ul class="object-tools">
        {% for button in buttons %}{% if button.show %}
            {% if button.forAll %}<li>{{button}}</li>{% endif %}
        {% endif %}{% endfor %}
      <li>
        <a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">
          {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
        </a>
      </li>
    </ul>
  {% endif %}
{% endblock %}
*******************************************************************************

BUTTON ADMIN CLASSES===========================================================
------------------------********************-----------------------------------
Create a file somewhere that you can import into your admin.py 
and put the following in it
------------------------********************-----------------------------------


*******************************************************************************
from django.template import loader, RequestContext, Context, Template
from django.http import HttpResponse, HttpResponseRedirect
from django.utils.translation import ugettext as _
from django.utils.functional import update_wrapper
from django.utils.encoding import force_unicode
from django.shortcuts import get_object_or_404
from django.utils.safestring import mark_safe
from django.contrib.admin.util import unquote
from django.contrib.admin import helpers
from django.utils.text import capfirst
from django.utils.html import escape
from django.contrib import admin
from django.http import QueryDict

class Button(object):
    def __init__(self, url, desc, kls=None, saveOnClick=True, forAll=False, display=True, needSuperUser=True):
        if forAll:
            saveOnClick = False
        self.url = url
        self.desc = desc
        self.kls = kls
        self.display = display
        self.saveOnClick = saveOnClick
        self.forAll = forAll
        self.needSuperUser = needSuperUser
            
    def __unicode__(self):
            
        if not self.saveOnClick or self.forAll:
            if self.saveOnClick or not self.url.startswith('/'):
                url = "tool_%s" % self.url
            else:
                url = self.url
        
            link = u'<a href="%s"' % url
            if self.kls:
                link += u' class="%s"' % self.kls
            link += u'>%s</a>' % self.desc
        else:
            link = u'<input type="submit" name="tool_%s" value="%s"/>' % (self.url, self.desc)
        return mark_safe(link)

    def determineIfShow(self, user):
        if (self.needSuperUser and not user.is_superuser) or not self.display:
            self.show = False
        else:
            self.show = True
    
class ButtonAdminMixin(object):
    def tool_urls(self):
        """Mostly copied from django.contrib.admin.ModelAdmin.get_urls"""
        
        from django.conf.urls.defaults import patterns, url

        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
        
        urls = []
        for button in self.buttons:
            urls.append(
                url(r'^(.+)/tool_%s/$' % button.url,
                    wrap(self.button_url),
                    kwargs = dict(button=button),
                    name = '%s_%s_tool_%%s' % info % button.url,
                )
            )
            
        urlpatterns = patterns('', *urls)
        return urlpatterns

    
    def button_url(self, request, object_id, button):
    
        model = self.model
        obj = get_object_or_404(model, pk=object_id)
        result = getattr(self, "tool_%s" % button.url)(request, obj, button)
        
        try:
            File, extra = result
        except:
            return result
        
        opts = model._meta
        app_label = opts.app_label
        context = {
            'title': _('%s: %s') % (button.desc, force_unicode(obj)),
            'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
            'object': obj,
            'root_path': self.admin_site.root_path,
            'app_label': app_label,
            'bread_title' : button.desc,
        }
        context.update(extra or {})
        
        t = loader.get_template(File)
        c = RequestContext(request, context)
        return HttpResponse(t.render(c))

class ButtonAdmin(admin.ModelAdmin, ButtonAdminMixin):
    """Unfortunately I can't add these to the mixin, but I still want to have the mixin stuff as a mixin"""
    def changelist_view(self, request, extra_context=None):
        if not extra_context:
            extra_context = {}
        
        [b.determineIfShow(request.user) for b in self.buttons]
        extra_context['buttons']=self.buttons
        
        return super(ButtonAdmin, self).changelist_view(request, extra_context)
        
    def change_view(self, request, object_id, extra_context=None):
        if not extra_context:
            extra_context = {}
        
        [b.determineIfShow(request.user) for b in self.buttons]
        extra_context['buttons']=self.buttons
        
        result = super(ButtonAdmin, self).change_view(request, object_id, extra_context)
        
        redirect = None
        for key in request.POST.keys():
            if key.startswith("tool_"):
                redirect = key
                
        if not redirect:
            return result
        else:
            #a lot of this is copied from django.contrib.admin.ModelAdmin.change_view
            model = self.model
            opts = model._meta

            try:
                obj = self.queryset(request).get(pk=unquote(object_id))
            except model.DoesNotExist:
                # Don't raise Http404 just yet, because we haven't checked
                # permissions yet. We don't want an unauthenticated user to be able
                # to determine whether a given object exists.
                obj = None

            if not self.has_change_permission(request, obj):
                raise PermissionDenied

            if obj is None:
                raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})

            if request.method == 'POST' and request.POST.has_key("_saveasnew"):
                return self.add_view(request, form_url='../add/')

            ModelForm = self.get_form(request, obj)
            formsets = []
            if request.method == 'POST':
                form = ModelForm(request.POST, request.FILES, instance=obj)
                if form.is_valid():
                    form_validated = True
                    new_object = self.save_form(request, form, change=True)
                else:
                    form_validated = False
                    new_object = obj
                prefixes = {}
                for FormSet in self.get_formsets(request, new_object):
                    prefix = FormSet.get_default_prefix()
                    prefixes[prefix] = prefixes.get(prefix, 0) + 1
                    if prefixes[prefix] != 1:
                        prefix = "%s-%s" % (prefix, prefixes[prefix])
                    
                    try:
                        formset = FormSet(request.POST, request.FILES,
                                      instance=None, prefix=prefix)
                        formsets.append(formset)
                    except:
                        #Deleted items can cause this to fail.
                        pass
                    
                errors = helpers.AdminErrorList(form, formsets)
                if not errors:
                    return HttpResponseRedirect(redirect)
                            
        return result
        
    def urls(self):
        if hasattr(self, 'buttons'):
            return self.tool_urls() + self.get_urls()
        else:
            return self.get_urls()
    urls = property(urls)
*******************************************************************************

USAGE==========================================================================
------------------------********************-----------------------------------
In your desired admin model there's three parts to enabling this
------------------------********************-----------------------------------

1) Make sure your admin model inherits from ButtonAdmin
2) Add a Buttons attribute to your admin
3) Add necessary functions that are called

<import ButtonAdmin, Button>

class Test(ButtonAdmin):
    buttons = (
        Button('test', "A Test Button"),
        Button('redirect', "A redirect"),
    )
    
    def tool_test(self, request, obj, button):
        return 'templates/test.html', {"message" : "a test"}
    
    def tool_redirect(self, request, obj, button):
        return HttpResponseRedirect('/somewhere')
        
Each button has the following parameters : url, desc, kls=None, saveOnClick=True, forAll=False, display=True, needSuperUser=True

* forAll : determines whether the button shows up on the change form or the change list. 
    If forAll is False, it appears on the change form. 
    The function that is called is called tool_<url> and receives :
           the request object, the object being edited and the button object.

    If forAll is True, it appears on the change list. 
    Unfortunately you must create your url and view somewhere else 
    and pass in the aboslute url to this view as the url parameter to the button (limitation of django admin)

* desc : what is displayed on the button

* kls : specifies the css class of the button

* saveOnClick : Determines if the form is saved when the button is clicked or not. 
    If set to True : the form is saved before the function is called 
                     and the button appears at the bottom
    If set to False : the form is not saved before the function is called 
                      and the button appears at the top
    This doesn't apply when forAll is set to True
   
* display : determines if the button should be displayed or not 
(doesn't effect url creation)

* needSuperUser : if set to true then only super users can see the button


The functions can return either a HttpResponse object or a two value tuple of template name, and extra context. 
When the tuple is returned it will render the template with the given context.

    
    

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 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, 6 months ago

Comments

darksky0 (on December 30, 2009):

using it & loving it! kudos

#

Please login first before commenting.