Working with ChoiceField in Django

Django provides flexible ways to work with choices. In this article, we will cover the next topics: 

  • Django model's choices attribute
  • forms.ModelChoiceField
  • forms.ChoiceField
     

Working with Field.choices in Django Models

Let's assume we have the following model, which represents a developer:

class Developer(models.Model):
    JUNIOR = 'JR'
    MIDDLE = 'ML'
    SENIOR = 'SR'

    LEVEL_CHOICES = (
        (JUNIOR, 'Junior'),
        (MIDDLE, 'Middle'),
        (SENIOR, 'Senior'),
    )
    name = models.CharField(max_length=200)
    level = models.CharField(max_length=2, choices=LEVEL_CHOICES, default=JUNIOR)

The first value of each tuple inside LEVEL_CHOICES is stored in the related table's level column and the second value is the human-readable version. It's best practice to define choices inside the model class and constants (JUNIOR, MIDDLE, SENIOR) which contain a choice's actual value. So, we can use one of the constants to mark as the default value.

The default widget of the field with defined choices is the select/option box instead of text input.

And actual values ("JR") are assigned to the value attribute of the option HTML tag.

Also, Django allows defining grouped choices. It allows combining grouped options with the ungrouped options, such as “Unknown” in the example below:

class Developer(models.Model):
    
...
    CITY_CHOICES = [
        ('US', (
            ('ATL', 'Atlanta'),
            ('BOS', 'Boston'),
        )
         ),
        ('UK', (
            ('BAT', 'Bath'),
            ('BRI', 'Bristol'),
        )
         ),
        ('UN', 'Unknown'),
    ]

    name = models.CharField(max_length=200)
    level = models.CharField(max_length=2, choices=LEVEL_CHOICES)
    city = models.CharField(max_length=3, choices=CITY_CHOICES)

 

For each model field that has choices attribute set, Django adds a helper method to display a human-readable version of the stored value. This method has the form obj.get_FOO_display(), where FOO is the field name. The following example makes it clear:


>>> d = Developer.objects.first()
>>> d.level
'SR'
>>> d.get_level_display()
'Senior'
>>> d.city
'ATL'
>>> d.get_city_display()
'Atlanta'

 

Foreign Key field as a choice field in the form

Now, developers can have a mentor. So we add a new ForeignKey field which refers to the same model. 

class Developer(models.Model):
    ...
    name = models.CharField(max_length=200)
    level = models.CharField(max_length=2, choices=LEVEL_CHOICES, default=JUNIOR)
    city = models.CharField(max_length=3, choices=CITY_CHOICES)
    mentor = models.ForeignKey('self', on_delete=models.PROTECT, null=True, blank=True)

  	def __str__(self):
        return f"{self.name} - {self.level}"

Next, add a view in which admins can add a new developer by assigning an appropriate mentor.

Our forms.py looks the following way:

from django import forms
from .models import Developer


class DeveloperAddForm(forms.ModelForm):
    class Meta:
        model = Developer
        fields = (
            'name',
            'level',
            'mentor',
        )

 

Let's assume only the Senior developer can be a mentor, so we have to display in the select options only Senior level developer names. How can we do that?

Well, it's not so difficult. The form's field mentor is automatically pulled from the related model, when we included it inside Meta class's fields attribute.

 

Django form ModelChoiceField

To display only Senior developers we need to customize our form field mentor and here we introduce forms.ModelChoiceField. This field has queryset attribute which can be assigned queryset with an extra filter/exclude logic. In our case, we filter mentors by level, so that only senior programmers can be displayed in the mentor select box.

class DeveloperAddForm(forms.ModelForm):
    mentor = forms.ModelChoiceField(
        queryset=Developer.objects.filter(level=Developer.SENIOR)
    )

    class Meta:
    ---

As you can see, it displays only Senior developers. One thing to note, the option tags value contains the id of the Developer model (primary key). 

 

Django forms ChoiceField

Django forms also have another type of ChoiceField. It is not attached to any model queryset. For example, to add a age select box to our form without adding such a choice field into our Developer model, we can add the field directly to the form. AGE_CHOICES is the tuple that contains inner tuples and it is assigned to the choices attribute of the forms.ChoiceField.

class DeveloperAddForm(forms.ModelForm):
    AGE_CHOICES = (
        (1, 'Under 18'),
        (2, '19-24'),
        (3, '25-35'),
        (4, 'Older than 35')
    )

    mentor = forms.ModelChoiceField(
        queryset=Developer.objects.filter(level=Developer.SENIOR)
    )
    age = forms.ChoiceField(
        choices=AGE_CHOICES
    )

    class Meta:
    ---

As we can see in the next image, the field is converted to the nice select/option box:

The value of the option tags is the actual value in the AGE_CHOICES tuple, which is the first value of each inner tuple.

 

Hope, this guide helps you to better understand how different forms of ChoiceField work in the Django framework.

Happy coding!