Django Nested Inline. Save Data.
Tags: python, django, tutorial
Published: 2022-08-31 17:26:43

_This article is part 2 in a 3-part series on creating nested inlines in the Django admin panel._

1. [Nested inline rendering](https://dev.to/danilovmy/django-nested-inline-redering-4pfg). 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.

On the journey to a working nested inline in this article I will touch on the issues of form prefixes, validating a custom field, saving a custom field and form encoding's problem.

If you are want a quick code example [click here](https://github.com/wP-soft-GmbH/django_admin_nested_inlines).

## Form prefixes

First, we need to handle the form prefixes so that our data is sent in the correct form.

Djangos inline has a `prefix` associated with it, each new line gets its own `id`, set as follows `prefix-*index*`. I thought a reasonable solution for nested-inline would be to handle the prefix of the nested-forms as follows: 

```
parentPrefix_parentIndex_childPrefix-childIndex
```

In the end it should resemple this stucture:

```
>ParentPrefix-0
>>ParentPrefix_0_ChildPrefix-0
>>ParentPrefix_0_ChildPrefix-1

>ParentPrefix-1
>>ParentPrefix_1_ChildPrefix-0
>>ParentPrefix_1_ChildPrefix-1
```

We update the inline-formset prefix with the prefix of the parent form. This can be done in the `image_inline` method, but first we need to get the prefix of the parent form from somewhere. We can set the `form` attribute in instance inside the `__init__` method of that form itself.

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

So we get the 'prefix' from the parent form to generate the 'prefix' of the child forms.


## Data validation

Django handles validation of the main form and its own inlines on its own, but validation of nested inlines is not yet supported.

Since validation happens in ModelForm, we need to look there to implement validation for our nested-inlines.

While I was trying different methods, I realized that it would be much easier to implement the nested-inline itself in the form as follows.

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

We see that inside the `is_valid` method calls `self.nested.formset.is_valid()`. `Self.nested` is an attribute that keeps the link to the nested inline.

- First is created an instance of *Product*ModelAdmin.
- After that, the `_create_formsets` method is called. This method returns the corresponding form sets and their instances depending on whether `change_view` or `add_view` was requested.
- After `get_inline_formsets` returns the nested inlines of that form.
- At the end the prefix is handled and the finished inline is returned.

We set the `request` attribute of `ShopModelAdmin` in the `render_change_form` method before. Now, that's not enough. If we want to have the `request` attribute for both **GET** and **POST** requests, we need to set the attribute in `changeform_view`, because this method is called earlier and is independent of the method of request.

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

Now that the nested inline is processed in the form, we can shorten the code of the original `image_inline` method.

# save method

Let's wrap the `save_form` method of `ShopModelAdmin` to also save nested-inlines.

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

A form calls its save method only when 2 conditions are met. 
 - `form.is_valid()` must be **True**.
 - `form.has_changed()` must be **True**. 
We've already handled the is_valid method. Now we need to change the `has_changed` method. It should check if our nested data has changed. We also need to specify, which fields have changed so that the form can save the changed data in the model.

Fortunately, we can do everything in one `changed_data` property.

{% embed https://gist.github.com/danilovmy/f141dffcf7e8dcc885feefce6dcf3386.js %}
_Of course it should be a on-line generator and not the two for's_ 

Since our nested-inline is defined as a **read-only** field, the `changed_data` property will nominally ignore all changes in nested-inline, so we need to add the modified fields of nested- inline to the other modified fields.

Right now we have a working nested-inline for most Use-cases. But I discovered an exception. If our nested-inline contain any `FileField`, the `request.POST` processing breaks. It took me a long time to figure out, why it worked with other fields and not with the FileField. The answer lies in the attribute enctype="" of the HTML-form.

## Form encoding

There are two types of enctype attribute values for our purposes:

- "application/x-www-form-urlencoded"
- "multipart/form-data"

This attribute determines how the browser sends the HTML form data.

> The `application/x-www-form-urlencoded` enctype creates a simple long string of name and value pairs. These pairs are separated by `&`, and the name and value are separated by `=`.

> The `multipart/form-data` enctype define a 'delimeter' in the Conent-Type header. Each pair of values is separated by this boundary formatted as: `--<boundary_value>`. Then comes `Content-Disposition: form-data; name="<field_name>"`. After that, the value of the field is set. After that comes another boundary.
> Final `--` closes the body of the query.

It is not possible to send files with the `application/x-www-form-urlencoded`. This enctype is best used for most web forms with short alphanumeric values.

The `multipart/form-data`, as already mentioned, is used when we want to send files of any type.

But why do we need two arts of enctypes for HTML forms?

The answer lies in the speed and size of the request. The request without data with `application/x-www-form-urlencoded` enctype is about 6 times faster and incredibly smaller than the same request in `multipart/form-data` enctype.

Why is this important for our nested-inline? If we have a `FileField` in a nested-inline, by default the form does not recognize it, because we have nested-inline as a read-only field.  This means that the nested-inline will not send the content of the FileField. The other fields will be handled normally.

To fix this, we need to wrap the `is_multiform` method of our form so that it also calls the `is_multiform` method of the nested inline.

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

Why not use `multipart/form-data` enctype all the time? Yes, you can do that, and it will work. But the query size increases significantly with each field you add to the form. That's just unnecessary overhead.

For more information on the encoding of HTML forms I would recommend [this article](https://dev.to/sidthesloth92/understanding-html-form-encoding-url-encoded-and-multipart-forms-3lpa) about it and [this discussion](https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data) on Stackoverflow.

## Limitations

This implementation of nested inlines is not without its flaws. This is just an example of how to approach creating nested inlines.

With a little more time and determination, it is possible to implement general inline support in the Django-inlines themselves, which would make creating nested-inlines pretty easy.

In this solution, we assume that an inline is already defined in ProductModelAdmin. But that may not be the case. We can add a check to see if ModelAdmin has an inline.

Multiple inlines with multiple nested.inlines are not implemented in this solution. The `nested` method returns only the first inline from ProducModelAdmin. This is solvable by returning a list of all ModelAdmin inlines.

As you can see, this is not the final answer to the question of creating nested inlines, but this is the working solution that you can use in your projects.

## Next Article

Saving data is now possible in a nested inline, but you may notice that it still cannot be used in the django admin panel. This is a result of the mess the django developers created in inline.js.

We'll solve this problem in the next article.

_Written by Martin Achenrainer_

PS: 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 a working environment.