{"id":393506,"date":"2024-06-29T11:02:56","date_gmt":"2024-06-29T11:02:56","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=393506"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=393506","title":{"rendered":"<span>Django admin dynamic Inline positioning<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/253\/d1d\/7ea\/253d1d7eabecec046d668b10a331adba.jpg\" alt=\"Django ModelAdmin and Inlines\" title=\"Django ModelAdmin and Inlines\" width=\"1200\" height=\"504\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/253\/d1d\/7ea\/253d1d7eabecec046d668b10a331adba.jpg\" data-blurred=\"true\"\/><figcaption>Django ModelAdmin and Inlines<\/figcaption><\/figure>\n<p>Recently I&#8217;ve received an interesting request from a client about one of our Django projects.<br \/>He asked if it would be possible to show an inline component above other fields in the Django admin panel.<\/p>\n<p>At the beginning I thought, that there shouldn&#8217;t be any issue with that.<br \/>Though there was no easy solution other then installing another battery to the project. My gut feeling told me, there were another way around that problem.<\/p>\n<p>The first\u00a0<a href=\"https:\/\/linevi.ch\/en\/django-inline-in-fieldset.html\" rel=\"noopener noreferrer nofollow\"><u>solution<\/u><\/a>\u00a0I found was from 2017. It had too much code for such a simple task.<\/p>\n<p>Our executive lead programmer, Maxim Danilov found quite a short solution. He\u00a0<a href=\"https:\/\/habr.com\/ru\/post\/651179\/\" rel=\"noopener noreferrer nofollow\"><u>published his work online in russian<\/u><\/a>\u00a0about a month ago.<\/p>\n<p>I\u00b4d like to share these ideas with englisch speaking Django community in order to help others simplify their code. It might come handy for such a &#171;simple&#187; at first glance issues.<\/p>\n<p>Long story short, let&#8217;s dive into the code:<\/p>\n<p>Imagine you are building an E-commerce project. You have a\u00a0<code>ProductModel<\/code>which have O2M relation to\u00a0<code>ImageModel<\/code>.<\/p>\n<pre><code class=\"python\">from django.db import models from django.utils.translation import gettext_lazy as _  class Product(models.Model):   title = models.CharField(verbose_name=_('Title of product'), max_length=255)   price = models.DecimalField(verbose_name=_('Price of product'), max_digits=6, decimal_places=2)  class Image(models.Model):   src = models.ImageField(verbose_name=_('Imagefile'))   product = models.ForeignKey(Product, verbose_name=_('Link to product'), on_delete=models.CASCADE)<\/code><\/pre>\n<p>You also need\u00a0<code>ModelAdmins<\/code>\u00a0for given models.<\/p>\n<pre><code class=\"python\">from django.contrib import admin from .models import Image, Product  @admin.register(Product) class ProductModelAdmin(admin.ModelAdmin):   fields = ('title', 'price')  @admin.register(Image) class ImageModelAdmin(admin.ModelAdmin):   fields = ('src', 'product')<\/code><\/pre>\n<p>Now let&#8217;s create a simple inline to put into our\u00a0<code>ProductModelAdmin<\/code>.<\/p>\n<pre><code class=\"python\"> from django.contrib import admin     from django.contrib.admin.options import TabularInline     from .models import Image, Product      class ImageAdminInline(TabularInline):         extra = 1         model = Image      @admin.register(Product)     class ProductModelAdmin(admin.ModelAdmin):         inlines = (ImageAdminInline,)         fields = ('title', 'price')      @admin.register(Image)     class ImageModelAdmin(admin.ModelAdmin):         fields = ('src', 'product')<\/code><\/pre>\n<p>So far we have two simple models and basic\u00a0<code>ModelAdmins<\/code>\u00a0with an\u00a0<code>InlineModel<\/code>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ae1\/ae6\/cd8\/ae1ae6cd8587bee065158dd241bf4d44.JPG\" alt=\"Standard ModelAdmin With Inline\" title=\"Standard ModelAdmin With Inline\" width=\"664\" height=\"407\"\/><figcaption>Standard ModelAdmin With Inline<\/figcaption><\/figure>\n<p>How should we squeeze that Inline above or in between the two fields of the\u00a0<code>ProductModelAdmin<\/code>?<\/p>\n<p>I suppose, you are familiar with the\u00a0<a href=\"https:\/\/docs.djangoproject.com\/en\/4.0\/ref\/contrib\/admin\/#django.contrib.admin.ModelAdmin.readonly_fields\" rel=\"noopener noreferrer nofollow\"><u>added field<\/u><\/a>\u00a0concept in\u00a0<code>ModelAdminForm<\/code>. You can create a method in the\u00a0<code>ModelAdmin<\/code>\u00a0to display the response of the method in the form as a readonly field.<br \/>Keep in mind, that the rendering sequence of the\u00a0<code>ModelAdmin<\/code>\u00a0will create the\u00a0<code>InlineModels<\/code>\u00a0first, then render\u00a0<code>AdminForm<\/code>\u00a0and after that render the\u00a0<code>InlineForms<\/code>.<\/p>\n<p>We can use that to rearrange the order of Inlines and fields.<\/p>\n<pre><code class=\"python\">from django.contrib import admin from django.contrib.admin.options import TabularInline from django.template.loader import get_template from .models import Image, Product  class ImageAdminInline(TabularInline):  extra = 1  model = Image  @admin.register(Product) class ProductModelAdmin(admin.ModelAdmin):   inlines = (ImageAdminInline,)   fields = ('image_inline', 'title', 'price')   readonly_fields= ('image_inline',) # method as readonly field    def image_inline(self, *args, **kwargs):     context = getattr(self.response, 'context_data', None) or {}     inline = context['inline_admin_formset'] = context['inline_admin_formsets'].pop(0)     return get_template(inline.opts.template).render(context, self.request)    def render_change_form(self, request, *args, **kwargs):     self.request = request     self.response = super().render_change_form(request, *args, **kwargs)     return self.response<\/code><\/pre>\n<p>We use the\u00a0<code>render_change_form<\/code>\u00a0to get the objects\u00a0<code>request<\/code>\u00a0and\u00a0<code>response<\/code>.<br \/>We use those objects in the\u00a0<code>image_inline<\/code>\u00a0method to take one\u00a0<code>inline_formset<\/code>\u00a0from the list of\u00a0<code>inline_admin_formsets<\/code>\u00a0that have not been processed yet, and render\u00a0<code>InlineFormset<\/code>.<\/p>\n<p>After the\u00a0<code>change_form<\/code>\u00a0rendering the remaining\u00a0<code>inline_admin_formsets<\/code>\u00a0will be rendered, in case if the\u00a0<code>ModelAdmin<\/code>\u00a0still has some.<\/p>\n<p>Now we can use the method\u00a0<code>image_inline<\/code>\u00a0to determine the position of our\u00a0<code>InlineFormset<\/code>.<br \/>With the code-snippet above the inline element will be placed above all other fields.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4fc\/b7d\/776\/4fcb7d776a2a38a7570a92160381740c.JPG\" alt=\"ModelAdminForm with Inline on the top\" title=\"ModelAdminForm with Inline on the top\" width=\"666\" height=\"446\"\/><figcaption>ModelAdminForm with Inline on the top<\/figcaption><\/figure>\n<p>When we rearrange the fields this way the inline is rendered between the fields:<\/p>\n<pre><code class=\"python\">@admin.register(Product) class ProductModelAdmin(admin.ModelAdmin):   inlines = ImageAdminInline,   fields = 'title','image_inline', 'price'   readonly_fields= 'image_inline',  # method as readonly field<\/code><\/pre>\n<p>Of course Django admin adds a lable infront of the Inline with the name of the method, but that can be easily removed by some simple CSS in\u00a0<code>Media<\/code>\u00a0attribute of\u00a0<code>ProductModelAdmin<\/code>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c3a\/04c\/605\/c3a04c6057910542bcf252ff097f41a5.JPG\" alt=\"ModelAdminForm has inline in the middle\" title=\"ModelAdminForm has inline in the middle\" width=\"666\" height=\"442\"\/><figcaption>ModelAdminForm has inline in the middle<\/figcaption><\/figure>\n<p><strong>This solution has one fatal error! Every Django ModelAdmin is singelton, that is why we can not use ModelAdmin.self as a container in\u00a0<\/strong><code>render_change_form<\/code>!<\/p>\n<p>It is possible to change the ModelAdmins singleton\u00b4s behavior with a Mixin, staying inline with the concept of Djangos GCBV. We will take a closer look at it in my next article.<\/p>\n<p>It simply means, that we can&#8217;t use the instance of the\u00a0<code>ModelAdmin<\/code>\u00a0as a container to save our\u00a0<code>request<\/code>\u00a0and\u00a0<code>response<\/code>.<br \/>The solution is to save those objects in the\u00a0<code>AdminForm<\/code>\u00a0instance.<\/p>\n<pre><code class=\"python\">@admin.register(Product)     class ProductModelAdmin(admin.ModelAdmin):         inlines = (ImageAdminInline,)         fields = ('title', 'image_inline', 'price')         readonly_fields= ('image_inline',)  # we set the method as readonly field          def image_inline(self, obj=None, *args, **kwargs):             context = obj.response['context_data']             inline = context['inline_admin_formset'] = context['inline_admin_formsets'].pop(0)             return get_template(inline.opts.template).render(context, obj.request)          def render_change_form(self, request, context, *args, **kwargs):             instance = context['adminform'].form.instance  # get the model instance from modelform             instance.request = request             instance.response = super().render_change_form(request, context, *args, **kwargs)             return instance.response<\/code><\/pre>\n<p>The argument\u00a0<code>obj<\/code>\u00a0is not always given in\u00a0<code>render_change_form<\/code>\u00a0(i.e. add new object). That is why we have to get it from the\u00a0<code>ModelForm<\/code>, which is wrapped into the\u00a0<code>AdminForm<\/code>.<\/p>\n<p>Now we can set our request and response as attributes of the\u00a0<code>ModelForm<\/code>\u00a0instance and use those in\u00a0<code>image_inline<\/code>.<\/p>\n<h4>Summing up the above: you don&#8217;t have to install another battery to your project to solve a simple problem. Sometimes you need to dig deep enough into the framework that you use, and find a simple, short and quick solution.<\/h4>\n<hr\/>\n<p>I\u00b4d like to thank Martin Achenrainer the intern of wPsoft for contributing to this article and translating it.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/659173\/\"> https:\/\/habr.com\/ru\/articles\/659173\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><figcaption>Django ModelAdmin and Inlines<\/figcaption><\/figure>\n<p>Recently I&#8217;ve received an interesting request from a client about one of our Django projects.<br \/>He asked if it would be possible to show an inline component above other fields in the Django admin panel.<\/p>\n<p>At the beginning I thought, that there shouldn&#8217;t be any issue with that.<br \/>Though there was no easy solution other then installing another battery to the project. My gut feeling told me, there were another way around that problem.<\/p>\n<p>The first\u00a0<a href=\"https:\/\/linevi.ch\/en\/django-inline-in-fieldset.html\" rel=\"noopener noreferrer nofollow\"><u>solution<\/u><\/a>\u00a0I found was from 2017. It had too much code for such a simple task.<\/p>\n<p>Our executive lead programmer, Maxim Danilov found quite a short solution. He\u00a0<a href=\"https:\/\/habr.com\/ru\/post\/651179\/\" rel=\"noopener noreferrer nofollow\"><u>published his work online in russian<\/u><\/a>\u00a0about a month ago.<\/p>\n<p>I\u00b4d like to share these ideas with englisch speaking Django community in order to help others simplify their code. It might come handy for such a &#171;simple&#187; at first glance issues.<\/p>\n<p>Long story short, let&#8217;s dive into the code:<\/p>\n<p>Imagine you are building an E-commerce project. You have a\u00a0<code>ProductModel<\/code>which have O2M relation to\u00a0<code>ImageModel<\/code>.<\/p>\n<pre><code class=\"python\">from django.db import models from django.utils.translation import gettext_lazy as _  class Product(models.Model):   title = models.CharField(verbose_name=_('Title of product'), max_length=255)   price = models.DecimalField(verbose_name=_('Price of product'), max_digits=6, decimal_places=2)  class Image(models.Model):   src = models.ImageField(verbose_name=_('Imagefile'))   product = models.ForeignKey(Product, verbose_name=_('Link to product'), on_delete=models.CASCADE)<\/code><\/pre>\n<p>You also need\u00a0<code>ModelAdmins<\/code>\u00a0for given models.<\/p>\n<pre><code class=\"python\">from django.contrib import admin from .models import Image, Product  @admin.register(Product) class ProductModelAdmin(admin.ModelAdmin):   fields = ('title', 'price')  @admin.register(Image) class ImageModelAdmin(admin.ModelAdmin):   fields = ('src', 'product')<\/code><\/pre>\n<p>Now let&#8217;s create a simple inline to put into our\u00a0<code>ProductModelAdmin<\/code>.<\/p>\n<pre><code class=\"python\"> from django.contrib import admin     from django.contrib.admin.options import TabularInline     from .models import Image, Product      class ImageAdminInline(TabularInline):         extra = 1         model = Image      @admin.register(Product)     class ProductModelAdmin(admin.ModelAdmin):         inlines = (ImageAdminInline,)         fields = ('title', 'price')      @admin.register(Image)     class ImageModelAdmin(admin.ModelAdmin):         fields = ('src', 'product')<\/code><\/pre>\n<p>So far we have two simple models and basic\u00a0<code>ModelAdmins<\/code>\u00a0with an\u00a0<code>InlineModel<\/code>.<\/p>\n<figure class=\"full-width\"><figcaption>Standard ModelAdmin With Inline<\/figcaption><\/figure>\n<p>How should we squeeze that Inline above or in between the two fields of the\u00a0<code>ProductModelAdmin<\/code>?<\/p>\n<p>I suppose, you are familiar with the\u00a0<a href=\"https:\/\/docs.djangoproject.com\/en\/4.0\/ref\/contrib\/admin\/#django.contrib.admin.ModelAdmin.readonly_fields\" rel=\"noopener noreferrer nofollow\"><u>added field<\/u><\/a>\u00a0concept in\u00a0<code>ModelAdminForm<\/code>. You can create a method in the\u00a0<code>ModelAdmin<\/code>\u00a0to display the response of the method in the form as a readonly field.<br \/>Keep in mind, that the rendering sequence of the\u00a0<code>ModelAdmin<\/code>\u00a0will create the\u00a0<code>InlineModels<\/code>\u00a0first, then render\u00a0<code>AdminForm<\/code>\u00a0and after that render the\u00a0<code>InlineForms<\/code>.<\/p>\n<p>We can use that to rearrange the order of Inlines and fields.<\/p>\n<pre><code class=\"python\">from django.contrib import admin from django.contrib.admin.options import TabularInline from django.template.loader import get_template from .models import Image, Product  class ImageAdminInline(TabularInline):  extra = 1  model = Image  @admin.register(Product) class ProductModelAdmin(admin.ModelAdmin):   inlines = (ImageAdminInline,)   fields = ('image_inline', 'title', 'price')   readonly_fields= ('image_inline',) # method as readonly field    def image_inline(self, *args, **kwargs):     context = getattr(self.response, 'context_data', None) or {}     inline = context['inline_admin_formset'] = context['inline_admin_formsets'].pop(0)     return get_template(inline.opts.template).render(context, self.request)    def render_change_form(self, request, *args, **kwargs):     self.request = request     self.response = super().render_change_form(request, *args, **kwargs)     return self.response<\/code><\/pre>\n<p>We use the\u00a0<code>render_change_form<\/code>\u00a0to get the objects\u00a0<code>request<\/code>\u00a0and\u00a0<code>response<\/code>.<br \/>We use those objects in the\u00a0<code>image_inline<\/code>\u00a0method to take one\u00a0<code>inline_formset<\/code>\u00a0from the list of\u00a0<code>inline_admin_formsets<\/code>\u00a0that have not been processed yet, and render\u00a0<code>InlineFormset<\/code>.<\/p>\n<p>After the\u00a0<code>change_form<\/code>\u00a0rendering the remaining\u00a0<code>inline_admin_formsets<\/code>\u00a0will be rendered, in case if the\u00a0<code>ModelAdmin<\/code>\u00a0still has some.<\/p>\n<p>Now we can use the method\u00a0<code>image_inline<\/code>\u00a0to determine the position of our\u00a0<code>InlineFormset<\/code>.<br \/>With the code-snippet above the inline element will be placed above all other fields.<\/p>\n<figure class=\"full-width\"><figcaption>ModelAdminForm with Inline on the top<\/figcaption><\/figure>\n<p>When we rearrange the fields this way the inline is rendered between the fields:<\/p>\n<pre><code class=\"python\">@admin.register(Product) class ProductModelAdmin(admin.ModelAdmin):   inlines = ImageAdminInline,   fields = 'title','image_inline', 'price'   readonly_fields= 'image_inline',  # method as readonly field<\/code><\/pre>\n<p>Of course Django admin adds a lable infront of the Inline with the name of the method, but that can be easily removed by some simple CSS in\u00a0<code>Media<\/code>\u00a0attribute of\u00a0<code>ProductModelAdmin<\/code>.<\/p>\n<figure class=\"full-width\"><figcaption>ModelAdminForm has inline in the middle<\/figcaption><\/figure>\n<p><strong>This solution has one fatal error! Every Django ModelAdmin is singelton, that is why we can not use ModelAdmin.self as a container in\u00a0<\/strong><code>render_change_form<\/code>!<\/p>\n<p>It is possible to change the ModelAdmins singleton\u00b4s behavior with a Mixin, staying inline with the concept of Djangos GCBV. We will take a closer look at it in my next article.<\/p>\n<p>It simply means, that we can&#8217;t use the instance of the\u00a0<code>ModelAdmin<\/code>\u00a0as a container to save our\u00a0<code>request<\/code>\u00a0and\u00a0<code>response<\/code>.<br \/>The solution is to save those objects in the\u00a0<code>AdminForm<\/code>\u00a0instance.<\/p>\n<pre><code class=\"python\">@admin.register(Product)     class ProductModelAdmin(admin.ModelAdmin):         inlines = (ImageAdminInline,)         fields = ('title', 'image_inline', 'price')         readonly_fields= ('image_inline',)  # we set the method as readonly field          def image_inline(self, obj=None, *args, **kwargs):             context = obj.response['context_data']             inline = context['inline_admin_formset'] = context['inline_admin_formsets'].pop(0)             return get_template(inline.opts.template).render(context, obj.request)          def render_change_form(self, request, context, *args, **kwargs):             instance = context['adminform'].form.instance  # get the model instance from modelform             instance.request = request             instance.response = super().render_change_form(request, context, *args, **kwargs)             return instance.response<\/code><\/pre>\n<p>The argument\u00a0<code>obj<\/code>\u00a0is not always given in\u00a0<code>render_change_form<\/code>\u00a0(i.e. add new object). That is why we have to get it from the\u00a0<code>ModelForm<\/code>, which is wrapped into the\u00a0<code>AdminForm<\/code>.<\/p>\n<p>Now we can set our request and response as attributes of the\u00a0<code>ModelForm<\/code>\u00a0instance and use those in\u00a0<code>image_inline<\/code>.<\/p>\n<h4>Summing up the above: you don&#8217;t have to install another battery to your project to solve a simple problem. Sometimes you need to dig deep enough into the framework that you use, and find a simple, short and quick solution.<\/h4>\n<hr\/>\n<p>I\u00b4d like to thank Martin Achenrainer the intern of wPsoft for contributing to this article and translating it.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/659173\/\"> https:\/\/habr.com\/ru\/articles\/659173\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-393506","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/393506","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=393506"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/393506\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=393506"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=393506"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=393506"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}