How to use Enums for Django Field.choices

In Django when using the choices parameter on a form field the format passed in must be as follows:

# within your models.Model class...
    ('0', 'freshman'),
    ('1', 'sophomore'),
    ('2', 'junior'),
    ('3', 'senior'),
student_type = models.CharField(max_length=1, choices=STUDENT_TYPE_CHOICES)

This means elsewhere in your code if you want to specify a choice field value, you’d have to enter the first slot of the tuple’s value, e.g.:

junior_students = Student.objects.filter(student_type='2')

This is pretty terrible since it’s hardcoded in our source, possibly over many files.

How to fix this mess:

First, install enum34 on the commandline

pip install enum34

In my project I added common/ containing the following:

import inspect
from enum import Enum

class ChoiceEnum(Enum):

    def choices(cls):
        # get all members of the class
        members = inspect.getmembers(cls, lambda m: not(inspect.isroutine(m)))
        # filter down to just properties
        props = [m for m in members if not(m[0][:2] == '__')]
        # format into django choice tuple
        choices = tuple([(str(p[1].value), p[0]) for p in props])
        return choices

That’s the hard work over.

Now when you create your choice field:

from common.utils import ChoiceEnum

class StudentTypes(ChoiceEnum):
    freshman = 0
    sophomore = 1
    junior = 2
    senior = 3

# within your models.Model class...
student_type = models.CharField(max_length=1, choices=StudentTypes.choices())

Now if we need to access StudentTypes from elsewhere in our source code, we can simply:

# obviously import StudentTypes
junior_students = Student.objects.filter(student_type=StudentTypes.junior.value)

That’s it. If anyone knows of a nicer way feel free to comment below.

11 thoughts on “How to use Enums for Django Field.choices

  1. asd

    In the choices method you can just do “for member in cls” or “for member in cls.__members__”.

  2. Jake

    I think you’re solving a problem that doesn’t exist and and adding unnecessary complexity.

    You’re not limited to using integers as keys for the choices. You’re storing the key in a charfield, so a string will work fine (increase the max_length, obviously).

  3. anon62453

    [re-posted with code block]

    Here’s what we used for the same issue:

    def choices(cls):
    return [(x.value, for x in cls]

  4. Victor

    choices = tuple([(str(p[1].value), p[0]) for p in props])

    There is not need in creating the intermediary list:

    choices = tuple((str(p[1].value), p[0]) for p in props)

  5. Damien de Lemeny

    Hi and thanks for the tip !
    Enumerations provided by enum34 support iteration, so your snippet can be simplified to a one liner :

    return [(, choice.value) for choice in cls]

  6. matt

    This solution cannot be recommended. Use the django-enumfield instead

    The *.value syntax will give you headaches in the long run especially in equality tests on your models:
    At some point you will test your model fields for equality:
    model.student_type == StudentTypes.freshman
    model.student_type == StudentTypes.freshman.value

    Have been there, do not want to go there again.

  7. nmgeek

    The ChoiceEnum.choices function was not returning the choices in the order I declared them so I reworked that class and ended up with a more compact implementation:

    class ChoiceEnum(Enum):
    def choices(cls):
    # format into django choice list
    return [(cls[m].value,m.replace('_',' ').title()) for m in reversed(cls._member_names_)]

    This also massages the user visible names for multi-word enum members.


Leave a Reply to Sergei Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>