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

Posted On 08 May 2017 By MicroPyramid


Need any Help in your Project?Let's Talk

Latest Comments
Related Articles
Introduction to API development using Django REST framework with Example

Introduction to API development with Django REST framework. You can build the API for any Django application. Pre-requisites are Django and OOPS(object oriented programming concepts) ...

Continue Reading...
Django Template language Intro

We can write the required logics based on programming[python] syntax in models and views but, when we want to write simple logics we should follow ...

Continue Reading...
Django-REST Framework Object Level Permissions and User Level Permissions

Django-REST User Level Permissions and Object Level Permissions. User Level Permissions and Object level Permissions allow to serve customers based on their access levels or ...

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