Implementation of enumeration classes in Django models

In official Django documentation, you can read how to use choices for a field. In general, they recommend using the predefined list of values in the class. To make our models a little bit more straightforward we can move predefined options into native Python enumerations. Enumerations (Python 3.4+) are a set of unique values.

from django.db import models
from django.utils.translation import ugettext_lazy as _ 


class Example(models.Model):
    STATUS_NEW = 'NEW'
    STATUS_CLOSED = 'CLOSED'
    STATUSES = (
        (STATUS_NEW, _('New')),
        (STATUS_CLOSED, _('Closed')),
    )
    status = models.CharField(_('status'), default=STATUS_NEW, choices=STATUSES, max_length=255)

Let’s refactor model above to use native Python’s enumeration. We are adding the new class into a separate file named enums.py within the same application directory which is going to inherit from enumeration class.

from enum import Enum

from django.utils.translation import ugettext_lazy as _


class ExampleStatus(Enum):
    NEW = 'NEW'
    DEMAND = 'DEMAND'

    def __str__(self):
        return self.value

    @classmethod
    def choices(cls):
        return (
            (str(cls.NEW), _('New')),
            (str(cls.CLOSED), _('Closed')),
        )

You can see we added choices method which is used as a value for choices attribute in the field definition. There are some other ways how to implement the choices method by adding a new package but we like to keep our requirements minimal without any unnecessary dependencies. Below you can see a new file structure after implementing a custom enumeration class.

| example_application
|- __init__.py
|- models.py
|- enums.py

It is possible to simplify the solution again by iterating through all enumeration’s members (yes, enumerations are iterable) and using name and value properties on each member. With this approach, we will lose translation support when using, for example, get_FIELD_NAME_display() method. Anyway, the solution may be handy in some specific use-cases.

from enum import Enum

class ExampleStatus(Enum):
    NEW = 'NEW'
    DEMAND = 'DEMAND'

    @classmethod
    def choices(cls):
        return ((item.key, item.value) for item in cls)

Below you can see the final model. We got rid of all predefined values in models.py and moved options into separate enumeration class making the code more readable and maintainable.

from django.db import models
from django.utils.translations import ugettext_lazy as _

from .enums import ExampleStatus


class Example(models.Model):
    status = models.CharField(_('status'), default=ExampleStatus.NEW, choices=ExampleStatus.choices(), max_length=255)