By continuing to navigate on this website, you accept the use of cookies to serve you more relevant services & content.
For more information and to change the setting of cookies on your computer, please read our Cookie Policy.

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
Django Generic many to many field implementation

Django application has a GM2MField that combines the features of the standard Django "ManyToManyField" and "GenericForeignKey".

Installation:
pip install django-gm2m

Continue Reading...
Understanding Django Permissions And Groups

Django comes with a simple permissions system. It provides a way to assign permissions to specific users and groups of users. We can have permissions ...

Continue Reading...
How to monitor django application live events with sentry

Let us now learn how to track exceptions and events in SENTRY. We will setup browser and server end tracking for Django Project.

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