9

In django admin site, if the model has a foreignkey, by default it will be a select input and there are three links(edit, add, delete) like below. How to disable these links only for foreignkey? If using has_delete_permission, they it cannot be deleted in its own change form either.

Default: default

Expected: after

Community
  • 1
  • 1
Mas Zero
  • 503
  • 3
  • 16
  • does this help: https://stackoverflow.com/a/17348422/4872140 source at https://docs.djangoproject.com/en/2.2/_modules/django/contrib/admin/options/ – AMG Jun 02 '19 at 12:06
  • it helps. can_delete_related is in widget ... – Mas Zero Jun 02 '19 at 12:37

5 Answers5

15

It's not exactly a documented feature. Furthermore, formfield_for_dbfield is a damned mess (just as most of the django.contrib.admin). But it's the cleanest approach I found, and it works fine for me.

class MyAdmin(django.contrib.admin.ModelAdmin):

    def formfield_for_dbfield(self, *args, **kwargs):
        formfield = super().formfield_for_dbfield(*args, **kwargs)

        formfield.widget.can_delete_related = False
        formfield.widget.can_change_related = False
        # formfield.widget.can_add_related = False  # can change this, too
        # formfield.widget.can_view_related = False  # can change this, too

        return formfield
Art
  • 2,235
  • 18
  • 34
7

in admin.py create a def under your model ModelAdmin class. Choose the options for Add, Change or Delete related to the foreign_key field, keeping the ones you desire to hide and deleting (or changing boolean from False to True) for the ones you wante to show:

class YourModelAdmin(admin.ModelAdmin):
    ...
    def get_form(self, request, obj=None, **kwargs):
        form = super(YourModelAdmin, self).get_form(request, obj, **kwargs)
        field = form.base_fields["your_foreign_key_field"]
        field.widget.can_add_related = False
        field.widget.can_change_related = False
        field.widget.can_delete_related = False
        return form
Guaracy Lima
  • 91
  • 1
  • 4
  • This worked great in the base admin, but I can't get it to also work in an inline (`StackedInline`). Any suggestions? :) – arielkaluzhny Aug 02 '21 at 19:54
  • 1
    Answer to my own question^^ use `get_formset` instead! Worked like a charm: ```def get_formset(self, request, obj=None, **kwargs): formset = super().get_formset(request, obj, **kwargs) field = formset.form.base_fields["impersonating"]``` – arielkaluzhny Aug 02 '21 at 20:07
  • 1
    @arielkaluzhny I would suggest posting a separate answer so that you can show your answer with full code formatting! Cheers – Greg Sadetsky Nov 23 '21 at 01:49
  • 2
    Thanks @GregSadetsky, I did that. :) – arielkaluzhny Dec 15 '21 at 20:14
4

(Transferring my comment to an answer)

The above answers work great for regular admin pages. To get this to work in an admin inline (ex: admin.StackedInline), use get_formset instead of get_form:

def get_formset(self, request, obj=None, **kwargs):
     formset = super().get_formset(request, obj, **kwargs)
     field = formset.form.base_fields["your_foreign_key_field"]
     field.widget.can_add_related = False
     field.widget.can_change_related = False
     field.widget.can_delete_related = False
     return formset

arielkaluzhny
  • 307
  • 2
  • 12
3

Just to add to the completeness of Art's answer:

For an Inline (admin.StackedInline, admin.TabularInline) you can use formfield_for_dbfield as well:

class MyInline(django.contrib.admin.TabularInline):

    def formfield_for_dbfield(self, *args, **kwargs):
        formfield = super().formfield_for_dbfield(*args, **kwargs)
        if formfield:
            formfield.widget.can_delete_related = False
            formfield.widget.can_change_related = False
            formfield.widget.can_add_related = False
            formfield.widget.can_view_related = False

        return formfield

formfield is somehow None sometimes for Inlines so you need to add the if statement.

Austin Fox
  • 121
  • 1
  • 8
0
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.fields['xxx'].widget.can_delete_related = False
Ron
  • 22,128
  • 31
  • 108
  • 206
Mas Zero
  • 503
  • 3
  • 16
  • 5
    While this code snippet may solve the question, [including an explanation](//meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – rene Jun 02 '19 at 15:37
  • ```fields``` is ```None``` in ```__init___``` – nak Aug 13 '22 at 18:12