How to add a custom button to Django admin
Question:
I want to add a custom button right next to the SAVE button in my admin change form. I tried to add a button in submit_line.html
<input type="submit" action = "admin/{{id??}}" value="show PDF" name="show PDF{{ onclick_attrib }}/>
But, it doesn’t redirect to my given page and I don’t know how to pass the current id.
Actually, I dont think it’s a good idea to change submit_line.html anyways, because I only want it in one model. Is there a better solution for that?
Answers:
I think you can add your own logic. Here you check app verbose name, to show that button only in your app:
{% if opts.verbose_name == 'Your app verbose name' %}<input type="submit" value="{% trans 'Show PDF' %}" name="_show_pdf" {{ onclick_attrib }}/>{% endif %}
and in your admin.py:
class YourAdmin(admin.ModelAdmin):
def response_change(self, request, obj):
if '_continue' in request.POST:
return HttpResponseRedirect(obj.get_admin_url())
elif '_show_pdf' in request.POST:
# Do some stuf here( show pdf in your case)
Django1.10:
I would do it as follows, to avoid the verbosity and extra template processing of the accepted answer
1) Extend admin/change_form.html
in your app’s template directory (you are namespacing your app’s templates, I hope) and override block submit_row
:
{% extends "admin/change_form.html" %}
{% block submit_row %}
<div class="submit-row">
{% if extra_buttons %}
{% for button in extra_buttons %}
{{ button }}
{% endfor %}
{% endif %}
{% 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 %}
</div>
{% endblock %}
This assumes, of course, that button
‘s string representation is an appropriate browser input
or button
element, and is marked safe with django.utils.safestring.mark_safe
. Alternatively, you could use the safe
template filter or access the attributes of button
directly to construct the <input>
. In my opinion, it’s better to isolate such things to the python level.
2) Override MyModelAdmin.change_view
and MyModelAdmin.change_form_template
:
change_form_template = "my_app/admin/change_form.html"
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or self.extra_context()
return super(PollAdmin, self).change_view(
request, object_id, form_url, extra_context=extra_context,
)
In my case I only needed for users to do a custom action only after they saved, like “Add another”, other buttons such as “export to pdf” I put in the change list as there it’s super easy to add buttons.
If you are here for a case like mine the built-in messages framework will do the trick.
adding a response message is simple and you will need to override the
admin class save_model
, goes like this:
from django.utils.safestring import mark_safe
def save_model(self, request, obj, form, change):
# if new model
if not change:
message_html = "<a href='http://yourSITE/admin/appname/appmodel/add/'>Add another</a>"
messages.add_message(request, messages.INFO, mark_safe(message_html))
super(YourAdminClass, self).save_model(request, obj, form, change)
there are 4 different types of messages (I changed the css of INFO) and they are displayed in the screenshot
You can add a custom button right next to "SAVE" button on "Add" form and "Change" form for a specifc admin.
First, in the root django project directory, create "templates/admin/custom_change_form.html" and "templates/admin/submit_line.html" as shown below:
Next, copy & paste all the code of "change_form.html" under django library whose path is "django/contrib/admin/templates/admin/change_form.html" to "templates/admin/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, copy & paste all the code of "submit_line.html" under django library whose path is "django/contrib/admin/templates/admin/submit_line.html" to "templates/admin/submit_line.html" as shown below:
# "templates/admin/submit_line.html"
{% load i18n admin_urls %}
<div class="submit-row">
{% block submit-row %}
{% if show_save %}<input type="submit" value="{% translate 'Save' %}" class="default" name="_save">{% endif %}
... Much more code below
Next, add the code below between "{% block submit-row %}" and "{% if show_save %}" on "templates/admin/submit_line.html". *style="float:right;margin-left:8px;" is important for this code below to put a custom button right next to "SAVE" button:
{% if custom_button %}
<input type="submit" style="float:right;margin-left:8px;" value="{% translate 'Custom button' %}" name="_custom_button">
{% endif %}
So, this is the full code as shown below:
# "templates/admin/submit_line.html"
{% load i18n admin_urls %}
<div class="submit-row">
{% block submit-row %}
{% if custom_button %}
<input type="submit" style="float:right;margin-left:8px;" value="{% translate 'Custom button' %}" name="_custom_button">
{% endif %}
{% if show_save %}<input type="submit" value="{% translate 'Save' %}" class="default" name="_save">{% endif %}
... Much more code below
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:
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:
I want to add a custom button right next to the SAVE button in my admin change form. I tried to add a button in submit_line.html
<input type="submit" action = "admin/{{id??}}" value="show PDF" name="show PDF{{ onclick_attrib }}/>
But, it doesn’t redirect to my given page and I don’t know how to pass the current id.
Actually, I dont think it’s a good idea to change submit_line.html anyways, because I only want it in one model. Is there a better solution for that?
I think you can add your own logic. Here you check app verbose name, to show that button only in your app:
{% if opts.verbose_name == 'Your app verbose name' %}<input type="submit" value="{% trans 'Show PDF' %}" name="_show_pdf" {{ onclick_attrib }}/>{% endif %}
and in your admin.py:
class YourAdmin(admin.ModelAdmin):
def response_change(self, request, obj):
if '_continue' in request.POST:
return HttpResponseRedirect(obj.get_admin_url())
elif '_show_pdf' in request.POST:
# Do some stuf here( show pdf in your case)
Django1.10:
I would do it as follows, to avoid the verbosity and extra template processing of the accepted answer
1) Extend admin/change_form.html
in your app’s template directory (you are namespacing your app’s templates, I hope) and override block submit_row
:
{% extends "admin/change_form.html" %}
{% block submit_row %}
<div class="submit-row">
{% if extra_buttons %}
{% for button in extra_buttons %}
{{ button }}
{% endfor %}
{% endif %}
{% 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 %}
</div>
{% endblock %}
This assumes, of course, that button
‘s string representation is an appropriate browser input
or button
element, and is marked safe with django.utils.safestring.mark_safe
. Alternatively, you could use the safe
template filter or access the attributes of button
directly to construct the <input>
. In my opinion, it’s better to isolate such things to the python level.
2) Override MyModelAdmin.change_view
and MyModelAdmin.change_form_template
:
change_form_template = "my_app/admin/change_form.html"
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or self.extra_context()
return super(PollAdmin, self).change_view(
request, object_id, form_url, extra_context=extra_context,
)
In my case I only needed for users to do a custom action only after they saved, like “Add another”, other buttons such as “export to pdf” I put in the change list as there it’s super easy to add buttons.
If you are here for a case like mine the built-in messages framework will do the trick.
adding a response message is simple and you will need to override the
admin class save_model
, goes like this:
from django.utils.safestring import mark_safe
def save_model(self, request, obj, form, change):
# if new model
if not change:
message_html = "<a href='http://yourSITE/admin/appname/appmodel/add/'>Add another</a>"
messages.add_message(request, messages.INFO, mark_safe(message_html))
super(YourAdminClass, self).save_model(request, obj, form, change)
there are 4 different types of messages (I changed the css of INFO) and they are displayed in the screenshot
You can add a custom button right next to "SAVE" button on "Add" form and "Change" form for a specifc admin.
First, in the root django project directory, create "templates/admin/custom_change_form.html" and "templates/admin/submit_line.html" as shown below:
Next, copy & paste all the code of "change_form.html" under django library whose path is "django/contrib/admin/templates/admin/change_form.html" to "templates/admin/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, copy & paste all the code of "submit_line.html" under django library whose path is "django/contrib/admin/templates/admin/submit_line.html" to "templates/admin/submit_line.html" as shown below:
# "templates/admin/submit_line.html"
{% load i18n admin_urls %}
<div class="submit-row">
{% block submit-row %}
{% if show_save %}<input type="submit" value="{% translate 'Save' %}" class="default" name="_save">{% endif %}
... Much more code below
Next, add the code below between "{% block submit-row %}" and "{% if show_save %}" on "templates/admin/submit_line.html". *style="float:right;margin-left:8px;" is important for this code below to put a custom button right next to "SAVE" button:
{% if custom_button %}
<input type="submit" style="float:right;margin-left:8px;" value="{% translate 'Custom button' %}" name="_custom_button">
{% endif %}
So, this is the full code as shown below:
# "templates/admin/submit_line.html"
{% load i18n admin_urls %}
<div class="submit-row">
{% block submit-row %}
{% if custom_button %}
<input type="submit" style="float:right;margin-left:8px;" value="{% translate 'Custom button' %}" name="_custom_button">
{% endif %}
{% if show_save %}<input type="submit" value="{% translate 'Save' %}" class="default" name="_save">{% endif %}
... Much more code below
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:
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: