CHANGE TEMPLATES=============================================================== ------------------------********************----------------------------------- In a folder referenced in settings.TEMPLATE_DIRS create two files : ------------------------********************----------------------------------- ******************************************************************************* /admin//change_form.html ******************************************************************************* {%extends "admin/change_form.html" %} {% load i18n admin_modify adminmedia %} {% block object-tools %} {% if change %} {% if not is_popup %} {% endif %} {% endif %} {% endblock %} {% block after_related_objects %}
{% for button in buttons %}{% if not button.forAll %}{% if button.show %} {% if button.saveOnClick %} {{button}} {% endif %}{% endif %} {% endif %}{% endfor %}
{% endblock %} ******************************************************************************* /admin//change_list.html ******************************************************************************* {%extends "admin/change_list.html" %} {% load i18n %} {% block object-tools %} {% if has_add_permission %} {% 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'%s' % self.desc else: link = u'' % (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 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_ 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.