FormSet’s for to include FormSet’ form of another class.

Im not really sure how to name this topic correctly so let me explain the question in details:

I have some ‘Order’ model as well as ‘OrderItem’ mode linked to ‘Order’ model through FK.
Each ‘OrderItem’ has ‘ConfigSchema’ (FK link as well).

ConfigSchema → OrderItem → Order

Let’s assume I have ‘OrderItemFormset’ renders on Order creation (no troubles with this).

class OrderItemForm(forms.ModelForm):
    category = forms.ChoiceField()

Now I want dynamically render particular ‘ConfigSchemaForm’ depends on category chosen.
So far I’ve managed to pull ‘ConfigSchemaForm’ using Ajax and insert it within ‘OrderItem’ formset.
But now I wondering if there is more native approach as basically each form of ‘ConfigSchemaForm’ may be a formset form as well.

But I can’t figure out how to nest a single formset unit within another single formset unit if another model.
Some kind of Python zip() function

If I’m understanding you correctly, you are effectively looking for a “nested formset” structure, where each element of a formset contains a different formset for that specific item.

If that’s correct, then no - Django does not intrinsically provide this feature.

However, it is a topic that has been discussed here a few times in the past. You can search the forum for “Nested Formset” to find the topics.

A couple that I found quickly:

(There are others - this is a topic that comes up periodically.)

1 Like

Hello, @KenWhitesell !
Thanks for a quick reply.
Was not sure how to form request to search for such topic.
Hoped there is straight solution for this goal but seems it requires some workaround.
Will take a look for links attached for some ideas.

Thank you!

@DoOrQuit I’ve tried my luck with nested formsets and while I made it work, it had to ditch that approach at the very end since my nested forms were not identical (some configurations such as widget types and required). So definitely keep that in mind, for formsets the forms have to be identical.

If you still want to go down that route, one site that helped to understand it was the following: How to use nested formsets in django | MicroPyramid. Pay special attention to BaseInlineFormSet and add_fields.

if you still want to go down that route

Is there more generic way to implement that ?

I can share how I did it and from there you can decide if you want to use nested Formsets or go the manual route. The key, at least for me, was to understand that building the nested forms should ideally happen in forms.py and that request.POST is a simple dictionary and any forms.ModelForm grabs what matches their prefix + fields. So, here a very simplistic example:

class OrderItemForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.nested_forms = []
        self.init_nested_forms()

    def init_nested_forms(self):
        # Add any conditional logic here to determine what
        # nested forms to add, how many to add, passing instances, etc.

        prefix = f"{self.prefix}-nested"
        nested_form = OrderItemForm(data=self.data if self.data else None, prefix=prefix)
        self.nested_forms.append(nested_form)

    def is_valid(self):
        is_valid = super().is_valid()
        is_valid &= all(form.is_valid() for form in self.nested_forms)

        return is_valid

    def has_changed(self):
        has_changed = super().has_changed()
        has_changed |= any(form.has_changed() for form in self.nested_forms)

        return has_changed

    def save(self, commit=True):
        instance = super().save(commit=commit)

        # Define how the nested forms should be saved, e.g.
        # assign the parent instance to the nested form

        for form in self.related_forms:
            form.instance.item = instance
            form.save(commit=commit)

In the HTML it would be basically something like:

{% for subform in form.nested_form %}
    {{ subform }}

    {% for subsubform in subform.nested_form %}
        {{ subsubform }}

       // and so on... Ideally, you would use partial html's ({% include '...' %}) with the "only" keyword to avoid the recursion error.

    {% endfor %}

{% endfor %}

Now, you still have a long way to go from here and deal with all of the many details, but I hope it is a good starting point and you can slowly work you way through.

@KenWhitesell, if you have any thoughts or feedback, it would be much appreciated!

1 Like

Thanks for an example and time spent for posting it. It’s really helpful.

But now I wondering how can I dynamically change which nested form being rendered depending on OrderItem field chosen.

For example :
OrderItem has a category field.
So when user picks some Category then nested form must contain fields related to this category (e.g. this category parameters form ). Usually it’s easily done with empty_form but empty form renders with template with some static dummy data. Even it can be change in view I can’t get how to modify dynamically each OrderItem on category change trigger.

I’ve some feeling that models refactor/re-design will help but still can’t find out the right models design to make this task more generic and simple to scale/maintain.

You are talking about chained selection, right? You render a single ItemForm and based on the selection you want to dynamically create and add the child forms? I solved this by using HTMX that listens to changes to the ParentItemForm. This sends a request that basically re-creates the form populated with request data (the selected fields) and passes it back to the HTML where I then render the nested_forms (as explained in my previous answer).

In the init_nested_forms you define what childForms you want to render. Given that you have access to the data you can check if any of its relevant fields (in your case category) is populated and based on this then create the ChildForms, filtering their querysets, etc.

In Summary:

  • in the CreateView, I start with a single Form
  • I select the product
  • it sends an HTMX request populating the same ItemForm with the request data
  • In the init_nested_forms I create my childForms based on the selection
  • I render it back to the HTML
  • I select a field in any of the ChildForms
  • It sends an HTMX request populating the ChildForm with the request data
  • Rinse and repeat until no ChildForms are left to render or no selection is made.

This works very well in the editView, too btw. since all my ChildForms are created in the init based on the data of the ParentForm so all the recursive magic happens in one place. You have to make sure that your HTML can handle that recursiveness (thats why I mentioned the include with the only keyword) and that you Models have the right constraints so that you don’t end up in a loop - Category A creating Forms for Category B creating Forms for Category A, etc.

1 Like

@0584265c , omg that actually worked for me.
I just took this idea and adapt the code for my goal but this post actually solve the problem I wasn’t expecting to.
As I have just a single nested form for each OrderItem I’ve just made a nested form mapper and single nested form inits within OrderItemForm depends on field value (or default).
And it’s looks scalable as to introduce new product I just have to add new form to the mapper with TextChoices class as a key.

Thanks a lot everyone for help, @KenWhitesell , @0584265c