Implementation of enumeration classes in Django models
From Django version 3+ native enumerations support has been implemented. Use the tutorial below only in case you are using Django 2 otherwise we recommend using native implementation.
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
)