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...
STUDENT_TYPE_CHOICES = (
    ('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:

In my project I added common/utils.py containing the following:

from enum import Enum

class ChoiceEnum(Enum):
    @classmethod
    def choices(cls):
        return tuple((i.name, i.value) for i in cls)

That’s the hard work over.

Now when you create your field with choices:

from common.utils import ChoiceEnum

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

# within your models.Model class...
class Student(models.Model):
    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:

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.

16 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__”.

    Reply
  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).

    Reply
  3. anon62453

    [re-posted with code block]

    Here’s what we used for the same issue:

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

    Reply
  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)

    Reply
  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.name, choice.value) for choice in cls]

    Reply
  6. matt

    This solution cannot be recommended. Use the django-enumfield instead
    https://pypi.python.org/pypi/django-enumfield/

    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
    vs
    model.student_type == StudentTypes.freshman.value

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

    Reply
    1. Siddharth Pant

      That’s easily solvable by just defining your own Enum class with __str__, __repr__ and __eq__ special methods. You can define them at one place and use it everywhere. By this way you have to use StudentTypes.freshman.value only at the Field definition in the model(i.e. only when you provide a default field value). At all other places StudentTypes.freshman will work. i think that is much better than using a third party library.

      Reply
  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):
    @classmethod
    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.

    Reply
  8. Samantha Atkins

    Why not something like:

    class Choices(object):
    def __init__(self, **choices):
    self.__dict__.update(choices)
    self._choices = choices
    self._rev_choices = {v:k for k,v in choices.items()}

    @property
    def choices(self):
    return self._choices

    def choice_name(self, value):
    return self._rev_choices.get(value)

    Reply

Leave a Reply

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