How to use nested formsets in django

Reading Time : ~ .

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.

    By Posted On
SENIOR DEVELOPER at MICROPYRAMID

Need any Help in your Project?Let's Talk

Latest Comments
Related Articles
Export html web page to pdf using jspdf Vidyasagar Rudraram

jsPDF is used to generate pdf files in client-side Javascript. You can find the links for jsPDF here and also you can find the link ...

Continue Reading...
Preserve file names with sorl for better SEO Ashwin Kumar

We use sorl-thumbnail for scaling images by keeping the original one intact. Sorl proven to be great tool for generating different sized images throughout website. ...

Continue Reading...
Integration of 2checkout with django Nikhila Mergu

Payment Gateways which facilitate communication within banks and Security is an integral component of all payment gateways, as sensitive data such as Credit Card Numbers ...

Continue Reading...

Subscribe To our news letter

Subscribe to our news letter to receive latest blog posts into your inbox. Please fill your email address in the below form.
*We don't provide your email contact details to any third parties