Django nested inline redering
Tags: django, python
Published: 2022-06-24 09:26:41

This article expands on the ideas of my previous articles:
[Django admin dynamic Inline positioning](https://dev.to/danilovmy/django-admin-dynamic-inline-positioning-3cam) and [How to solve the singleton problem in Django ModelAdmin](https://dev.to/danilovmy/how-to-solve-the-singleton-problem-in-django-modeladmin-g42).

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.](https://dev.to/danilovmy/django-nested-inline-save-data-5a7n)
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](https://github.com/wP-soft-GmbH/django_admin_nested_inlines).

## 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](https://pypi.org/project/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:

{% embed https://gist.github.com/danilovmy/71b515e1ca8af0fcfc11017a168c7492.js %}

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

{% embed https://gist.github.com/danilovmy/d290d6e7acb11d2e80498076b0a1b752.js %}

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

{% embed https://gist.github.com/danilovmy/0ba76e176a185f8333d21e05a4b5335d.js %}

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](https://dev.to/danilovmy/how-to-solve-the-singleton-problem-in-django-modeladmin-g42) 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.

{% embed https://gist.github.com/danilovmy/1f7bd9dd076feb2da6ba4c561f1f33c3.js %}

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](https://dev.to/danilovmy/django-admin-dynamic-inline-positioning-3cam) 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.

{% embed https://gist.github.com/danilovmy/ee78fbe9f05383f69c0863cb3a48e3e6.js %}

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 {}
```
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]

```

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)

```

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:

{% embed https://gist.github.com/danilovmy/d68cf48221a50bce62d6fc56a2e2e5f7.js %}

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

![basic nested inline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4kq0vn7a36lcj16k1f8y.PNG)

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](https://code.djangoproject.com/ticket/33394) 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);

```
Not it works better than before.

![inline with js fixed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qveesbj3nokdcovb3icj.PNG)

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

![inline with js fixed, extended](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hueazw1ro8v3ezo4kv9l.PNG)

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