It is a common request to override the labels that are used in a
ModelChoiceField or ModelMultipleChoiceField. There are two approaches
I will show you to accomplish this.
You will want to use a ModelChoiceField or ModelMultipleChoice field
when you need to create a drop-down of the objects in a model. The ModelForm
class will automatically use these fields for either a foreign key or a
many-to-many field, respectively.
Defining the Form
Lets go ahead and create a form where we will use a ModelChoiceField:
from django import newforms as forms
from acme.shop.models import Manufacturer
class ProductForm(models.Form):
manufacturer = forms.ModelChoiceField(Manufacturer.objects.all())
For purposes of this article, I will use the project and application acme.shop
that contains the model Manufacturer.
Overriding __unicode__ on the model
The first approach I want to show you is to simply overriding the __unicode__
method on your model that is representing the field. You should be doing this
anyway as a best practice. The fields use a call to smart_unicode to
resolve the label from the object.
The definition of the Manucfacturer model should look something like this:
from django.db import models
class Manufacturer(models.Model):
name = models.CharField(max_length=100)
city = models.CharField(max_length=100)
def __unicode__(self):
return self.name
Now the label that is used in the ModelChoiceField will be the name of the
manufacturer.
Creating your own form field
The second approach is by subclassing one of the fields. This will allow you to
define your own behavior of how it gets the label from the model objects. You
will want to do this if the model is something you can't override the
__unicode__ method or you simply need a different label for the form than
what your __unicode__ method provides.
Before I begin, I would like to mention that will seem much more involved than it really should be. Ticket #4620 was created to help improve this, but was marked as wontfix since the correct method of doing so is what I am about to show you. Lets get started.
Both the ModelChoiceField and ModelMultipleChoiceField use another
class called QuerySetIterator that will provide the options in the select
box. You will need to subclass this first before we get into subclassing the
form field. It is common practice to use a forms.py file to store your form,
which is where you can also subclass these classes.
from django.newforms.models import QuerySetIterator
class ManufacturerQuerySetIterator(QuerySetIterator):
def __iter__(self):
if self.empty_label is not None:
yield (u"", self.empty_label)
for obj in self.queryset:
# Here is where you adjust the behavior of what is used.
# yield (obj.pk, smart_unicode(obj))
yield (obj.pk, "%s (%s)" % (obj.name, obj.city))
# clear the QuerySet cache if required
if not self.cache_choices:
self.queryset._result_cache = None
Now we have our desired behavior around the label generation. Now we will need
to subclass the ModelChoiceField. Here we will have to override the
_get_choices method and
class ManufacturerChoiceField(forms.ModelChoiceField):
def _get_choices(self):
# Technically, don't have to do this, but do so anyway to be the most
# compatible with parent's behavior.
if hasattr(self, "_choices"):
return self._choices
return ManufacturerQuerySetIterator(self.queryset, self.empty_label,
self.cache_choices)
choices = property(_get_choices, forms.ModelChoiceField._set_choices)
Now you have overridden the labels of the drop-down. While there could be some Python shortcuts here, I wanted to demonstrate a complete and non-hacky method of accomplishing this.
I hope this has helped someone learn something new and get their code working the way they would like it to behave.

This worked like a charm, and I have been looking weeks to find the correct way to list users by their proper names instead of usernames.
The only hiccup I had was when I imported the forms.py. I used the ModelForm to create my form so I did not have anything but the subclasses for the Iterator and the Field in the forms.py file. It was not obvious to me that I needed to import the newforms in this file (from django import newforms as forms). But that really wasn't much of a problem. Thanks so much for the post I helped a ton!
Posted by Zack on Feb 27, 2008 at 6:01 PM