Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: How to add a custom button to admin change form page that executes an admin action?

I have already defined a custom admin action for my model which works perfectly as expected. I also looked at multiple ways of adding a button to the admin change form page here on SO. The only step that I am missing is how do I make a button in the change form page execute my custom admin action with current object?

The goal is to allow admin to inspect every object individually and perform an action on them without needing to go back to list view, selecting the inspected object, and executing the action from the list.

My custom admin action looks like this:

def admin_apply_change(modeladmin, request, queryset):     # loop over objects in query set and perform action 

I am assuming there is a simple and clean way of calling this action in admin change form, where the queryset would only contain the currently opened object the admin is looking at.

NOTE: It would be preferable if the button is at the bottom of the change form, next to Save button instead of being at top with History which is not very visible.

Solution

See the answer below by Remi for the solution. In order to make it work the following corrections are needed:

  1. In the override of response_change initialization of some variables is missing:

    opts = self.model._meta pk_value = obj._get_pk_val() preserved_filters = self.get_preserved_filters(request) 
  2. New inclusion tag custom_submit_row should be placed in templatetags and not in admin (see docs for custom templatetags)

  3. This is the oversight you could lose some time on. In change_form.html you not only have to change the suggested line:

    {% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %} 

    but also the more important line at the bottom where submit_row appears:

    {% block submit_buttons_bottom %}{% submit_row %}{% endblock %} 

    It is located just above the javascript block in change_form.html.

like image 897
dsalaj Avatar asked Jan 20 '16 10:01

dsalaj


People also ask

How do I add a button to a Django project?

First, install it: pip install django-object-actions . (Also add django-object-actions to your requirements file if you have one). Second, add django_object_actions to your INSTALLED_APPS . You should now see an Imports button in the admin, and when pressed, the imports function defined in ImportAdmin will be called.

What is admin Modeladmin in Django?

One of the most powerful parts of Django is the automatic admin interface. It reads metadata from your models to provide a quick, model-centric interface where trusted users can manage content on your site. The admin's recommended use is limited to an organization's internal management tool.


2 Answers

You could take a look at the change_form_template and set it to a custom template of yours and override the response_change method:

class MyModelAdmin(admin.ModelAdmin):      # A template for a customized change view:     change_form_template = 'path/to/your/custom_change_form.html'      def response_change(self, request, obj):         opts = self.model._meta         pk_value = obj._get_pk_val()         preserved_filters = self.get_preserved_filters(request)          if "_customaction" in request.POST:             # handle the action on your obj             redirect_url = reverse('admin:%s_%s_change' %                                (opts.app_label, opts.model_name),                                args=(pk_value,),                                current_app=self.admin_site.name)              redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)              return HttpResponseRedirect(redirect_url)         else:              return super(MyModelAdmin, self).response_change(request, obj) 

Copy the change_form.html from your site-packages/django/contrib/admin/templates/change_form.html and edit the line 40

 {% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %} 

to

 {% if save_on_top %}{% block submit_buttons_top %}{% custom_submit_row %}{% endblock %}{% endif %} 

Also check the line:

 {% block submit_buttons_bottom %}{% submit_row %}{% endblock %} 

just above the javascript block.

Then you can register a new inclusion tag somewhere in your admin.py or add it to templatetags:

@register.inclusion_tag('path/to/your/custom_submit_line.html', takes_context=True) def custom_submit_row(context):     """     Displays the row of buttons for delete and save.     """     opts = context['opts']     change = context['change']     is_popup = context['is_popup']     save_as = context['save_as']     ctx = {         'opts': opts,         'show_delete_link': (             not is_popup and context['has_delete_permission'] and             change and context.get('show_delete', True)         ),         'show_save_as_new': not is_popup and change and save_as,         'show_save_and_add_another': (             context['has_add_permission'] and not is_popup and             (not save_as or context['add'])         ),         'show_save_and_continue': not is_popup and context['has_change_permission'],         'is_popup': is_popup,         'show_save': True,         'preserved_filters': context.get('preserved_filters'),     }     if context.get('original') is not None:         ctx['original'] = context['original']     return ctx 

The contents of your custom_submit_line.html:

{% load i18n admin_urls %} <div class="submit-row"> {% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %} {% if show_delete_link %}     {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}     <p class="deletelink-box"><a href="{% add_preserved_filters delete_url %}" class="deletelink">{% trans "Delete" %}</a></p> {% endif %} {% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{% endif %} {% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %} {% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}  <input type="submit" value="{% trans 'Custom Action' %}"  name="_customaction" />  </div> 

It is a lot of code, but mostly copy/paste. Hope that helps.

like image 84
Remi Smirra Avatar answered Oct 05 '22 13:10

Remi Smirra


Most people probably do this without thinking, though it wasn't clear from the answer that the admin change form should be simply extended rather than overwritten entirely.

custom_change_form.html

{% extends "admin/change_form.html" %}  {% if save_on_top %}{% block submit_buttons_top %}{% custom_submit_row %}{% endblock %}{% endif %}  {% block submit_buttons_bottom %}{% custom_submit_row %}{% endblock %} 
like image 38
Andrew Bird Avatar answered Oct 05 '22 13:10

Andrew Bird