Cannot hide "Save and add another" button in Django Admin

Question:

I would like to hide all the "Save" buttons in Django’s Admin’s Change Form, for a specific model, when certain conditions are met. Therefore, I override the changeform_view method in the relevant ModelAdmin, like so:

def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
    extra_context = extra_context or {}
    obj = collection_management_MammalianLine.objects.get(pk=object_id)
    if obj:
        if not (request.user.is_superuser or request.user.groups.filter(name='Lab manager').exists() or request.user == obj.created_by):
            extra_context['show_save'] = False
            extra_context['show_save_and_continue'] = False
            extra_context['show_save_and_add_another'] = False
        else:
            pass
    else:
        pass
    return super(MammalianLinePage, self).changeform_view(request, object_id, extra_context=extra_context)

With this code, I can successfully hide the "Save" and "Save and continue" buttons, but not the "Save and add another" one. I can see that submit_line.html contains the following three lines:

{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% 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 %}

My question is: why can I hide the "Save" and "Save and continue" buttons, but not the "Save and add another" one? Even though the relevant templatetag (show_save_and_continue) is in the template.

Asked By: Nicola Zilio

||

Answers:

The other keys are checked for in the passed context except show_save_and_continue. Django always sets this directly.

'show_save_and_add_another': (
        context['has_add_permission'] and not is_popup and
        (not save_as or context['add'])
    ),

You can patch the submit_row template tag function to first check the context dictionary for show_save_and_add_another.

@register.inclusion_tag('admin/submit_line.html', takes_context=True)
def submit_row(context):
    """
    Display the row of buttons for delete and save.
    """
    change = context['change']
    is_popup = context['is_popup']
    save_as = context['save_as']
    show_save = context.get('show_save', True)
    show_save_and_continue = context.get('show_save_and_continue', True)
    show_save_and_add_another = context.get('show_save_and_add_another', False)
    ctx = Context(context)
    ctx.update({
        '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.get('show_save_and_add_another', None) or
            (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'] and show_save_and_continue,
        'show_save': show_save,
    })
    return ctx

Edit

Steps to patch the “admin/submit_line.html” inclusion tag

  1. Create a templatetags folder at the same level of models.py and views.py

  2. Create __init__.py in the templatetags folder

  3. Copy django/contrib/admin/templatetags/admin_modify.py to templatetags/admin_modify.py.

  4. Overwrite submit_row function definition with the one above.

Note that this is applicable for Django 2.0 and below.

For recent Django versions, find a context mix that allows this expression to be False.e.g.

has_add_permission and not is_popup and
(not save_as or add) and can_save

See values for the names used in the above expression.

Answered By: Oluwafemi Sule

I have an alternative way to hide the “Save and add another” button.

In form.py

class TestForm(forms.ModelForm):
    class Media:
          js = ('admin/yourjsfile.js',)

In yourjsfile.js

(function($) {
'use strict';
$(document).ready(function() {
    // hide the "Save and add another" button
    $("input[name='_addanother']").attr('type','hidden');
});
})(django.jQuery);

I had same issue for hiding that button.
I’ve tested it in Django 1.11 and it did work, but I think there are better ways to achieve it.

Answered By: FLYtheSKY

EDIT:

As of Django 3.1, the approach from the original question should simply work:

...
extra_context['show_save_and_add_another'] = False
...

See this Django commit for details.

OLD ANSWER:

summary

Some additional options:

  1. Set save_as=True on your ModelAdmin. As described in the docs, this will replace the "Save and add another" button with a "Save as new" button. This may not be ideal for all cases, but it is the simplest solution as far as I know.

  2. Apply a monkey patch for the has_add_permission method on your ModelAdmin, so it returns False during the call to super().change_view (or changeform_view).

  3. Override the TemplateResponse.content (docs) to simply hide (or remove) the submit-row element, which contains the buttons, from the HTML.

details option 1

The simplest option is to set save_as=True on the ModelAdmin. This will replace the "Save and add another" button with a "Save as new" button. As a result, assuming the other save buttons have been disabled, any changes made to the current object can only be saved as a new object. The current object will remain unchanged.

Basic implementation:

class MyModelAdmin(ModelAdmin):
    save_as = True

    # your other code here, such as the extended changeform_view 

details option 2

The submit_line.html template shows which context variables are used to show/hide the save and delete buttons. Most of those context variables can be set via the extra_context in changeform_view (or change_view). However, as the OP showed, we cannot simply override show_save_and_add_another in that manner.

As pointed out in @Oluwafemi-Sule‘s answer,
show_save_and_add_another is set in admin_modify.py, which creates the context for submit_line.html.

Upon closer inspection of the source, it is tempting to override the has_add_permission context variable, because that determines the value of show_save_and_add_another. However, simply adding has_add_permission=False to extra_context does not work in Django < 2.1, because the change will be undone by the ModelAdmin.render_change_form method.

Fortunately, rather than overriding the template or patching the template tags, we can simply trick Django into thinking that has_add_permission is False, using a monkey patch in change_view:

def change_view(self, request, object_id=None, form_url='', extra_context=None):
    # use extra_context to disable the other save (and/or delete) buttons
    extra_context = dict(show_save=False, show_save_and_continue=False, show_delete=False)
    # get a reference to the original has_add_permission method
    has_add_permission = self.has_add_permission
    # monkey patch: temporarily override has_add_permission so it returns False
    self.has_add_permission = lambda __: False
    # get the TemplateResponse from super (python 3)
    template_response = super().change_view(request, object_id, form_url, extra_context)
    # restore the original has_add_permission (otherwise we cannot add anymore)
    self.has_add_permission = has_add_permission
    # return the result
    return template_response

This also works if you replace change_view by changeform_view, as used by the OP (source).

Note: obviously, this option can only be used if has_add_permission=False does not interfere with other things in your change view.

details option 3

Based on this example from the docs, it is also possible simply to hide (or even remove) the submit-row from the rendered HTML for the change_view:

def change_view(self, request, object_id=None, form_url='',
                extra_context=None):
    # get the default template response
    template_response = super().change_view(request, object_id, form_url,
                                            extra_context)
    # here we simply hide the div that contains the save and delete buttons
    template_response.content = template_response.rendered_content.replace(
        '<div class="submit-row">',
        '<div class="submit-row" style="display: none">')
    return template_response

As noted, we can also remove the submit-row section altogether, but that is a bit more work.

This is simpler than option 2, but more fragile.

Answered By: djvg

You may override render_change_form method in ModelAdmin subclass.
In this method obj is available as argument and you can check certain conditions.

class OrderAdmin(admin.ModelAdmin):

    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
        context.update({
            'show_save': False,
            'show_save_and_continue': False,
            'show_save_and_add_another': False,
            'show_delete': False
        })
        return super().render_change_form(request, context, add, change, form_url, obj)
Answered By: un1t

I propose (changed option 3 of djvg’s answer) removing this html input element with regex as with this example:

class MyModelAdmin(admin.ModelAdmin):

    def add_view(self, request, form_url='', extra_context=None):
        template_response = super(MyModelAdmin, self).add_view(
            request, form_url=form_url, extra_context=extra_context)
        # POST request won't have html response
        if request.method == 'GET':
            # removing Save and add another button: with regex
            template_response.content = re.sub("<input.*?_addanother.*?(/>|>)", "", template_response.rendered_content)
        return template_response

_addanother is this html element’s id

Answered By: Jerzy Kiler

If no one is against, then I publish my little monkey patch.
This can be inserted anywhere and called up from the settings file (preferably at the very end).

# Hide "save_and_add_another" button
from django.contrib.admin.templatetags import admin_modify

submit_row = admin_modify.submit_row
def submit_row_custom(context):
    ctx = submit_row(context)
    ctx['show_save_and_add_another'] = False
    return ctx
admin_modify.submit_row = submit_row_custom
Answered By: Vyaches

To remove "Save and add another" button, return "False" in "has_add_permission()" as shown below and I don’t know why we cannot remove "Save and add another" button by setting "False" to "extra_context[‘show_save_and_another’]" in "changeform_view()":

# "admin.py"

from django.contrib import admin
from .models import MyModel

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
    def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
        extra_context = extra_context or {}

        extra_context['show_save'] = False
        extra_context['show_save_and_continue'] = False

        return super().changeform_view(request, object_id, form_url, extra_context)

    def has_add_permission(self, request, obj=None): # Here
        return False
Answered By: Kai – Kazuya Ito