Overriding ModelChoiceField labels

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.


Comments

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

rXybjh <a href="http://mflhjopbtnaq.com/">mflhjopbtnaq</a>, [url=http://kfssciecejsb.com/]kfssciecejsb[/url], [link=http://owmwirgkigwq.com/]owmwirgkigwq[/link], http://wkoqpposjlvt.com/

Posted by tscrzsaal on May 8, 2008 at 1:13 AM

Add Your Comment



Entry Details

Published: Feb 23, 2008 at 2:50 PM

© 2007 - 2008 Brian Rosner