Dynamically adding forms to a Django FormSet with an add button using jQuery

Add more button in ModelFormSet
Image: Add more button in ModelFormSet (License: CC-BY-SA)
Published:
Last modified:
Tags Django , Form

Overview

FormSet’s are a powerful Django CMS feature that makes it easy to display an arbitrary number of forms of the same form class and then process them all together.

There is a missing functionality that you would naturally expect them to have, that is to have a button where you can let your users add the amount of forms they desire when displaying the template.

There is a very popular approach of cloning a div based in this StackOverflow’s answer, where it uses Javascript to manually clone a previous form present in the webpage.

There is an easier approach that I will be covering here.

Understanding

Form Skel

Django’s Formsets come with a special attribute: empty-form, which returns a form instance with a prefix of __prefix__.

This way we have an empty form ready to be used when displaying formsets where just need to replace the __prefix__ part with the current form position to define its order among all the other forms with Javascript.

FormSet special attrbs

When adding and removing a form dynamically we should also set the following special fields of management formsets:

# special field names
TOTAL_FORM_COUNT = 'TOTAL_FORMS'
INITIAL_FORM_COUNT = 'INITIAL_FORMS'
MIN_NUM_FORM_COUNT = 'MIN_NUM_FORMS'
MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
ORDERING_FIELD_NAME = 'ORDER'
DELETION_FIELD_NAME = 'DELETE'

# default minimum number of forms in a formset
DEFAULT_MIN_NUM = 0

# default maximum number of forms in a formset, to prevent memory exhaustion
DEFAULT_MAX_NUM = 1000

These fields are used in FormSet validation and control how many form instances are being displayed.

So they should also have to change in synchronization with our new added forms

These variables are used when using {{ my_formset.management_form }} to render our form.

When we send POST data, we should include these variables to avoid receiving a django.forms.utils.ValidationError: ['ManagementForm data is missing or has been tampered with'] validation error, for example, for a form myform where we added just one form, POST data would look like:

data = {
  # each form field data with a proper index form
  'myformset-0-raw': 'my raw field string',
  
  # form status, number of forms
  'myformset-INITIAL_FORMS': 1,
  'myformset-TOTAL_FORMS': 2,
}

Adding to a Formset

This is the actual code we need to insert in the template:

<h3>FormSet example</h3>
{{ myformset.management_form }}
<div id="form_set">
    {% for form in myformset.forms %}
	    {{form.non_field_errors}}
		{{form.errors}}
        <table class='no_error'>
            {{ form }}
        </table>
    {% endfor %}
</div>
<input type="button" value="Add More" id="add_more">
<div id="empty_form" style="display:none">
    <table class='no_error'>
        {{ myformset.empty_form }}
    </table>
</div>

Here we are including the number of forms we have in {{myformset.management_form}}, then looping over all of the forms we have in the formset myformset.forms, display the button to add more forms, and finally have the skeleton for new forms hidden.

Then adding the button with the following Javscript code:

$('#add_more').click(function() {
	var form_idx = $('#id_form-TOTAL_FORMS').val();
	$('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
	$('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
});

Each time we press the Add button, it will copy the skeleton form, replace the __prefix__ placeholder with the right form position and update the total number of forms so Django validation knows how many forms we are sending.

Possible Errors

ReferenceError: $ is not defined

If you get ReferenceError: $ is not defined in your browser’s console, then it is due having defined the script before loading JQuery, just load the javascript code after inserting the JQuery library.

References

Uruguay
Marcelo Canina
I'm Marcelo Canina, a developer from Uruguay. I build websites and web-based applications from the ground up and share what I learn here.
comments powered by Disqus


Guide to have a button in a Django template, that display forms being part of a FormSet

Clutter-free software concepts.
Translations English Español

Except as otherwise noted, the content of this page is licensed under CC BY-NC-ND 4.0 . Terms and Policy.

Powered by SimpleIT Hugo Theme

·