Create custom buttons in admin change_form in Django

Question:

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

Asked By: bnabilos

||

Answers:

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.

Answered By: Andy Baker

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.

Answered By: Josh

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

Answered By: Karim N Gorjux

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

Answered By: Kai – Kazuya Ito