Remove "add another" in Django admin screen

Viewed 25431

Whenever I'm editing object A with a foreign key to object B, a plus option "add another" is available next to the choices of object B. How do I remove that option?

I configured a user without rights to add object B. The plus sign is still available, but when I click on it, it says "Permission denied". It's ugly.

I'm using Django 1.0.2

12 Answers

I use the following approaches for Form and InlineForm

Django 2.0, Python 3+

Form

class MyModelAdmin(admin.ModelAdmin):
    #...
    def get_form(self,request, obj=None, **kwargs):

        form = super().get_form(request, obj, **kwargs)
        user = form.base_fields["user"]

        user.widget.can_add_related = False
        user.widget.can_delete_related = False
        user.widget.can_change_related = False

        return form  

Inline Form

class MyModelInline(admin.TabularInline):
    #...
    def get_formset(self, request, obj=None, **kwargs):

        formset = super().get_formset(request, obj, **kwargs)
        user = formset.form.base_fields['user']

        user.widget.can_add_related = False
        user.widget.can_delete_related = False
        user.widget.can_change_related = False

        return formset

The answer by @Slipstream shows how to implement the solution, viz. by overriding the attributes for the formfield's widget, but, in my opinion, get_form is not the most logical place to do this.

The answer by @cethegeek shows where to implement the solution, viz. in an extension of formfield_for_dbfield, but does not provide an explicit example.

Why use formfield_for_dbfield? Its docstring suggests that it is the designated hook for messing with form fields:

Hook for specifying the form Field instance for a given database Field instance.

It also allows for (slightly) cleaner and clearer code, and, as a bonus, we can easily set additional form Field attributes, such as initial value and/or disabled (example here), by adding them to the kwargs (before calling super).

So, combining the two answers (assuming the OP's models are ModelA and ModelB, and the ForeignKey model field is named b):

class ModelAAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, request, **kwargs):
        # optionally set Field attributes here, by adding them to kwargs
        formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
        if db_field.name == 'b':
            formfield.widget.can_add_related = False
            formfield.widget.can_change_related = False
            formfield.widget.can_delete_related = False
        return formfield

# Don't forget to register...
admin.site.register(ModelA, ModelAAdmin)

NOTE: If the ForeignKey model field has on_delete=models.CASCADE, the can_delete_related attribute is False by default, as can be seen in the source for RelatedFieldWidgetWrapper.

I'm using Django 2.x and I think I found best solution, at least for my case.

The HTML file to the "Save and Add Another" button is on your_python_installation\Lib\site-packages\django\contrib\admin\templates\admin\subtmit_line.html.

  1. Copy that html file and paste to your project like so your_project\templates\admin\submit_line.html.
  2. Open it and comment/delete the button code as desired:

{#{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}#}

I know this problem is already answered. But maybe someone in the future have a similar case with me.

Based on cethegeek answer I made this:

class SomeAdmin(admin.ModelAdmin):
    form = SomeForm

    def formfield_for_dbfield(self, db_field, **kwargs):
        formfield = super(SomeAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        if db_field.name == 'some_m2m_field':
            request = kwargs.pop("request", None)
            formfield = self.formfield_for_manytomany(db_field, request, **kwargs)  # for foreignkey: .formfield_for_foreignkey
            wrapper_kwargs = {'can_add_related': False, 'can_change_related': False, 'can_delete_related': False}
            formfield.widget = admin.widgets.RelatedFieldWidgetWrapper(
                formfield.widget, db_field.remote_field, self.admin_site, **wrapper_kwargs
            )
        return formfield

The way i fixed a similar situation based on django docs

https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.extra

The outcome of the solution is that it lets you add an inline just for that instance. Or in different words: add an inline and just one; no other buttons.

#models.py
class Model_A(models.Model):
    ...    

class Model_B(models.Model):
    ...
    relevant_field = models.ForeignKey(Model_A, related_name='Model_B_relevant_field')

# forms.py or someotherfile.py
from django.contrib.admin import StackedInline, TabularInline
    
class Model_B_Inline(StackedInline):
    verbose_name = 'Some Name'
    ...

    def get_extra(self, request, obj=None, *args, **kwargs):
        the_extra = super().get_extra(request, obj=obj, *args, **kwargs)
        self.extra = 1
        if obj:
            the_counter = obj.Model_B_relevant_field.count()
        else:
            the_counter = -1
        self.max_num = the_counter + 1
        return the_extra
    
Related