Django Admin nested inline
DjangoDjango ModelsDjango AdminDjango Problem Overview
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 ?
Django Solutions
Solution 1 - Django
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. Give CertificateInline
an additional field "Details" which is a link to its ModelAdmin
change form.
models.py:
from django.core import urlresolvers
class Certificate(models.Model):
# ...
def changeform_link(self):
if self.id:
# Replace "myapp" with the name of the app containing
# your Certificate model:
changeform_url = urlresolvers.reverse(
'admin:myapp_certificate_change', args=(self.id,)
)
return u'<a href="%s" target="_blank">Details</a>' % changeform_url
return u''
changeform_link.allow_tags = True
changeform_link.short_description = '' # omit column header
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', 'changeform_link')
readonly_fields = ('changeform_link', )
class PersonAdmin(admin.ModelAdmin):
inlines = [CertificateLinkInline,]
admin.site.register(Person, PersonAdmin)
Solution 2 - Django
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)
Solution 3 - Django
pip install django-nested-inline
This package should do what you need.
Solution 4 - Django
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.
Solution 5 - Django
Nested inlines are provided at: https://github.com/BertrandBordage/django-super-inlines/
pip install django-super-inlines
Solution 6 - Django
A more up to date solution (february 2021) is to use the show_change_link config variable: https://docs.djangoproject.com/en/3.1/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
UPDATE (January 25th, 2022): Here's the updated link in the docs (Django 4.0): https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.show_change_link
Solution 7 - Django
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))