DEV Community

Cover image for Django nested inline redering
Maxim Danilov
Maxim Danilov

Posted on • Edited on

Django nested inline redering

This article expands on the ideas of my previous articles:
Django admin dynamic Inline positioning and How to solve the singleton problem in Django ModelAdmin.

This is also the first part of a three-part series on how to get Nested Inlines in the Django admin panel.

  1. Django Nested Inline. Rendering.
    This article is about rendering a Nested Inline. At the end we should see a Nested Inline in our ModelAdmin. But this Nested Inline can not save own data yet.

  2. Django nested inline. Save data.
    The second article shows integration of nested inlines in the ModelAdmin to make saving data possible.

  3. Django nested inline. Javascript.
    The final article in the series explains how to override the inlines.js in Django package to make it possible to add/remove Nested Inlines on fly in the ModelAdmin.

Although you will hopefully not need to implement nested-inlines at present, you can learn a thing or two about Django.

If you are want a quick code example click here.

Introspection of existed Nested Inline packages

The direct way to get Nested Inlines in Django Admin would be to install the package, since surely someone has already solved this problem, right?

After a quick search, you come across Django-Nested-Inline and think you're done.
Unfortunately, this solution has some problems and does not work properly.

For example, it doesn't allow synchronous editing of objects at the same time. I mean - if several people are working with your Admin Panel at the same time, there will be unexplainable errors all the time. The probability of such errors grows exponentially with each manager and each nested Inline.

So we can try to create our own Nested Inline. It's not complicated.

Basic Nested Inline rendering

First create a shop model, a product model and an image model. The product model must relate to the shop model, and the image model must relate to the product model.

Something like this:

Now we create some basic ModelAdmins with Inlines, like this:

Let's make some changes to ProductModelAdmin and ShopModelAdmin. We need to set the request and response as attributes for both ModelAdmin.

Experienced Django developers may notice that using a ModelAdmin instance as a container is not threadsave, and they would be correct if it were the default admin panel.
Here I refer them back to my previous article to explain how this works.

This is one of the cornerstones of how to create nested Inlines.

Additionally we should override the get_inline_instances method of ShopModelAdmin to set the modeladmin inline attribute to the current admin instance, this will be useful later.

I have made this function as generator, it calculated only when the result used for iteration.

We can now take the idea of dynamic inline positioning and develop it further. If it's possible to use inline in ModelAdmin as a field, it should be possible to do the same in inline itself, so let's copy the ProductModelAdmin code from that article.

But not everything works the same as in the ModelAdmin.

First, we can not get the context from the self.response attribute because the inline does not have that attribute.
But the Inline has the attribute modeladmin because we defined it in get_inline_instances. inline.modeladmin is the ModelAdmin instance. That has a request and a response attribute. So we can get the context from there.



context = getattr(self.modeladmin.response, 'context_data', None) or {}


Enter fullscreen mode Exit fullscreen mode

Secondly, Context does not contain the Information about Nested inline. With the code above we want to display a nested inline in the field of the current inline. We can call the view method for ProductModelAdmin and extract a nested inline from response. There is inline that must be nested.



admin_response = ProductModelAdmin(self.model, self.modeladmin.admin_site).add_view(self.modeladmin.request)

inline = admin_response.context_data['inline_admin_formsets'][0]



Enter fullscreen mode Exit fullscreen mode

Although it should be noted that this is not the final version: in reality we need to call the appropriate ModelAdmin view as requested, rather than just add_view.

Thirdly, we need to modify the args we pass to the render method so, that they contain an updated context that includes our extracted nested inline, and request.



return get_template(inline.opts.template).render(context | {'inline_admin_formset': inline}, self.modeladmin.request)



Enter fullscreen mode Exit fullscreen mode

In this construction we don't change context variable directly. The update operator ('|') creates a copy of the dictionary and updates it with the new values.

The final method should look like this:

If we start our project and try add a new shop model we see our nested inline.

basic nested inline

As you can see, our nested inline is displayed. Yay! But you might also notice that there are some problems rendering the inlines. This is because Djangos inlines.js for inlines is a complete mess and needs to be refactored. My issue about it in djangoproject was rejected as "ivalid".

Fortunately, I will show, how to do this in last article of this series.

At the moment, this display error can be temporarily fixed by changing one line of code in the django\contrib\admin\static\admin\js\inlines.js file.



// row 335:
$(selector).stackedFormset(selector, inlineOptions.options);

// change to:

$(this).find("[id^=" + inlineOptions.name.substring(1) + "-]").stackedFormset(selector, inlineOptions.options);



Enter fullscreen mode Exit fullscreen mode

Not it works better than before.

inline with js fixed

Now it's not perfect, and the rendering problem still exists.

inline with js fixed, extended

Next Article

This article was only about getting the nested inline before the first render, without validation and saving data of nested Inline. In the next article I will show how to save the data properly.

This article war Written by Martin Achenrainer. I thank Martin, for translating and assisting with this English article. This idea was first presented in September 2021 at PyCon RU

Some words from Martin:

I would like to thank Maxim Danilov for his extensive help on this series of articles, he was the inspiration and led me to this clever solution. I would also like to thank him and his company wP Soft for giving me the opportunity to develop and improve my programming skills in learning by doing.

Top comments (2)

Collapse
 
dehess profile image
DeHess

Hello,

This code works great!
there is just one bug, when I put the "extra" attribute in the Inlines to 0,
the addInlineclickHandlers don't function anymore. In this case, clicking on "Add Another" doesn't do anything.

I'm going to try and fix this in the inlines.js myself and post a fix here if I can find one.
If you guys have a quick solution to this I'd be glad to hear from you.

Collapse
 
danilovmy profile image
Maxim Danilov • Edited

Thank you. Can you add an issue about this bug? I try to improve it.