Multi-Object-Edit With Django FormSets


I had to write a multi-object edit table the other day for a Django project and as such I dove into the FormSet Documentation. Django’s documentation is really good usually but the part abut the FormSets was a bit of a letdown.

So in case anybody else is in the same situation here is some code of how I did it (written from memory - should still be okay I hope).

# forms.py
from django import forms
from django.forms.models import modelformset_factory

# creating a FormSet for a specific Model is easy
FooFormSetBase = modelformset_factory(
  Foo, extra=0, fields=('somefield', 'someotherfield'))

# now we want to add a checkbox so we can do stuff to only selected items
class FooFormSet(FooFormSetBase):
  # this is where you can add additional fields to a ModelFormSet
  # this is also where you can change stuff about the auto generated form
  def add_fields(self, form, index):
    super(FooFormSet, self).add_fields(form, index)
    form.fields['is_checked'] = forms.BooleanField(required=False)
    form.fields['somefield'].widget.attrs['class'] = 'somefieldclass'

After writing the FormSet itself here is the view:

# views.py
from django.shortcuts import redirect
from django.template import RequestContext
from fooproject.fooapp.forms import FooFormSet
from fooproject.models import Foo


def fooview(request):
  if request.method == 'POST':
    # we have multiple actions - save and delete in this case
    action = request.POST.get('action')
    formset = FooFormSet(
      request.POST, queryset=Foo.objects.all())

    if formset.is_valid():
      # iterate over all forms in the formset
      for form in formset.forms:
        # only do stuff for forms in which is_checked is checked
        if form.cleaned_data.get('is_checked'):
          if action == u'delete':
            # we need to call save to get an actual model but
            # there is no need to hit the database hence the
            # commit=False
            model_instance = form.save(commit=False)
            # now that we got a model we can delete it
            model_instance.delete()
          if action == u'save':
            form.save()

      redirect('someview')

  else:
    formset = FooFormSet(queryset=Foo.objects.all())

  return render_to_response('sometemplate.html', {'formset': formset},
    context_instance=RequestContext(request))

Now all that’s missing is the template:

action="." method="post" accept-charset="utf-8"> {% for form in formset.forms %} {% endfor %}
is_checked somefield someotherfield
{# don't forget about the id field #} {{ form.id }} {{ form.is_checked }} {{ form.somefield }} {{ form.someotherfield }}

{# and don't forget about the management form #} {{ formset.management_form }} {% csrf_token %}

Of course there is stuff still missing – you won’t see errors in your form for example. But you get the general idea.