Django Administration: Inlines for Inlines
Tags: admin, django, basics, intermediate
Published: 2019-04-06 21:06:05

---
title: "Django Administration: Inlines for Inlines"
published: true
tags: Admin,Django,Basics,Intermediate
canonical_url: http://djangotricks.blogspot.com/2016/12/django-administration-inlines-for-inlines.html
---

 ![](https://1.bp.blogspot.com/-76HnA7StJQA/WFN_Rn-JDSI/AAAAAAAABxQ/WNkXJyBjuxEv4jT3_p91b4vSP2w7x-HagCLcB/s1600/Django%2BAdministration-%2BInlines%2Bfor%2BInlines.png) 

The default Django model administration comes with a [concept of inlines](https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#inlinemodeladmin-objects). If you have a one-to-many relationship, you can edit the parent and its children in the same form. However, you are limited in a way that you cannot have inlines under inlines at nested one-to-many relations. For example, you can't show models `Painter`, `Picture`, and `Review` in the same form if one **painter** may have drawn multiple **pictures** and each **picture** may have several **reviews**.

 

In this article I would like to share a workaround allowing you to quickly access the inlines of an inline model. The idea is that for every inline you can provide a HTML link leading to the separate form where you can edit the related model and its own relations. It's as simple as that.

 

For example, in the form of `Painter` model, you have the instances of `Picture` listed with specific links "Edit this Picture separately":

  ![](https://4.bp.blogspot.com/-_dnJS_4sgVA/WFF_qUh6oeI/AAAAAAAABw8/1OvFUeVcDAM4Fdc0vu2WfLNrwnyb1XnagCLcB/s1600/painter-fullpage.png) 

When such a link is clicked, the administrator goes to the form of the `Picture` model which shows the instances of `Review` model listed underneath:

  ![](https://1.bp.blogspot.com/-LKT31vHsjQU/WFCbSBaeb-I/AAAAAAAABv0/Bokt-STUvm0vIVRNPw-z3HNPcnYwTSYBACLcB/s1600/picture-fullpage.png) 

Let's have a look, how to implement this.

 

First of all, I will create a `gallery` app and define the three models there. Nothing fancy here. The important part there is just that the `Picture` model has a foreign key to the `Painter` model and the `Review` model has a foreign key to the `Picture` model.

```python
# gallery/models.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals

import os

from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils.text import slugify

@python_2_unicode_compatible
class Painter(models.Model):
    name = models.CharField(_("Name"), max_length=255)

    class Meta:
        verbose_name = _("Painter")
        verbose_name_plural = _("Painters")

    def __str__ (self):
        return self.name

def upload_to(instance, filename):
    filename_base, filename_ext = os.path.splitext(filename)
    return "painters/{painter}/{filename}{extension}".format(
        painter=slugify(instance.painter.name),
        filename=slugify(filename_base),
        extension=filename_ext.lower(),
    )

@python_2_unicode_compatible
class Picture(models.Model):
    painter = models.ForeignKey(Painter, verbose_name=_("Painter"), on_delete=models.CASCADE)
    title = models.CharField(_("Title"), max_length=255)
    picture = models.ImageField(_("Picture"), upload_to=upload_to)

    class Meta:
        verbose_name = _("Picture")
        verbose_name_plural = _("Pictures")

    def __str__ (self):
        return self.title

@python_2_unicode_compatible
class Review(models.Model):
    picture = models.ForeignKey(Picture, verbose_name=_("Picture"), on_delete=models.CASCADE)
    reviewer = models.CharField(_("Reviewer name"), max_length=255)
    comment = models.TextField(_("Comment"))

    class Meta:
        verbose_name = _("Review")
        verbose_name_plural = _("Reviews")

    def __str__ (self):
        return self.reviewer
```
 

Then I will create the administration definition for the models of the `gallery` app. Here I will set two types of administration for the `Picture` model:

- By extending `admin.StackedInline` I will create administration stacked as inline.
- By extending `admin.ModelAdmin` I will create administration in a separate form.
 

In Django model administration besides usual form fields, you can also include some computed values. This can be done by your `fields` (or `fieldsets`) and `readonly_fields` attributes referring to a callable or a method name.

 

You can set a translatable label for those computed values by defining `short_description` attribute for the callable or method. If you want to render some HTML, you can also set the `allow_tags` attribute to `True` (otherwise your HTML string will be escaped).

 
```python
# gallery/admin.py
# -*- coding: UTF-8 -*-
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.utils.text import force_text

from .models import Painter, Picture, Review

def get_picture_preview(obj):
    if obj.pk: # if object has already been saved and has a primary key, show picture preview
        return """<a href="{src}" target="_blank"><img src="{src}" alt="{title}" style="max-width: 200px; max-height: 200px;" /></a>""".format(
            src=obj.picture.url,
            title=obj.title,
        )
    return _("(choose a picture and save and continue editing to see the preview)")
get_picture_preview.allow_tags = True
get_picture_preview.short_description = _("Picture Preview")

class PictureInline(admin.StackedInline):
    model = Picture
    extra = 0
    fields = ["get_edit_link", "title", "picture", get_picture_preview]
    readonly_fields = ["get_edit_link", get_picture_preview]

    def get_edit_link(self, obj=None):
        if obj.pk: # if object has already been saved and has a primary key, show link to it
            url = reverse('admin:%s_%s_change' % (obj._meta.app_label, obj._meta.model_name), args=[force_text(obj.pk)])
            return """<a href="{url}">{text}</a>""".format(
                url=url,
                text=_("Edit this %s separately") % obj._meta.verbose_name,
            )
        return _("(save and continue editing to create a link)")
    get_edit_link.short_description = _("Edit link")
    get_edit_link.allow_tags = True

@admin.register(Painter)
class PainterAdmin(admin.ModelAdmin):
    save_on_top = True
    fields = ["name"]
    inlines = [PictureInline]

class ReviewInline(admin.StackedInline):
    model = Review
    extra = 0
    fields = ["reviewer", "comment"]

@admin.register(Picture)
class PictureAdmin(admin.ModelAdmin):
    save_on_top = True
    fields = ["painter", "title", "picture", get_picture_preview]
    readonly_fields = [get_picture_preview]
    inlines = [ReviewInline]
```

__UPDATE!__ Since Django 2.0, the `get_picture_preview()` function should use `mark_safe()` instead of `allow_tags = True`:

```python
from django.utils.safestring import mark_safe
# ...
    def get_edit_link(self, obj=None):
        if obj.pk:  # if object has already been saved and has a primary key, show link to it
            url = reverse(
                'admin:%s_%s_change' % (obj._meta.app_label, obj._meta.model_name), 
                args=[force_text(obj.pk)]
            )
            return mark_safe("""<a href="{url}">{text}</a>""".format(
                url=url,
                text=_("Edit this %s separately") % obj._meta.verbose_name,
            ))
        return _("(save and continue editing to create a link)")
    get_edit_link.short_description = _("Edit link")
```

In this administration setup, the `get_edit_link()` method creates a HTML link between the inline and the separate administration form for the `Picture` model. As you can see, I also added the `get_picture_preview()` function as a bonus. It is included in both administration definitions for the `Picture` model and its purpose is to show a preview of the uploaded picture after saving it.

 

To recap, nested inlines are not supported by Django out of the box. However, you can have your inlines edited in a separate page with the forms linked to each other. For the linking you would use some magic of the `readonly_fields` attribute.

 

What if you really need to have inlines under inlines in your project? In that case you might check [django-nested-admin](https://github.com/theatlantic/django-nested-admin) and don't hesitate to share your experience with it in the comments.


_This post was originally published on [djangotricks.blogspot.com](http://djangotricks.blogspot.com/2016/12/django-administration-inlines-for-inlines.html)_