Skip to content

Working with nested forms with Django

django-form-website-development-swapps-square

One of the biggest advantages of Django is its ability to create CRUDs applications with no effort. You just need to define your models, register them at the admin and that’s it. If you are not going to follow the admin approach, you may use Django’s class based views to define each of the views you would require to manage the objects in your database. For creating and updating model instances, you do not even require to define a form because CreateView and UpdateView will do it for you. In just a couple of hours, you may have a very stable CRUD application with almost no effort.

However, this awesomeness seems to disappear when we start having relationships between our models; such as foreign keys or one to one relationships. Following the previous approach, It is hard to edit the parent object while editing a child, or to edit several children at once.

Django admin solves this situation using admin inlines. But, what may we do when our app will use its own views to manage our CRUD? Well, we use Formsets. In this blog post, we will check how to use them, along with our class based views, in order to achieve an awesome CRUD with several relationships and with a little amount of code. So, Let’s start!

Setting up our models

For this blog post, I will use two small models. Parent and Child. As their name indicates, a parent may have several children, but a child may only have one parent.

from django.db import models

class Parent(models.Model):
    name = models.CharField(max_length=250)
    class Meta:
        verbose_name = "Parent"
        verbose_name_plural = "Parents"
    def __str__(self):
        return self.name

class Child(models.Model):
    name = models.CharField(max_length=250)
    parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
    class Meta:
        verbose_name = "Child"
        verbose_name_plural = "Children"
    def __str__(self):
        return self.name

Now, let’s add the create view for the parent, where we want to create the related children as well.

from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, UpdateView
from .models import Parent

class ParentListView(ListView):
    model = Parent
class ParentCreateView(CreateView):
    model = Parent
    fields = ["name"]

If we leave it like that, we will not be able to add children for a parent. But if we add child_set to the fields array, a “FieldError: Unknown field(s) (child_set) specified for Parent” will be raised. So, what should we do?

Setting up the forms and the views

In order to create the inline forms, we will use Django’s inline form sets. These kind of form sets allows you to create several child objects from a parent object, all in the same form. These are the base of what Django admin uses when you register inlines instances.

For our example, we are going to go further and be even more abstract. We will use inlineformset_factory method that, in just one line, creates the formset that we need.

from django.forms.models import inlineformset_factory
ChildFormset = inlineformset_factory(
    Parent, Child, fields=('name',)
)

Now, we will update our createView to add the inline formset.

class ParentCreateView(CreateView):
    model = Parent
    fields = ["name"]

    def get_context_data(self, **kwargs):
        # we need to overwrite get_context_data
        # to make sure that our formset is rendered
        data = super().get_context_data(**kwargs)
        if self.request.POST:
            data["children"] = ChildFormset(self.request.POST)
        else:
            data["children"] = ChildFormset()
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        children = context["children"]
        self.object = form.save()
        if children.is_valid():
            children.instance = self.object
            children.save()
        return super().form_valid(form)

    def get_success_url(self):
        return reverse("parents:list")

As you may see, we added the formset to our context in order to be able to render it. As well, on form_valid method, after we save our parent object, we assign it as the instance of the formset, we check its validity and save it; creating the children objects as well.

In order to render the form, we may create a parents/parent_form.html file:

<h1>Parents</h1>
<form method="post">{% csrf_token %}
    {{ form.as_p }}
    <h2>children</h2>
    {{ children.as_p }}
    <input type="submit" value="Save">
</form>

This template will look like this:

rendered formset

As you may see, the form not only allows you to add the parent, but also to create three children.

For the update view, we may follow the same approach. The only difference is that we initiate the ChildFormSet object with the instance argument. That way, our formset will be initialized with the children of the parent we are editing.

class ParentUpdateView(UpdateView):
    model = Parent
    fields = ["name"]

    def get_context_data(self, **kwargs):
        # we need to overwrite get_context_data
        # to make sure that our formset is rendered.
        # the difference with CreateView is that
        # on this view we pass instance argument
        # to the formset because we already have
        # the instance created
        data = super().get_context_data(**kwargs)
        if self.request.POST:
            data["children"] = ChildFormset(self.request.POST, instance=self.object)
        else:
            data["children"] = ChildFormset(instance=self.object)
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        children = context["children"]
        self.object = form.save()
        if children.is_valid():
            children.instance = self.object
            children.save()
        return super().form_valid(form)

    def get_success_url(self):
        return reverse("parents:list")

And that’s it! By using just a few methods and objects from Django, we were able to create a nested form to create or edit a parent object and its children at the same time with almost no effort.

I have been working with Django for almost four years and it still manages to amaze me with its simplicity and all the useful tools it provides. For this case, it allows to make a very complex task, that in other cases would require AJAX request or something similar, only using python and pure html.

And this is just a simple case. If you need another level of relationship, for example if your Child model has a Grandchild, you may create a formset that will allow you to edit a Parent, a Child and a Grandchild without using a different approach. You may see an excellent example here.