How to use nested formsets in django

Django Formsets manage the complexity of multiple copies of a form in a view. By using formsets you can know how many forms were their initially, which ones have been changed, and which ones should be deleted.

Similar to Forms and Model Forms, Django offers Model Formsets, which simplify the task of creating a formset for a form that handles multiple instances of a model.

Django also provides inline formsets which can be used to handle set of objects belong to common foreign key.

In the below example models, we can write a inline-formset for handling all children for a parent or all addresses of a child.

# models.py

class Parent(models.Model):
    name = models.CharField(max_length=255)

class Child(models.Model):
    parent = models.ForeignKey(Parent)
    name = models.CharField(max_length=255)

class Address(models.Model):
    child = models.ForeignKey(Child)
    country = models.CharField(max_length=255)
    state = models.CharField(max_length=255)
    address = models.CharField(max_length=255)
# forms.py

from django.forms.models import inlineformset_factory

ChildrenFormset = inlineformset_factory(models.Parent, models.Child, extra=1)
AddressFormset = inlineformset_factory(models.Child, models.Address, extra=1)
 

By using above formsets you can handle all children for a parent in one page and can handle all addresses of a child in other page. But if you want to allow the users to add/edit all children along with addresses, all in a single page, then in this case, you should have a complete Address formset for each child form in Child Formset.

Here comes the point of using nested formsets. The nested formset is normal inline-formset. The below steps will help you to handle nested formsets.

Step 1: Create basic inline-formset

# forms.py

from django.forms.models import BaseInlineFormSet

class BaseChildrenFormset(BaseInlineFormSet):
    pass

ChildrenFormset = inlineformset_factory(models.Parent,
                                        models.Child,
                                        formset=BaseChildrenFormset,
                                        extra=1)

Step 2: Attach a nested formset for each form same as below. The supe class 'BaseInlineFormSet' defines 'add_fields' method which is responsible for adding the fields for each form in a formset. So, here we can write logic to associate a nested formset.

# forms.py
class BaseChildrenFormset(BaseInlineFormSet):

    def add_fields(self, form, index):
        super(BaseChildrenFormset, self).add_fields(form, index)

        # save the formset in the 'nested' property
        form.nested = AddressFormset(
                        instance=form.instance,
                        data=form.data if form.is_bound else None,
                        files=form.files if form.is_bound else None,
                        prefix='address-%s-%s' % (
                            form.prefix,
                            AddressFormset.get_default_prefix()),
                        extra=1)

   * Here we have created a new property called "form.nested" that hold the nested-formset(AddressFormset).

Step 3: Handle formset and nested formsets in views

# views.py

def manage_children(request, parent_id):
    """Edit children and their addresses for a single parent."""

    parent = get_object_or_404(models.Parent, id=parent_id)

    if request.method == 'POST':
        formset = forms.ChildrenFormset(request.POST, instance=parent)
        if formset.is_valid():
            formset.save()
            return redirect('parent_view', parent_id=parent.id)
    else:
        formset = forms.ChildrenFormset(instance=parent)

    return render(request, 'manage_children.html', {
                  'parent':parent,
                  'children_formset':formset})

Step 4: Display nested formset in template


# manage_children.html (Just formset display part)

{{ children_formset.management_form }}
{{ children_formset.non_form_errors }}

{% for child_form in children_formset.forms %}
    {{ child_form }}

    {% if child_form.nested %}
        {{ child_form.nested.management_form }}
        {{ child_form.nested.non_form_errors }}

        {% for nested_form in child_form.nested.forms %}
            {{ nested_form }}
        {% endfor %}

    {% endif %}

{% endfor %}

Here there are few cases that need to be handled:

    1. Validation - When validating a form in the formset, we also need to validate its sub-forms which are in nested formset.

    2. Saving data - When saving a form, additions/changes to the forms in the nested formset also need to be saved.

When the page is submitted, then we call formset.is_valid() to validate the forms. We override is_valid on our formset to add validation for the nested formsets as well.

# forms.py

class BaseChildrenFormset(BaseInlineFormSet):
    ...

    def is_valid(self):
        result = super(BaseChildrenFormset, self).is_valid()

        if self.is_bound:
            for form in self.forms:
                if hasattr(form, 'nested'):
                    result = result and form.nested.is_valid()

        return result

By this validating forms and nested formsets completes. Now we need to handle saving. So for that we need to override save method for saving the parent formset and all nested formsets.

# forms.py

class BaseChildrenFormset(BaseInlineFormSet):
    ...

    def save(self, commit=True):

        result = super(BaseChildrenFormset, self).save(commit=commit)

        for form in self.forms:
            if hasattr(form, 'nested'):
                if not self._should_delete_form(form):
                    form.nested.save(commit=commit)

        return result

The save method is responsible for saving the forms in the formset, as well as all the forms in nested formset for each form.

To Know more about our Django CRM(Customer Relationship Management) Open Source Package. Check Code

    By Posted On
SENIOR DEVELOPER at MICROPYRAMID

Need any Help in your Project?Let's Talk

Latest Comments
Related Articles
Understanding django serializers with examples Vamsi Popuri

Serializers are used for “translating” Django models into other formats like xmi,json,yaml(YAML Ain’t a Markup Language)

from django.core import serializers
data = serializers.serialize("xml", SomeModel.objects.all())

Continue Reading...
Check test coverage in code with Coveralls Swetha Naretla

Coverage: It is a tool used for measuring the effectiveness of tests, showing the percentage of your codebase covered by tests.
Test Coverage is an ...

Continue Reading...
Django efficient implementation of Amazon s3 and Cloudfront CDN for faster loading. Chaitanya Kattineni

Django by default to store the files in your local file system. To make your files load quickly and secure we need to go for ...

Continue Reading...
open source packages

Subscribe To our news letter

Subscribe and Stay Updated about our Webinars, news and articles on Django, Python, Machine Learning, Amazon Web Services, DevOps, Salesforce, ReactJS, AngularJS, React Native.
* We don't provide your email contact details to any third parties