Django inline formsets with Class-based views and crispy forms

Xenia on February 13, 2019

Recently I used inline formsets in one of my Django projects and I liked how it worked out very much. I decided to share my example of integratio... [Read Full]
markdown guide

Hey Xenia,

thank you for this helpful post. That's exactly what I was looking for. I've applied a small modification to make your solution a bit more crispy ;-)

Instead of putting all the form layout stuff into the file formset.html it would a better solution to add a LayoutHelper to the CollectionTitleForm:


from django import forms
from .models import Collection, CollectionTitle
from django.forms.models import inlineformset_factory
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Fieldset, Div, Row, HTML, ButtonHolder, Submit
from .custom_layout_object import Formset

import re

class CollectionTitleForm(forms.ModelForm):

    class Meta:
        model = CollectionTitle
        exclude = ()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        formtag_prefix = re.sub('-[0-9]+$', '', kwargs.get('prefix', ''))

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.layout = Layout(

CollectionTitleFormSet = inlineformset_factory(
    Collection, CollectionTitle, form=CollectionTitleForm,
    fields=['name', 'language'], extra=1, can_delete=True

class CollectionForm(forms.ModelForm):

    class Meta:
        model = Collection
        exclude = ['created_by', ]

    def __init__(self, *args, **kwargs):
        super(CollectionForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = True
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-3 create-label'
        self.helper.field_class = 'col-md-9'
        self.helper.layout = Layout(
                Fieldset('Add titles',
                ButtonHolder(Submit('submit', 'Save')),

Then you can simplify your formset.html

{% load crispy_forms_tags %}
{% load staticfiles %}

<style type="text/css">
  .delete-row {
    align-self: center;

{{ formset.management_form|crispy }}

{% for form in formset.forms %}
  {% for hidden in form.hidden_fields %}
    {{ hidden|as_crispy_field }}
  {% endfor %}
  {% crispy form %}
{% endfor %}

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'mycollections/libraries/django-dynamic-formset/jquery.formset.js' %}"></script>
<script type="text/javascript">
    $('.formset_row-{{ formset.prefix }}').formset({
        addText: 'add another',
        deleteText: 'remove',
        prefix: '{{ formset.prefix }}',

The advantage is that the layout is now modified by crispy_forms applying the selected template pack (e.g. bootstrap4). Only the CSS .delete-row must be secified to center the remove button of the django-formset plugin because only a link is added if the containing element is not a HTML-table.

Best regards,


Thanks for your contribution! I merged it into a new branch :)


Hey thanks a ton for this tutorial, im still quite new to django so I was wondering how would I go about adding a validation so at least one CollectionTitle is required for each Collection, thanks alot!


So I've figured out how to add validation for the CollectionTitle, but im doing it through views.py. Wondering if anybody knows how to add the validation to client side instead of going through views, thanks.

How I am doing it at the moment is adding a min_num and validate_min to the inlineformset_factory.


CollectionTitleFormSet = inlineformset_factory(
    Collection, CollectionTitle, form=CollectionTitleForm,
    fields=['name', 'language'], can_delete=True, min_num=1, validate_min=True, extra=0

And by setting an else function in the form_valid. I save the object after validating titles since my project requires at least one title for each collection.


class CollectionCreate(CreateView):
#kept rest the same till form_valid

def form_valid(self, form):
        context = self.get_context_data()
        titles = context['titles']
        with transaction.atomic():
            form.instance.created_by = self.request.user
            if titles.is_valid():
                self.object = form.save()
                titles.instance = self.object
                context.update({'titles': titles})
                return self.render_to_response(context)
        return super(CollectionCreate, self).form_valid(form)

Great code! have been looking all day :/
This works for me

    def form_valid(self, form):
        context = self.get_context_data()
        subunits = context['subunits']
        with transaction.atomic():
            if subunits.is_valid():
                self.object = form.save()
                subunits.instance = self.object
                return super(UnitCreateView, self).form_valid(form)
                return self.render_to_response(self.get_context_data(form=form))


How would you do this if you had another "inline_formset" inside of each title?
This should be done using nested formsets but you had to basically do the same thing you did for the titles but inside each title.

Does that make sense to you?

Hit me up with your thoughts.



Hi Gonçalo,

Thanks for the question!
You are right, you will need to use nested form inside each form in formset.
For this you need to create CollectionTitleChildFormSet and pass it in add_fields() method when overriding BaseInlineFormSet. Therefore the CollectionTitleFormSet will look different because now we need to set formset explicitly to BaseTitleChildFormset (check out my commit and this blog post).
The backend logic works fine for me but I didn't figure out how to implement dynamic 'add child': it should work like this - everytime I add a new Title the row with an empty Title and a row with at least One Child followed by button 'add child' is added.
If you figure it out I will be interested to know how ;)


Thanks for the quick reply!

I have my backend working as well. I believe that the jQuery library (django-dynamic-formset) is not prepared for this.
What you're doing with this library on the nested formsets is creating 1 formset with all the childs, and I believe this should create 1 formset with the childs on each title.
I also think your childs' prefix is wrong, let me know what prefix appears on the class when you use "formset_child-(( formset.prefix ))". I'm personally using formset_child-(( nested_form.prefix )) and it joins the formset (title) prefix with the childs' prefix, something like title-0-child-0, title-0-child-1.

I will spend the rest of the day trying to fix this, I'll let you know how it goes.


I really appreciated your article! I was looking at the terrible django-crispy doc, trying to also understand djang formset and I was at lost. Then I randomly stumbled on your article and everything clicked in. You resumed everything in a very educational way what others couldn't explain through pages of very unhelpful explanations. Thank you!


Can you please share how to do the same thing without using crispy forms?
I have tried to do the same and createview is working fine but in UpdateView unable to save the changes made in inline_formset. It save the data when adding new line.
please help me out.


Hi Sachin,

To implement it without crispy forms check out this blog, the solution is not using crispy forms. The UpdateView is essentially the same as CreateView except you need to pass instance (instance=self.object) in get_context_data() because the instance already exists in the database (the code part for this is here ). Hope it helps!



I got some bug in this code.please help me to find out.The bug is

ValueError: Cannot assign ">": "Collection.created_by" must be a "User" instance.


Hey elisa,

I think you have to call localhost:8000/admin and log in as user "admin" and password "admin". If you take a look into the classes CollectionCreate or CollectionUpdate inside views.py then you can see that the created_by field is always set to the user who executed the request. And the variable request.user is only set, if a user is logged in.

Hope that will help you to get the demo running ;-)
Best regards,


Hello Xenia, thank you for this post. It is the closest I've come to solvingmy problem in a week! Am working on a project where I want Users to upload up to 3 images for each post they make. So, I need your CollectionTitle model to have just the foreign key field to the Post model and the image field with at least 1 image. Am using Crispy forms and CBVs with LoginRequiredMixin and UserPassesTestMixins. Any help would be much appreciated.


I'm having trouble getting the form to display both the formset fields and the regular form fields. Currently, the template is only displaying the formset fields.

I think I've narrowed it down to something I didn't do right in either the custom_layout_object.py or the formset.html.

I know that's vague, but any advice/tips on what I might need to change from your example to work with my project?


I figured out both the problems I was having! The first one (above) I had improperly configured the self.helper.layout div so it didn't know what to display! Everything seems to be working now in terms of the basic setup.

I was wondering though if you could advise me on how to make the form look better? I've messed with the formset.html but can't seem to figure out where to put formatting to make it look nice.

This is what my form currently looks like:


I was able to get this working flawlessly with the CreateView. I am having an issue with the UpdateView though. I cannot get the related items to update although there are no errors or issues thrown anywhere when submitted. Thank you


Thank you for this helpful post! I was able to get this working in my project with relative ease. Does anyone know of a quick fix for the bootstrap datepicker not working in a datetime field in the formset?


Hey Xenia,

Could you please help me with the following situation:
Assume your model CollectionTitle now have 1 more field
user = models.ForeignKey(User, on_delete=models.CASCADE)

Now, any user can add title and language.
When a user enters title and language and submits the form, auto-save the user field to the currently logged in user.

For example, User A created a new Collection. Now, User B and User C can add titles for this new created Collection. When user B adds a title and language, the user should be auto-saved to this entry along with the title and language.

Thanks in advance for all the support and help.

Amey Kelekar


The models Collection and CollectionTitle made the code difficult to follow. I suggest choosing more distinct names to make it easier.


Hi Xenia and public,

I just implemented this but cant figure out why I'm getting "remove" buttons for each field of the formset. Also I can't customize the "Add another" or "delete" texts of the buttons. I have attached a photo of my result. Anyone any idea?


Thank you so much for this tut. How can i limit the number of forms in the form set that can be added?


When you create you inline formset you can limit the number of forms in the formset with max_num parameter


CollectionTitleFormSet = inlineformset_factory(
    Collection, CollectionTitle, form=CollectionTitleForm,
    fields=['name', 'language'], extra=1, max_num=3, can_delete=True

Thank you so much! You saved my day... no, a WEEK


Thank you very much great post , much appreciated


Hi! Thanks Xenia. A quick question - why do you use with transaction.atomic(): in your form_valid() function?



That guarantees the transaction's atomicity, which is a database property.
Take a look at the explanation in Django docs


There is something missing about the usage of the jquery plugin. I do not see the 'add' and remove' buttons added to the html at my end.

code of conduct - report abuse