Django is a Python-based web framework which aims to abstract much of the backend logic so you can quickly mock up a website without delving too much into the weeds. One useful feature of Django is automatic form validation (verifying that the inputs to a form are valid before writing to a database). However, once you get outside of core functionality, you sometimes find the documentation is a bit lacking. One of those areas is dynamic forms.

A dynamic form is a form where there are a variable number of inputs, depending on user selection. One simple example of a dynamic form is a TODO list where the number of items on the list can vary, and we have a checkbox for each item for if it is completed or not. Django handles dynamic forms using “formsets”. You can check out the documentation here: https://docs.djangoproject.com/en/3.0/topics/forms/formsets/

We won’t go over how to implement the Python side of things since this is well documented. Instead we’ll focus on the client-side code. Let’s start by how to render the formset itself using the Django templating system. We’d write something like this:

This should look familiar if you have used forms in Django before. Two new features to notice are the addition of a {{ formset.management_form }}, which keeps track of the total number of forms in the formset (among other things), and the for loop over forms in the formset. We’ve also added a link with id “add_form”, which will add a new copy of our form

As for usual forms, it is possible to break out the individual form elements if we so desire to have greater control over the rendering of the form, but for this example we’ve kept it simple and given the default rendering.

Next, we need some javascript code to handle our link to add a new form (for example, a new item on our TODO list).

(credit to stackoverflow user mpen)

To explain a little bit about what’s going on here, we are grabbing the total number of forms initially in our variable form_count (we can set the number of forms to start with in our Python code). Since forms in our formset are indexed started with zero, our new form will have id number equal to form_count. $(‘#add_form’) selects our “add_form” link, and we are registering a function to a click event on that link. When the user clicks the form, we assign a new form to our “new_form” variable, and append it to the div containing the forms (in this case, the div with id “forms”). It’s worth pointing out that if we wanted to, we could render individual form fields instead by replacing “formset.empty_form” with “formset.empty_form.fieldA”, “formset.empty_form.fieldB”, etc. using whatever field names you have assigned in your Python code. Just use the document.createElement to build the form elements however you like, and append the result to the forms div at the end.

One subtlety here is this “|escapejs” filter. Remember that everything between double curly braces is replaced with text by Django’s templating system before being served to the client. So we use this escapejs filter to escape single quotes and other special characters from our empty form before wrapping the result in single quotes.

It’s also worth explaining what the “replace” is doing here. As we just mentioned, {{formset.empty_form|escapejs}} is rendered as static html: there is no server-to-client interaction to get the current form count, so we are keeping track of this client-side. To account for this, formset.empty_form uses the string “__prefix__” instead of a form id in every location, so it is up to us to replace every instance of “__prefix__” with the appropriate id.

I hope this helps you get started with dynamic forms in Django, and as always, remain compassionate towards others!

Logic PhD transitioning into Data Science

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store