Django formsets allow developers to handle multiple forms in a single request. This can be useful when you want to create, update, or delete multiple objects at once. In this article, we will discuss how to use formsets in Django, and provide some examples to help you get started.
Before we dive into the details, let's first define what a formset is. A formset is a collection of forms that share the same structure and can be processed together. Each form in the formset represents an instance of a model, and the formset as a whole is used to handle multiple instances of that model.
Creating a Formset
To create a formset in Django, you first need to define a form that represents a single instance of your model. For example, if you have a model called Person
, you might define a form like this:
from django import forms
from .models import Person
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ['first_name', 'last_name', 'email']
Next, you need to import the formset_factory
function from the django.forms
module:
from django.forms import formset_factory
Then, you can create a formset by calling the formset_factory
function, passing in the form you just defined and the number of forms you want to create:
PersonFormSet = formset_factory(PersonForm, extra=2)
In this example, we've created a formset called PersonFormSet
that contains two forms.
Displaying a Formset
To display a formset in a template, you can use the formset
variable that is automatically created by Django when you pass the formset to the context:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form.as_p }}
{% endfor %}
<input type="submit" value="Save">
</form>
In this example, we've used a for
loop to iterate over each form in the formset and display it using the as_p
method, which renders the form as a series of paragraphs.
The management_form
variable is a special variable that is required by Django to handle formsets correctly. It contains hidden fields that Django uses to keep track of the forms in the formset.
Processing a Formset
To process a formset in a view, you need to check whether the request is a GET or a POST request. If it's a GET request, you simply create a new instance of the formset and pass it to the template. If it's a POST request, you need to validate the formset and save the data if it's valid.
Here's an example view that processes a formset:
from django.shortcuts import render
from .forms import PersonFormSet
def my_view(request):
if request.method == 'POST':
formset = PersonFormSet(request.POST)
if formset.is_valid():
formset.save()
else:
formset = PersonFormSet()
return render(request, 'my_template.html', {'formset': formset})
In this example, we've created a view called my_view
that checks whether the request is a GET or a POST request. If it's a GET request, we create a new instance of the formset and pass it to the template. If it's a POST request, we validate the formset using the is_valid
method and save the data using the save
method if the formset is valid.
Deleting Objects with a Formset
In addition to creating and updating objects, formsets can also be used to delete objects. To do this, you need to add a BooleanField
to the form that represents each object, and set its widget
to forms.HiddenInput
to hide it from the user.
Here's an example:
class PersonForm(forms.ModelForm):
delete = forms.BooleanField(required=False, widget=forms.HiddenInput)
class Meta:
model = Person
fields = ['first_name', 'last_name', 'email', 'delete']
In this example, we've added a BooleanField
called delete
to the PersonForm
class. We've set its required
attribute to False
so that it doesn't need to be filled out, and its widget
attribute to forms.HiddenInput
so that it's hidden from the user.
To delete objects with a formset, you need to loop over the forms in the formset and delete any objects that have the delete
field set to True
. Here's an example view that does this:
from django.shortcuts import render
from .forms import PersonFormSet
def my_view(request):
if request.method == 'POST':
formset = PersonFormSet(request.POST)
if formset.is_valid():
for form in formset:
if form.cleaned_data.get('delete'):
form.instance.delete()
else:
form.save()
else:
formset = PersonFormSet()
return render(request, 'my_template.html', {'formset': formset})
In this example, we've added a loop that iterates over the forms in the formset. For each form, we check whether the delete
field is set to True
using the cleaned_data
attribute. If it is, we delete the corresponding object using the delete
method. If it's not, we save the data using the save
method as before.
Using Inline Formsets
In some cases, you may want to use a formset to edit related objects. For example, if you have a model called Person
and a related model called PhoneNumber
, you might want to use a formset to edit the phone numbers for each person.
To do this, you can use an inline formset. An inline formset is a formset that is embedded in a parent form, and is used to edit related objects. To create an inline formset, you need to use the inlineformset_factory
function instead of the formset_factory
function.
Here's an example:
from django.forms import inlineformset_factory
from .models import Person, PhoneNumber
PhoneNumberFormSet = inlineformset_factory(
Person, PhoneNumber,
fields=['number'],
extra=1, can_delete=True
)
In this example, we've created an inline formset called PhoneNumberFormSet
that is used to edit the phone numbers for each person. We've passed in the Person
model and the PhoneNumber
model as arguments, and specified the fields that we want to include in the formset using the fields
argument.
We've also specified that the formset should include one extra form using the extra
argument, and that it should allow objects to be deleted using the can_delete
argument.
To display an inline formset in a template, you can use the formset
variable as before, but you also need to include the parent form in the template. Here's an example:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
{{formset.management_form }}
{% for form in formset %}
{{ form.as_p }}
{{ formset.empty_form.as_p }}
{% endfor %}
<button type="submit">Save</button>
</form>
In this example, we've included the parent form using the {{ form.as_p }}
template tag, and the inline formset using a loop that iterates over the forms in the formset. We've also included the formset.management_form
template tag to handle the management of the formset.
To process the form data in the view, you need to pass both the parent form and the formset to the view, and use the save
method on both. Here's an example:
from django.shortcuts import render
from .forms import PersonForm
from .models import Person, PhoneNumber
from .forms import PhoneNumberFormSet
def my_view(request):
person = Person.objects.get(id=1)
if request.method == 'POST':
form = PersonForm(request.POST, instance=person)
formset = PhoneNumberFormSet(request.POST, instance=person)
if form.is_valid() and formset.is_valid():
form.save()
formset.save()
else:
form = PersonForm(instance=person)
formset = PhoneNumberFormSet(instance=person)
return render(request, 'my_template.html', {'form': form, 'formset': formset})
In this example, we've passed both the parent form and the formset to the view, and used the instance
argument to specify the object that we want to edit. We've also used the is_valid
method to validate both forms, and the save
method to save both forms.
Conclusion
Django formsets provide a powerful way to handle multiple forms in Django. They allow you to create, update, and delete multiple objects at once, and can be used to edit related objects using inline formsets. By understanding how to use formsets, you can save time and reduce the amount of code needed to handle complex form workflows.