A step-by-step tutorial for setting up and testing a standard Django formset.
I’ve noticed on #django IRC that many people need guidance on formsets, and as I’ve now used them in a couple of my projects — most recently, Connect — I thought I could offer a short how-to based on my own experiences.
Firstly, if you haven’t already, go and read the docs. If you’re still confused, or want an end-to-end tutorial, then read on. The code contained in this tutorial has been tested to work with Django 1.7.
Contents
What Does a Formset Do?
Formsets are for dealing with sets of identical data. For example in Connect, I have a form where the user can save multiple links to their public profile, with each link having both a URL and an anchor:
I also want:
The formset to be nested within the user’s profile form.
The user to add or remove as many links as they like.
Custom validation checking that no anchor or URL is entered more than once.
This how-to, however, is going to focus on creating a standard formset using custom forms.
Step 1. Create Your Forms
First we need to set out our link form. This is just a standard Django form.
forms.py
As our formset will need to be nested inside a profile form, let’s go ahead and create that now:
forms.py
Step 2. Create Your Formset
For this particular example, we’re going to add some validation to our formset, as we want to ensure that there are no duplicate URLs or anchors.
We also want to verify that all links have both an anchor and URL. We could simply set the fields as required on the form itself, however this will prevent our users from submitting empty forms, which is not the behaviour we’re looking for here. From a usability perspective, it would be better to simply ignore forms that are completely empty, raising errors only if a form is partially incomplete.
If you don’t want any custom validation on your formset, you can skip this step entirely.
forms.py
Step 3. Hook Up Your View
Now we can use Django’s built in formset_factory to generate our formset.
As the name suggests, this function takes a form and returns a formset. At its most basic, we only need to pass it the form we want to repeat - in this case our LinkForm. However, as we have created a custom BaseLinkFormSet, we also need to tell our factory to use this instead of using Django’s default BaseFormSet.
In our example, we also want our formset to display all of the existing UserLinks for the logged in user. To do this, we need to build a dict of our user’s links and pass this as our initial_data.
To save our data we can build a list of UserLinks and save this to the user’s profile using the bulk_create method. Wrapping this code in a transaction will avoid a situation where the old links are deleted, but the connection to the database is lost before the new links are created.
We are also going to use the messages framework to tell our users whether their profile was updated.
views.py
Step 4. HTML / JS
Now that we have passed our formset to our template, we can use a forloop to
display each of our forms.
An additional (but not necessarily obvious) step here is to include {{ link_formset.management_form }}. This is used by Django to manage the forms within the formset.
My personal preference is to individually specify each form field so I can wrap additional HTML around it, but you can also use the standard shortcuts, such as {{ form.as_p }} within a formset.
We also want to use this jQuery plugin for dynamically adding and removing forms. Full documentation can be found here.
edit_profile.html
Unit Testing
Let’s set up some basic unit tests to make sure everything is working correctly.
As the profile form is available only to authenticated users, we’ll use the setup method to create and login a user. In the examples below I’ve used factory boy to generate a dummy user.
Most of the examples below are variations on posting the same data either to the view or the form directly. For this reason, much of this functionality has been split into separate helper functions.
Test the Profile Form
We can test the ProfileForm by passing data variations to the object and checking for validation errors.
tests/test_forms.py
Test the Formset
We can test our formset by either:
Passing data to the ProfileForm (for this to work we must include the TOTAL_FORMS and INITIAL_FORMS settings that are generated by the management_form).
Posting data directly to the view. This allows us to check for specific errors using assertFormsetError.
tests/test_forms.py
Testing Our View
Finally, we need to check that when we do submit valid data, that data is saved to our user’s profile.
tests/test_views.py
Conclusion
That’s it! We have a working tested formset saving our user’s links. If you found this article useful, please share it. If you have a comment or question, please get in touch!
The course is a step-by-step exploration of what it takes to build, test and launch a full Django application. The first six videos are available for free, so go and check it out!
6th
January,
2015
More Articles
Contact Me
Get In Touch!
I am available for consulting via Kabu Creative. To discuss a particular project, please contact me at n.harris [at] kabucreative.com.