Django Admin nested inline
Asked Answered
X

8

70

I need a nested django admin inline, which I can include the date field inlines in an other inline like below.

I have the models below:

class Person(models.Model):
     name = models.CharField(max_length=200)
     id_no = models.IntegerField()

class Certificate(models.Model):
     cerfificate_no = models.CharField(max_length=200)
     certificate_date = models.DateField(max_length=100)
     person = models.ForeignKey(Person)
     training = models.CharField(max_length=200)

class Training_Date(models.Model):
      date = models.DateField()
      certificate = models.ForeignKey(Certificate)

And, the admin below:

class CertificateInline(admin.StackedInline):
    model = Certificate

class PersonAdmin(admin.ModelAdmin):
     inlines = [CertificateInline,]
admin.site.register(Person,PersonAdmin)

But, I need to include the Training_Date model as inline which is part of Certificate admin inline.

Any idea?

Xerxes answered 13/1, 2013 at 20:54 Comment(0)
L
20

AFAIK, you can't have a second level of inlines in the default Django admin.

The Django admin is just a normal Django application, so nothing prevents you from implementing a second level of nested forms, but IMHO it would be a kind of convoluted design to implement. Perhaps that is why there is no provision for it.

Lysenkoism answered 13/1, 2013 at 22:18 Comment(1)
I am faced with a similar scenario. I think I am going to just override the inline template and add some links to the second level.Paris
C
49

There has been some movement in https://code.djangoproject.com/ticket/9025 recently, but I wouldn't hold my breath.

One common way around this is to link to an admin between first and second (or second and third) level by having both a ModelAdmin and an Inline for the same model:

Give Certificate a ModelAdmin with TrainingDate as an inline. Set show_change_link = True for CertificateInline so you can click on an inline to go to its ModelAdmin change form.

admin.py:

# Certificate change form has training dates as inline

class TrainingDateInline(admin.StackedInline):
    model = TrainingDate

class CertificateAdmin(admin.ModelAdmin):
    inlines = [TrainingDateInline,]
admin.site.register(Certificate ,CertificateAdmin)

# Person has Certificates inline but rather
# than nesting inlines (not possible), shows a link to
# its own ModelAdmin's change form, for accessing TrainingDates:

class CertificateLinkInline(admin.TabularInline):
    model = Certificate
    # Whichever fields you want: (I usually use only a couple
    # needed to identify the entry)
    fields = ('cerfificate_no', 'certificate_date')
    # Django 1.8 introduced this, no need to make your own link
    show_change_link = True

class PersonAdmin(admin.ModelAdmin):
    inlines = [CertificateLinkInline,]
admin.site.register(Person, PersonAdmin)
Cybernetics answered 14/1, 2013 at 23:29 Comment(8)
This is a nice solution. I want to point out that you can put the changeform_link in the CertificateLinkInline. That might be a better place because it's django-admin specific. Note that when you do this you should use instance.id instead of self.id to get to the instance of the model. See docs.djangoproject.com/en/dev/ref/contrib/admin/…Overcash
If the changeform_link method produces an exception then django will eat it and continue, leaving the field blank. I was never able to find where django puts the traceback or if it does anything with it at all. I suggest wrapping it in a try/except to make sure the exception gets logged somewhere. To simplify, it may be useful to create a decorator handle all of this.Gilstrap
Looks like on Django 2+ you can just add show_change_link = True to your admin.TabularInline class and I think as long as you have a regular admin.ModelAdmin for it also, it will show a “change” link in the inline.Jamikajamil
Though using show_change_link = True is much simpler.Cataphoresis
@Jamikajamil @Cataphoresis show_change_link was introduced by Django 1.8, released April 1, 2015. Over a year after my answer, but yes, that's been the one-liner equivalent since then. :)Cybernetics
@DannyW.Adair Excelent solution +1! Would be great for people if you update your answer with show_change_link = True, even with people already noticing it on the comments, because visitors and newcomers might not check the comments. Of course, you don't have to do it, but would be very nice of you.Beuthen
@RamonK. Done - I've updated the solution (somehow my comment from 2 days ago about this disappeared)Cybernetics
I didn't knew about show_change_link = True! That's awesome!Weakly
E
32

More universal solution

from django.utils.safestring import mark_safe
from django.urls import reverse

class EditLinkToInlineObject(object):
    def edit_link(self, instance):
        url = reverse('admin:%s_%s_change' % (
            instance._meta.app_label,  instance._meta.model_name),  args=[instance.pk] )
        if instance.pk:
            return mark_safe(u'<a href="{u}">edit</a>'.format(u=url))
        else:
            return ''

class MyModelInline(EditLinkToInlineObject, admin.TabularInline):
    model = MyModel
    readonly_fields = ('edit_link', )

class MySecondModelAdmin(admin.ModelAdmin):
    inlines = (MyModelInline, )

admin.site.register(MyModel)
admin.site.register(MySecondModel, MySecondModelAdmin)
Eloyelreath answered 1/3, 2014 at 11:47 Comment(2)
Simple and flexible. +1Federalize
A very similar solution has been described in more detail in this article: djangotricks.blogspot.com/2016/12/…Methacrylate
L
20

AFAIK, you can't have a second level of inlines in the default Django admin.

The Django admin is just a normal Django application, so nothing prevents you from implementing a second level of nested forms, but IMHO it would be a kind of convoluted design to implement. Perhaps that is why there is no provision for it.

Lysenkoism answered 13/1, 2013 at 22:18 Comment(1)
I am faced with a similar scenario. I think I am going to just override the inline template and add some links to the second level.Paris
B
20
pip install django-nested-inline

This package should do what you need.

Bestraddle answered 26/10, 2014 at 16:46 Comment(5)
django-nested-inline isn't (yet?) supported on latest django releases. But you could consider using github.com/theatlantic/django-nested-admin that is almost the same.Amidst
@Amidst could you give me a link to the doc, where described how to use it without packages?Dissonant
@OleksandrDashkov django-nested-admin.readthedocs.io/en/latest, on the top of the github page. But probably still have to install the package anyway.Amidst
Word of warning, I started using django-nested-inline and have run into multiple bugs that are time consuming to figure out. You may want to evaluate other options before deciding on which module to use.Sideways
@Sideways did you find any reasonable alternative to thisLevanter
W
6

A more up-to-date solution (february 2021) is to use the show_change_link config variable: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.show_change_link

This does exactly the same as the EditLinkToInlineObject proposed in solutions above, but is less code and is probably well tested by Django Developers

You would just have to define show_change_link=True in each one of your inlines

Wharve answered 7/2, 2021 at 23:16 Comment(0)
T
5

Nested inlines are provided at: https://github.com/BertrandBordage/django-super-inlines/

pip install django-super-inlines
Thirion answered 15/5, 2015 at 0:44 Comment(1)
I tried it. It does not allow me to add or delete some of the inlines and forces me to make 3 new child objects. It's not usable at this stage of development.Choli
D
5

Use django-nested-admin which is the best package to do nested inlines.

First, install "django-nested-admin":

pip install django-nested-admin

Then, add "nested_admin" to "INSTALLED_APPS" in "settings.py":

# "settings.py"

INSTALLED_APPS = (
    # ...
    "nested_admin", # Here
)

Then, add "path('_nested_ad..." to "urlpatterns" in "urls.py":

# "urls.py"

from django.urls import include, path

urlpatterns = [
    # ...
    path('_nested_admin/', include('nested_admin.urls')), # Here
]

Finally, extend "NestedTabularInline" with "Training_DateInline()" and "CertificateInline()" classes and extend "NestedModelAdmin" with "PersonAdmin()" class in "admin.py" as shown below:

# "admin.py"

from .models import Training_Date, Certificate, Person
from nested_admin import NestedTabularInline, NestedModelAdmin

class Training_DateInline(NestedTabularInline):
    model = Training_Date

class CertificateInline(NestedTabularInline):
    model = Certificate
    inlines = [Training_DateInline]

@admin.register(Person)
class PersonAdmin(NestedModelAdmin):
    inlines = [CertificateInline]
Doughty answered 23/6, 2022 at 20:39 Comment(0)
Y
2

I used the solution provided by @bigzbig (thank you).

I also wanted to go back to the first list page once changes had been saved so added:

class MyModelInline(EditLinkToInlineObject, admin.TabularInline):
    model = MyModel
    readonly_fields = ('edit_link', )

    def response_post_save_change(self, request, obj):
        my_second_model_id = MyModel.objects.get(pk=obj.pk).my_second_model_id
        return redirect("/admin/mysite/mysecondmodel/%s/change/" % (my_second_model_id))
Ylem answered 27/1, 2018 at 14:47 Comment(1)
Thanks for the snippet! I just wanted to add that this only worked for me after including the post_save_change function inside MyModelAdmin.Koger

© 2022 - 2024 — McMap. All rights reserved.