20

I want to add custom buttons to the add/change form at the administration interface. By default, there are only three:

  • Save and add another

  • Save and continue editing

  • Save

I have created some custom methods in my forms.py file, and I want to create buttons to call these methods. I have used the snippet http://djangosnippets.org/snippets/1842/, but it's not exactly what I want. This one allows to create buttons and call methods from the admin.py file and not forms.py.

Is there a way to do that?

This is my admin.py code:

class CategoryAdmin(admin.ModelAdmin):
    prepopulated_fields = { "alias": ("title",) }
    form = CategoryForm

admin.site.register(Category, CategoryAdmin)

And my forms.py code:

class CategoryForm(forms.ModelForm):
    """
    My attributes
    """
    def custom_method(self):
        print("Hello, World!")

How do I create a button that calls "custom_method()"?

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
bnabilos
  • 2,234
  • 4
  • 23
  • 30

4 Answers4

38

One simple way I found to add buttons is to add another row for the custom buttons. Create an admin directory in your template dir based on your needs. For example I usually add buttons for specific models in a custom template. Make a "templates/admin/app/model/" directory.

Then add a file change_form.html.

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

{% block submit_buttons_bottom %}
    <div class="submit-row">
       <input type="button" value="{% trans 'Another Button' %}" name="_anotherbutton" />
    </div>

    {{ block.super }}
{% endblock %}

The code before the {{ block.super }} is inspired by the submit_line.html template used by the template tag {% submit_row %}. I prefer this method because is straightforward but you must live with another row of buttons.

enter image description here

vmonteco
  • 14,136
  • 15
  • 55
  • 86
Karim N Gorjux
  • 2,880
  • 22
  • 29
  • 5
    This is the best approach if you only want to alter the admin for one model (currently you can't override submit_line.html for this purpose). If you want the button to call a model method, you can override response_change in the model admin. – Rick Westera Sep 29 '15 at 22:32
  • Two non-trivial clarifications to make this work for me: (1) I needed to use `type="submit"` instead of `type="button"` in order for this do anything when clicked. (2) Using an example of this [change_form.html](https://github.com/CAVaccineInventory/vial/blob/8ae0065d244600303da241649008c81335574ba3/vaccinate/templates/admin/change_report.html#L23) this modification of [admin.py](https://github.com/CAVaccineInventory/vial/blob/de967bab804bf700dfb139ebcb9caeb9a5269211/vaccinate/core/admin.py#L1387-L1398) allows you to do something with the model when clicking the button. – craastad Nov 12 '21 at 00:50
19

You can override admin/change_form.html. Copy the version in contrib.admin.templates into your project. Mine is myproject/templates/admin/change_form.html, but you could use /myproject/myapp/templates/admin/change_form.html.

Next, edit the copy and change the two references to the existing template tag, {% submit_row %}, to point to your own template tag, {% my_template_tag %}.

Base your template tag on the contrib.admin's {% submit_row %}, but edit the HTML template to contain any extra buttons you want to display.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andy Baker
  • 21,158
  • 12
  • 58
  • 71
  • I would recommend against this method as you're duplicating a lot of code. It can be done in a much DRYer way. – Josh Sep 10 '13 at 00:18
  • Exactly right, if you need any custom context variables in your new templates. However, it looks like _soon_ we will only have to override `admin/submit_line.html`: https://code.djangoproject.com/ticket/13875 – frnhr Apr 10 '16 at 00:09
7

The submit buttons in a change form are rendered by the submit_row template tag. This tag renders the template admin/submit_line.html. Since you want to add to the existing buttons, your best (and DRYest) approach is to override admin/submit_line.html.

For example, create a file my_project/templates/admin/submit_line.html with the following content:

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

<input type="submit" value="{% trans 'New button 1' %}" name="_button1" {{ onclick_attrib }}/>
<input type="submit" value="{% trans 'New button 2' %}" name="_button2" {{ onclick_attrib }}/>
</div>

Most of what's above was copied from django/contrib/admin/templates/submit_line.html. You can also add additional if statements in the template if you only want to show those additional buttons in certain cases.

Josh
  • 12,896
  • 4
  • 48
  • 49
  • I am trying to use this technique to have just a single custom button, but it appears my submit_line.html is not being used. Is there anything I have to do other then copy and modify that file? How can I debug why it's not getting picked up? I am using django 1.5 – Larry Martell Apr 04 '15 at 20:15
  • I want this behaviour for just one app, so put the file in my_app/templates/admin/. It appears you cannot override this on a per app basis. It works if I put it in my_project/templates/admin but it's effecting every model in admin. Is there a way to test for what model it's being called for? – Larry Martell Apr 04 '15 at 20:30
  • The only way I can think of is to somehow get the app name in the context while rendering the template. You'd need to override the template tag for that. – Josh Apr 06 '15 at 15:27
  • I posted another questions about this and someone gave me a way to override submit_line.html for a single model (http://stackoverflow.com/questions/29452826/overriding-submit-line-html-for-a-single-model-or-app). That works, but I am still having an issue. My override has one button that invokes some custom code that does not add the row to the database. When my code is done, something runs that add the row. I don't want that to happen. – Larry Martell Apr 06 '15 at 15:31
0

You can add a custom button at the bottom of "Add" form and "Change" form for a specifc admin.

First, in the root django project directory, create "templates/admin/custom_change_form.html" as shown below:

enter image description here

Next, under "django" library, there is "change_form.html" which is "django/contrib/admin/templates/admin/change_form.html" so copy & paste all the code of "change_form.html" to "custom_change_form.html" as shown below:

# "templates/admin/custom_change_form.html"

{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_modify %}

{% block extrahead %}{{ block.super }}
<script src="{% url 'admin:jsi18n' %}"></script>
{{ media }}
{% endblock %}

... Much more code below

Next, there is the code in line 64 on "custom_change_form" as shown below:

# "templates/admin/custom_change_form.html"

{% block submit_buttons_bottom %}{% submit_row %}{% endblock %} # Line 64

Then, add the code below between "{% submit_row %}" and "{% endblock %}":

{% if custom_button %}
<div class="submit-row">
    <input type="submit" value="{% translate 'Custom button' %}" name="_custom_button">
</div>
{% endif %}

So, this is the full code as shown below:

# "templates/admin/custom_change_form.html"

{% block submit_buttons_bottom %} # Line 64
{% submit_row %}
{% if custom_button %}
<div class="submit-row">
    <input type="submit" value="{% translate 'Custom button' %}" name="_custom_button">
</div>
{% endif %}
{% endblock %}

Next, this is the settings for templates in "settings.py":

# "settings.py"

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Then, add "os.path.join(BASE_DIR, 'templates')" to "DIRS" as shown below:

# "settings.py"

import os # Here

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], # Here
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Now, this is "Person" model as shown below:

# "models.py"

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

Then, this is "Person" admin as shown below:

# "admin.py"

from django.contrib import admin
from .models import Person

@admin.register(Person) # Here
class PersonAdmin(admin.ModelAdmin):
    pass

So next, for "Person" admin, set "admin/custom_change_form.html" to "change_form_template", set "True" to "extra_context['custom_button']" in "changeform_view()" and set "response_add()" and "response_change()" to define the action after pressing "Custom button" on "Add" form and "Change" form respectively as shown below. *Whether or not setting "response_add()" and "response_change()", inputted data to fields is saved after pressing "Custom button" on "Add" form and "Change" form respectively:

# "admin.py"

from django.contrib import admin
from .models import Person

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    change_form_template = "admin/custom_change_form.html" # Here
    
    def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
        extra_context = extra_context or {}
        
        extra_context['custom_button'] = True # Here
        
        return super().changeform_view(request, object_id, form_url, extra_context)

    def response_add(self, request, obj, post_url_continue=None): # Here

        if "_custom_button" in request.POST:
            # Do something
            return super().response_add(request, obj, post_url_continue)
        else:
            # Do something
            return super().response_add(request, obj, post_url_continue)

    def response_change(self, request, obj): # Here
        
        if "_custom_button" in request.POST:
            # Do something
            return super().response_change(request, obj)
        else:
            # Do something
            return super().response_change(request, obj)

Finally, "Custom button" is added at the bottom of "Add" form and "Change" form for "Person" admin as shown below:

enter image description here

enter image description here

In addition, for "Person" admin, you can replace "changeform_view()" with "render_change_form()" set "context.update({"custom_button": True})" as shown below:

# "admin.py"

from django.contrib import admin
from .models import Person

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    change_form_template = "admin/custom_change_form.html"
    
    def render_change_form(self, request, context, add=False, change=False, form_url="", obj=None):        
        
        context.update({"custom_button": True}) # Here

        return super().render_change_form(request, context, add, change, form_url, obj)

    def response_add(self, request, obj, post_url_continue=None):

        if "_custom_button" in request.POST:
            # Do something
            return super().response_add(request, obj, post_url_continue)
        else:
            # Do something
            return super().response_add(request, obj, post_url_continue)

    def response_change(self, request, obj):
        
        if "_custom_button" in request.POST:
            # Do something
            return super().response_change(request, obj)
        else:
            # Do something
            return super().response_change(request, obj)

Then, "Custom button" is added at the bottom of "Add" form and "Change" form for "Person" admin as well as shown below:

enter image description here

enter image description here

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129