Django forms are one of the most important parts of the stack: they enable us to write declarative code that will validate user input, and ensure we protect ourselves from malicious input.
Formsets are an extension of this: they deal with a set of homogeous forms, and will ensure that all of the forms are valid independently (and possibly do some inter-form validation, but that’s a topic for a later day).
The Django Admin contains an implementation of a dynamic formset: that is, it handles adding and removing forms from a formset, and maintains the management for accordingly. This post details an alternative implementation.
A Formset contains a Form (and has zero or more instances of that Form). It also contains a “Management Form”, which has metadata about the formset: the number of instances of the form that were provided initially, the number that were submitted by the user, and the maximum number of forms that should be accepted.
A Formset has a “prefix”, which is prepended to each element within the management form:
<input type="hidden" name="prefix-INITIAL_FORM_COUNT" value="..."> <input type="hidden" name="prefix-TOTAL_FORM_COUNT" value="..."> <input type="hidden" name="prefix-MIN_NUM_FORM_COUNT" value="..."> <input type="hidden" name="prefix-MAX_NUM_FORM_COUNT" value="...">
Each Form within the Formset uses the prefix, plus it’s index within the list of forms. For instance, if we have a Formset that contains three forms, each containing a single “name” field, we would have something similar to:
<input type="text" name="prefix-0-name" value="Alice"> <input type="text" name="prefix-1-name" value="Bob"> <input type="text" name="prefix-2-name" value="Carol">
Note that the form’s prefix is
To make a Formset dynamic, we just need to be able to add (and possibly remove, but there’s a little more complexity there) extra forms. The managment form needs to be updated to reflect this, and we need to ensure that the new form’s fields are named appropriately.
A Formset also contains an
empty_form. This is an unbound form, where the form’s “index” is set to
__prefix__. Thus, the empty form for the above formset might look somewhat like:
<input type="text" name="prefix-__prefix__-name" value="">
We can leverage this to allow us to have simpler code: instead of having to duplicate elements and remove the value, we can just duplicate the empty form, and replace the string
__prefix__ with whatever the index of the newly created form should be.
Here’s an implementation that has no dependencies, but does make some assumptions: