Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Admin nested inline

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?

like image 273
tuna Avatar asked Jan 13 '13 20:01

tuna


8 Answers

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)
like image 134
Danny W. Adair Avatar answered Oct 01 '22 00:10

Danny W. Adair


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)
like image 40
bigzbig Avatar answered Oct 03 '22 00:10

bigzbig


pip install django-nested-inline

This package should do what you need.

like image 32
s-block Avatar answered Oct 01 '22 00:10

s-block


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.

like image 26
Paulo Scardine Avatar answered Sep 30 '22 00:09

Paulo Scardine


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

pip install django-super-inlines
like image 31
Rick Westera Avatar answered Oct 02 '22 00:10

Rick Westera


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

like image 26
Daniel González Fernández Avatar answered Oct 03 '22 00:10

Daniel González Fernández


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))
like image 21
appdev epiccycles Avatar answered Oct 02 '22 00:10

appdev epiccycles


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]
like image 33
Kai - Kazuya Ito Avatar answered Oct 04 '22 00:10

Kai - Kazuya Ito