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:

Overview

FormSet’s are a powerful Django CMS feautre 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 to have them that is to have a button where you can let your users add the required numbers of forms when displaying the template.

There is a very popular approach of cloning a div based in this StackOverflow answer, using Javascript and cloning manually a previous form.

There is an easier approach that will be covering here.

Understanding

Form Skel

Django Formset’s 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 and to easily replace the __prefix__ part with the current order with Javascript.

FormSet special attrbs

When adding and removing a form dynamically we should also address 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 included rendering {{ my_formset.management_form }} in 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 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 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 button, it will copy the skeleton form, replace the __prefix__ placeholder with the right position and update the total number of forms so Django validation knows how many forms we send.

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

·