Working with Formsets in Django
This was the specs:
- Form that is populated with rows of students: their first and last names. And the teacher then has to enter her comments about each student. Row by row, rather than having a separate form for each student. (Imagine an excel spreadsheet).
The solution was to use formsets; essentially each record is its own form.
To setup the formset:
In models:
class Student(models.Model):
firstname = models.CharField(max_length=30, blank=False)
lastname = models.CharField(max_length=30, blank=False)
comments = models.TextField()
This is a standard Model.
In views:
from django.forms.models import modelformset_factory
StudentFormset = modelformset_factory(
Student,
fields=('id', 'lastname', 'firstname', 'comments'),
extra=0)
student_list = Student.objects.all().order_by('lastname', 'firstname')
student_formset = StudentFormset(queryset= student_list)
context = {'formset': student_formset }
template_name = 'students/comments.html'
return render(request, template_name, context)
-
First, import the modelformset_factory
-
Then, I create my StudentFormset.
- The basic syntax is:
modelformset_factory(model, fields=(a tuple of fields from that model))
. There are many more arguments you might use – likewidgets
,labels
,error_messages
. See the docs) fields:
I am basing this formset on the Student model, and listing exactly which fields from that model I want.extra:
how many blank forms I want. In this case, I don't want any, so it is0
.
- The basic syntax is:
-
A formset can have several blank forms (you can determine how many). Or it can be prepopulated. In this case I needed a specific queryset. Hence I did my
student_list
queryset which I will soon pass to the formset (I could have also written it directly into the instance of the StudentFormset - below) -
I create an instance of the StudentFormset with the student_list queryset.
-
I add the formset to the context to be rendered below.
the students/comments.html
template
<form method="post">{% csrf_token %}
{{ formset.management_form }}
<table>
{% for form in formset %}
<tr>
<td>{{form.lastname}} {{ form.firstname }} {{form.id}} </td>
<td>{{form.comments}} </td>
</tr>
{% endfor %}
</table>
<button type="submit" name="save" >Save</button>
</form>
That line of the management_form
is super important. It holds vital data about the formset – how many forms were actually created, etc.
– the prefix (formset
) must match the arguments used in the context.
The rest is basic template with a for
statement.
To save the data on the formset:
In the views:
if request.POST:
formset = StudentFormset(request.POST)
if formset.is_valid():
formset.save()
This is the most basic way to save. Normally, we might do other validations, etc. - Create an instance of the StudentFormset with the request.POST. - Make sure the data in all the forms in the formset it is valid. And then save. If anything is not valid, it will not save the entire formset. And produce a dictionary of errors.
Comment