Django tip: Showing <optgroup>s in a ModelForm

For my first Django tip on this brand new blog, I will provide some insight into my most recent head-scratcher: How do you get a ModelForm to display something like this…

…when you have your models laid out like this:

class Category(models.Model):
    name = models.CharField(max_length=50)  # Auto, Beauty, etc.

class SubCategory(models.Model):
    category = models.ForeignKey(Category)
    name = models.CharField(max_length=50)  # Parts, Fragrances, etc.

class Merchant(models.Model):
    sub_categories = models.ManyToManyField(SubCategory, verbose_name='Areas your business deals in')

On Dealit, the SubCategory model is used by many other models in a ManyToMany relationship, for example, when a merchant signs up (as I’m showing in the Merchant model above). But building out a ModelForm for Merchant will only display SubCategories without the related Category in an <optgroup>.

In order to fix that, we do this in our form…

class MerchantForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(MerchantForm, self).__init__(*args, **kwargs)
        self.fields['sub_categories'].choices = categories_as_choices()

def categories_as_choices():
    categories = []
    for category in Category.objects.all():
        new_category = []
        sub_categories = []
        for sub_category in category.subcategory_set.all():
            sub_categories.append([sub_category.id, sub_category.name])

        new_category = [category.name, sub_categories]
        categories.append(new_category)

    return categories

Here, we create a lot of nested lists with categories_as_choices() and tell Django to override the default list in the form. This is how Django wants it if you want it displaying <optgroup>s. Here’s an example from the documentation:

MEDIA_CHOICES = (
    ('Audio', (
            ('vinyl', 'Vinyl'),
            ('cd', 'CD'),
        )
    ),
    ('Video', (
            ('vhs', 'VHS Tape'),
            ('dvd', 'DVD'),
        )
    ),
    ('unknown', 'Unknown'),
)

This is exactly what we end up creating with categories_as_choices(), except we create lists instead of tuples. Both are acceptable.

Hope that helps.

Advertisement

7 Responses to Django tip: Showing <optgroup>s in a ModelForm

  1. nice one. didn’t know about this. thx for sharing

  2. Victor Cinaglia

    Man, this is sweet. Thanks for sharing it.

  3. oh boy was that great!
    now onto some js ;)

  4. Very useful tip!
    Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s