Iterating over items of selection fields in django templates using newforms

A year ago I wrote a custom CheckboxSelectMultiple control for django. My application needed to display a series of checkboxes on the webpage, but the default django control did not allow iteration over each checkbox when the control was rendered in the template (as it was possible with the RadioSelect control). This finer control was necessary because I needed to insert extra HTML between each checkbox.

As of version 0.95, django has been under heavy changes, and my custom control no longer works. In particular, the old forms module is being discarded in favor of the newforms module that will become the default forms module sometime in the future. A good explanation can be found in the on-site django documentation, under newforms-migration plan.

The good news is that newforms allows access to individual items of the form fields, multiple-select fields included. The newforms documentation is still work in progress, so it took me a while to figure out how to do it... by inspecting the source code and regression tests. It seems pretty obvious now, should have asked in the django-users list.

The example code has been tested with django svn release 4812 (2007-3-23).

template

<ul>
{% for choice in form.base_fields.tag.choices %}
<li> ({{ choice.0 }}, {{ choice.1 }}) </li>
{% endfor %}
</ul>

models.py

# models.py
from django.db import models
class Tag(models.Model):
    tag = models.CharField(maxlength=20)
    def __str__(self):
        return self.tag
class Post(models.Model):
    # other fields here:
    # text = models.CharField(maxlength=255)
    # title = models.CharField(maxlength=50)
    # etc.
    tag = models.ManyToManyField(Tag)

views.py

# views.py
from django.template import Context, loader
from django.http import HttpResponse, HttpResponseRedirect
from django import newforms as forms
from django.newforms.widgets import *
from project.models import *
def add_post(request):
    postForm = forms.models.form_for_model(Post)
    if request.method == 'POST':
       form = postForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("/")
    else:
         form = postForm()
         t = loader.get_template('add_post.html')
         c = Context({
               'form': form,
               })
         return HttpResponse(t.render(c))

This is an old version of views.py. The code works, but as I discovered later, there is no need to create another object (p = Post(**cleandata)) to handle the many-to-many field data. form.save() takes care of everything, as expected.

# views.py
from django.template import Context, loader
from django.http import HttpResponse, HttpResponseRedirect
from django import newforms as forms
from django.newforms.widgets import *
from project.models import *
def add_post(request):
    postForm = forms.models.form_for_model(Post)
    if request.method == 'POST':
        form = postForm(request.POST)
        if form.is_valid():
           cleandata = form.clean_data
           # use the form tag ids to select the Tag instances
           # related to this Post entry
           tag = Tag.objects.in_bulk(cleandata['tag'])
           # need to delete the tag ids from clean data,
           # otherwise p = Post(** cleandata) will complain that
           # tag is not a parameter of Post( )
           del cleandata['tag']
           # create an instance of Post from the form data
           p = Post(**cleandata)
           p.save()   # need to save so p gets an id.
           p.tag = tag
           p.save()
           return HttpResponseRedirect("/")
       else:
       form = postForm()
       t = loader.get_template('add_post.html')
       c = Context({
              'form': form,
        })
        return HttpResponse(t.render(c))
checkboxes, django, newforms, python, templates

Join my free newsletter and receive updates directly to your inbox.