Django Enum¶
Tip
See Migration from 1.x -> 2.x for how to update from 1.x to 2.x.
Full and natural support for PEP435 enumerations
as Django model fields.
Many packages aim to ease usage of Python enumerations as model fields. Most were superseded when Django provided TextChoices and IntegerChoices types. The motivation for django-enum was to:
Work with any
Enum
including those that do not derive from Django’s TextChoices and IntegerChoices.Coerce fields to instances of the
Enum
type by default.Allow strict adherence to
Enum
values to be disabled.Handle migrations appropriately. (See Write Migrations)
Integrate as fully as possible with Django’s existing level of enum support.
Support Enum Properties to enable richer enumeration types. (A less awkward alternative to dataclass enumerations with more features)
Represent enum fields with the smallest possible column type.
Support bit field queries using standard
Python Flag enumerations
.Be as simple and light-weight an extension to core Django as possible.
Enforce enumeration value consistency at the database level using check constraints by default.
(TODO) Support native database enumeration column types when available.
django-enum provides a new model field type, EnumField
, that allows
you to treat almost any PEP435 enumeration as a database column.
EnumField
resolves the correct native Django field type for the given
enumeration based on its value type and range. For example,
IntegerChoices that contain values between 0 and 32767 become
PositiveSmallIntegerField
.
from django.db import models
from django_enum import EnumField
class BasicExample(models.Model):
class TextEnum(models.TextChoices):
VALUE0 = "V0", "Value 0"
VALUE1 = "V1", "Value 1"
VALUE2 = "V2", "Value 2"
class IntEnum(models.IntegerChoices):
# fmt: off
ONE = 1, "One"
TWO = 2, "Two"
THREE = 3, "Three"
# fmt: on
# this is equivalent to:
# CharField(max_length=2, choices=TextEnum.choices, null=True, blank=True)
txt_enum = EnumField(TextEnum, null=True, blank=True, default=None)
# this is equivalent to
# PositiveSmallIntegerField(choices=IntEnum.choices, default=IntEnum.ONE.value)
int_enum = EnumField(IntEnum, default=IntEnum.ONE)
EnumField
is more than just an alias. The fields are now assignable
and accessible as their enumeration type rather than by-value:
instance = BasicExample.objects.create(
txt_enum=BasicExample.TextEnum.VALUE1,
int_enum=3 # by-value assignment also works
)
assert instance.txt_enum is BasicExample.TextEnum('V1')
assert instance.txt_enum.label == 'Value 1'
assert instance.int_enum is BasicExample.IntEnum.THREE
assert instance.int_enum.value == 3
Flag Support (BitFields)¶
enum.Flag
types are also seamlessly supported! This allows a database column to behave
like a bit field and is an alternative to having multiple boolean columns. There are positive
performance implications for using a bit field instead of booleans proportional on the size of the
bit field and the types of queries you will run against it. For bit fields more than a few bits long
the size reduction both speeds up queries and reduces the required storage space. See the
documentation for discussion and benchmarks.
from django.db import models
from enum import IntFlag
from django_enum import EnumField
class Permissions(IntFlag):
# fmt: off
READ = 1<<0
WRITE = 1<<1
EXECUTE = 1<<2
# fmt: on
class FlagExample(models.Model):
permissions = EnumField(Permissions, null=True, blank=True)
instance = FlagExample.objects.create(
permissions=Permissions.READ | Permissions.WRITE | Permissions.EXECUTE
)
# get all models with at least RW:
assert instance in FlagExample.objects.filter(
permissions__has_all=Permissions.READ | Permissions.WRITE
)
Enums with Properties¶
django-enum supports enum types that do not derive from Django’s IntegerChoices and TextChoices. This allows us to use other libs like Enum Properties which makes possible very rich enumeration fields:
> pip install enum-properties
import typing as t
from django.db import models
from enum_properties import Symmetric, StrEnumProperties
from typing_extensions import Annotated
from django_enum import EnumField
class PropertyExample(models.Model):
class Color(StrEnumProperties):
label: str
rgb: Annotated[t.Tuple[int, int, int], Symmetric()]
hex: Annotated[str, Symmetric(case_fold=True)]
# fmt: off
# name value label rgb hex
RED = "R", "Red", (1, 0, 0), "ff0000"
GREEN = "G", "Green", (0, 1, 0), "00ff00"
BLUE = "B", "Blue", (0, 0, 1), "0000ff"
# fmt: on
# any type hints before the values in the Enum's definition become
# properties on each value, and the enumeration value may be
# instantiated from any symmetric property's value
color = EnumField(Color)
instance = PropertyExample.objects.create(
color=PropertyExample.Color('FF0000')
)
assert instance.color is PropertyExample.Color['RED']
assert instance.color is PropertyExample.Color('R')
assert instance.color is PropertyExample.Color((1, 0, 0))
# note that we did not make label symmetric, so this does not work:
# PropertyExample.Color('Red')
# direct comparison to any symmetric value also works
assert instance.color == 'FF0000'
assert instance.color == 'R'
assert instance.color == (1, 0, 0)
assert instance.color != 'Red' # because label is not symmetric
# save by any symmetric value
instance.color = 'FF0000'
# access any enum property right from the model field
assert instance.color.hex == 'ff0000'
# this also works!
assert instance.color == 'ff0000'
# and so does this!
assert instance.color == 'FF0000'
instance.save()
# filtering works by any symmetric value or enum type instance
assert PropertyExample.objects.filter(
color=PropertyExample.Color.RED
).first() == instance
assert PropertyExample.objects.filter(color=(1, 0, 0)).first() == instance
assert PropertyExample.objects.filter(color='FF0000').first() == instance
While they should be unnecessary, if you need to integrate with code that expects an interface fully
compatible with Django’s TextChoices and
IntegerChoices django-enum
provides TextChoices
, IntegerChoices
,
FlagChoices
and FloatChoices
types that
derive from Enum Properties and Django’s Choices
. So the above enumeration could
also be written:
from django_enum.choices import TextChoices
class ChoicesWithProperties(models.Model):
class Color(TextChoices):
# label is added as a symmetric property by the base class
rgb: Annotated[t.Tuple[int, int, int], Symmetric()]
hex: Annotated[str, Symmetric(case_fold=True)]
# fmt: off
# name value label rgb hex
RED = "R", "Red", (1, 0, 0), "ff0000"
GREEN = "G", "Green", (0, 1, 0), "00ff00"
BLUE = "B", "Blue", (0, 0, 1), "0000ff"
# fmt: on
color = EnumField(Color)
Installation¶
> pip install django-enum
django-enum has several optional dependencies that are not installed by default.
EnumField
works seamlessly with all Django apps that work with model
fields with choices without any additional work. Optional integrations are provided with several
popular libraries to extend this basic functionality, these include:
- enum-properties
> pip install "django-enum[properties]"
Database Support¶
Like with Django, PostgreSQL is the preferred database for support. The full test suite is run against all combinations of currently supported versions of Django, Python, and PostgreSQL as well as psycopg3 and psycopg2. The other RDBMS supported by Django are also tested including SQLite, MySQL, MariaDB and Oracle. For these RDBMS (with the exception of Oracle), tests are run against the minimum and maximum supported version combinations to maximize coverage breadth.
See the latest test runs for our current test matrix
Further Reading¶
Consider using django-render-static to make your enumerations DRY across the full stack!
Please report bugs and discuss features on the issues page.
Contributions are encouraged!
Contents:
- Tutorials
- How To
- Performance
- Eccentric Enums
- Reference
- Change Log
- v2.2.2 (2025-04-18)
- v2.2.1 (2025-04-18)
- v2.2.0 (2025-03-28)
- v2.1.0 (2025-02-24)
- v2.0.2 (2024-09-25)
- v2.0.1 (2024-09-16)
- v2.0.0 (2024-09-09)
- v1.3.3 (2024-08-26)
- v1.3.2 (2024-07-15)
- v1.3.1 (2024-03-02)
- v1.3.0 (2023-12-13)
- v1.2.2 (2023-10-02)
- v1.2.1 (2023-04-08)
- v1.2.0 (2023-04-02)
- v1.1.2 (2023-02-15)
- v1.1.1 (2023-01-15)
- v1.1.0 (2022-08-13)
- v1.0.1 (2022-08-11)
- v1.0.0 (2022-08-11)
- v0.1.0 (2010-09-18)