2016-12-16

Django Administration: Inlines for Inlines

The default Django model administration comes with a concept of inlines. 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":

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:

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.

# 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).

# 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:

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 and don't hesitate to share your experience with it in the comments.


Cover photo by Denys Nevozhai

2 comments:

  1. Консоли от корпорации Microsoft не сразу завоевали всемирную известность и доверие игроков. Первая консоль под названием Xbox, вышедшая в далеком 2001 году, существенно уступала PlayStation 2 по количеству проданных приставок. Но все изменилось с выходом Xbox 360 - консоли седьмого поколения, которая стала по-настоящему "народной" для обитателей Рф и государств СНГ - игры для xbox 360 скачать торрент. Сайт Ru-Xbox.Ru является популярным ресурсом среди поклонников приставки, так как он предлагает игры для Xbox 360, которые поддерживают все имеющиеся версии прошивок - совсем бесплатно! Для чего играть на оригинальном железе, если имеется эмуляторы? Для Xbox 360 игры выходили длительное время и представлены как посредственными проектами, так и хитами, многие из которых даже сегодня остаются эксклюзивными для это консоли. Некоторые гости, желающие сыграть в игры для Xbox 360, смогут задать вопрос: зачем необходимы игры для прошитых Xbox 360 freeboot либо различными версиями LT, если имеется эмулятор? Рабочий эмулятор Xbox 360 хоть и существует, однако он требует производительного ПК, для покупки которого потребуется вложить существенную сумму. К тому же, разнообразные артефакты в виде исчезающих текстур, отсутствия некоторых графических эффектов и освещения - смогут значительно попортить впечатления об игре и отбить желание для ее предстоящего прохождения. Что предлагает этот веб-сайт? Наш веб-сайт стопроцентно приурочен к играм для приставки Xbox 360. У нас вы можете совершенно бесплатно и без регистрации скачать игры на Xbox 360 через torrent для следующих версий прошивок консоли: - FreeBoot; - LT 3.0; - LT 2.0; - LT 1.9. Каждая прошивка имеет свои особенности обхода интегрированной защиты. Поэтому, для запуска той либо прочей игры потребуется загрузить специальную ее версию, которая полностью адаптирована под одну из четырех вышеперечисленных прошивок. На нашем сайте можно без усилий подобрать желаемый проект под подходящую прошивку, так как возле каждой игры присутствует заглавие версии (FreeBoot, LT 3.0/2.0/1.9), под которую она приспособлена. Гостям данного ресурса доступна особая категория игр для 360-го, созданных для Kinect - специального дополнения, которое считывает все движения одного либо нескольких игроков, и позволяет управлять с помощью их компьютерными персонажами. Большой выбор ПО Кроме способности скачать игры на Xbox 360 Freeboot либо LT разных версий, здесь вы можете подобрать программное обеспечение для консоли от Майкрософт: - современные версии Dashboard, которые позволяют кастомизировать интерфейс консоли под свои нужды, сделав его более удобным и нынешним; - браузеры; - просмотрщики файлов; - сохранения для игр; - темы для консоли; - программы, для конвертации образов и записи их на диск. Кроме перечисленного выше игры на Xbox 360 Freeboot вы можете запускать не с дисковых, а с USB и других носителей, используя программу x360key, которую вы можете достать на нашем сайте. Посетителям доступно множество нужных статей, а кроме этого форум, где вы можете пообщаться с единомышленниками либо попросить совета у более опытных владельцев консоли.

    ReplyDelete
  2. Консоли от корпорации Microsoft не сразу захватили всемирную известность и доверие игроков. 1-ая консоль под названием Xbox, вышедшая в далеком 2001 году, значительно уступала PlayStation 2 по количеству проданных приставок. Однако все изменилось с выходом Xbox 360 - консоли седьмого поколения, которая стала по-настоящему "народной" для жителей России и стран СНГ - http://ru-xbox.ru/load/1/igry_xbox_360_kinect/11. Сайт Ru-Xbox.Ru является пользующимся популярностью ресурсом в числе поклонников приставки, поскольку он предлагает игры для Xbox 360, которые поддерживают все имеющиеся версии прошивок - совершенно бесплатно! Зачем играть на оригинальном железе, если имеется эмуляторы? Для Xbox 360 игры выходили длительное время и находятся как посредственными проектами, так и хитами, многие из которых даже сегодня остаются уникальными для это консоли. Некие гости, желающие сыграть в игры для Xbox 360, могут задать вопрос: для чего нужны игры для прошитых Xbox 360 freeboot либо разными версиями LT, если есть эмулятор? Рабочий эмулятор Xbox 360 хоть и существует, но он требует производительного ПК, для покупки которого потребуется вложить существенную сумму. К тому же, разные артефакты в виде исчезающих текстур, отсутствия некоторых графических эффектов и освещения - могут изрядно испортить впечатления об игре и отбить желание для ее дальнейшего прохождения. Что предлагает этот интернет-сайт? Наш интернет-сайт вполне приурочен к играм для приставки Xbox 360. У нас вы можете совсем бесплатно и без регистрации загрузить игры на Xbox 360 через торрент для следующих версий прошивок консоли: - FreeBoot; - LT 3.0; - LT 2.0; - LT 1.9. Каждая прошивка имеет свои особенности обхода интегрированной защиты. Потому, для запуска той либо другой игры потребуется загрузить специальную ее версию, которая на сто процентов адаптирована под одну из четырех перечисленных выше прошивок. На нашем портале можно без усилий подобрать желаемый проект под подходящую прошивку, поскольку возле каждой игры находится заглавие версии (FreeBoot, LT 3.0/2.0/1.9), под которую она приспособлена. Геймерам данного ресурса доступна особая категория игр для 360-го, предназначенных для Kinect - специального дополнения, которое считывает все движения 1-го либо нескольких игроков, и позволяет управлять с их помощью компьютерными персонажами. Большой выбор ПО Кроме способности скачать игры на Xbox 360 Freeboot либо LT разных версий, тут можно найти программное обеспечение для консоли от Майкрософт: - всевозможные версии Dashboard, которые позволяют кастомизировать интерфейс консоли под свои нужды, сделав его более удобным и современным; - браузеры; - просмотрщики файлов; - сохранения для игр; - темы для консоли; - программы, для конвертации образов и записи их на диск. Кроме перечисленного выше игры на Xbox 360 Freeboot вы можете запускать не с дисковых, а с USB и прочих носителей, используя программу x360key, которую вы можете достать на нашем портале. Посетителям доступно множество нужных статей, а также форум, где можно пообщаться с единомышленниками или попросить совета у более опытных хозяев консоли.

    ReplyDelete