4

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?

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
kiwi541
  • 83
  • 2
  • 6
  • Have a look here: https://docs.djangoproject.com/en/1.7/ref/contrib/admin/#django.contrib.admin.ModelAdmin.change_view – Jingo Nov 10 '14 at 15:01

4 Answers4

4

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)
wolendranh
  • 4,202
  • 1
  • 28
  • 37
  • What happens when you update Djangos Version while using this approach? – Jingo Nov 10 '14 at 16:00
  • @Jingo Not sure if in dev version were some changes in this place,but if logic will be changed in new version you will need to rewrite you app according to latest version. I think this could be related to a lot of code snippets. – wolendranh Nov 10 '14 at 16:12
0

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,
    )
DylanYoung
  • 2,423
  • 27
  • 30
  • 1
    I think someone downvoted because your template suggestion is incorrect. E.g. on Django 1.10.6 **change_form.html** doesn't have `submit_row` block. But I upvoted your answer because you gave me a good idea where to put the code for my custom button - `change_view()` is a right place for that. – Vlad T. May 15 '17 at 15:47
  • Ah; thanks @VladT. I'll look into it. Maybe I got my versions mixed up. – DylanYoung May 15 '17 at 17:27
0

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

enter image description here

elad silver
  • 9,222
  • 4
  • 43
  • 67
0

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:

enter image description here

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:

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