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 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 Python PEP 435 Enum including those that do not derive from Django’s
TextChoices
andIntegerChoices
.Coerce fields to instances of the Enum type by default.
Allow strict adherence to Enum values to be disabled.
Handle migrations appropriately. (See 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 mask 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 PEP 435 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 MyModel(models.Model):
class TextEnum(models.TextChoices):
VALUE0 = 'V0', 'Value 0'
VALUE1 = 'V1', 'Value 1'
VALUE2 = 'V2', 'Value 2'
class IntEnum(models.IntegerChoices):
ONE = 1, 'One'
TWO = 2, 'Two',
THREE = 3, 'Three'
# this is equivalent to:
# CharField(max_length=2, choices=TextEnum.choices, null=True, blank=True)
txt_enum = EnumField(TextEnum, null=True, blank=True)
# 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 = MyModel.objects.create(
txt_enum=MyModel.TextEnum.VALUE1,
int_enum=3 # by-value assignment also works
)
assert instance.txt_enum == MyModel.TextEnum('V1')
assert instance.txt_enum.label == 'Value 1'
assert instance.int_enum == MyModel.IntEnum['THREE']
assert instance.int_enum.value == 3
Flag Support¶
Flag types are also seamlessly supported! This allows a database column to behave like a bit mask and is an alternative to multiple boolean columns. There are mostly positive performance implications for using a bit mask instead of booleans depending on the size of the bit mask and the types of queries you will run against it. For bit masks 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.
class Permissions(IntFlag):
READ = 1**2
WRITE = 2**2
EXECUTE = 3**2
class FlagExample(models.Model):
permissions = EnumField(Permissions)
FlagExample.objects.create(permissions=Permissions.READ | Permissions.WRITE)
# get all models with RW:
FlagExample.objects.filter(permissions__has_all=Permissions.READ | Permissions.WRITE)
Complex Enumerations¶
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
from enum_properties import StrEnumProperties
from django.db import models
class TextChoicesExample(models.Model):
class Color(StrEnumProperties):
label: Annotated[str, Symmetric()]
rgb: Annotated[t.Tuple[int, int, int], Symmetric()]
hex: Annotated[str, Symmetric(case_fold=True)]
# 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"
# any named s() values in the Enum's inheritance become properties on
# each value, and the enumeration value may be instantiated from the
# property's value
color = EnumField(Color)
instance = TextChoicesExample.objects.create(
color=TextChoicesExample.Color('FF0000')
)
assert instance.color == TextChoicesExample.Color('Red')
assert instance.color == TextChoicesExample.Color('R')
assert instance.color == TextChoicesExample.Color((1, 0, 0))
# direct comparison to any symmetric value also works
assert instance.color == 'Red'
assert instance.color == 'R'
assert instance.color == (1, 0, 0)
# 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 TextChoicesExample.objects.filter(
color=TextChoicesExample.Color.RED
).first() == instance
assert TextChoicesExample.objects.filter(color=(1, 0, 0)).first() == instance
assert TextChoicesExample.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 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)]
# 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"
Installation¶
?> pip install django-enum
django-enum has several optional dependencies that are not pulled in by default. EnumFields
work 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.
Integrations are provided that leverage enum-properties to make enumerations do more work and to provide extended functionality for django-filter and djangorestframework.
?> pip install enum-properties
?> pip install django-filter
?> pip install djangorestframework
Continuous Integration¶
Like with Django, Postgres is the preferred database for support. The full test suite is run against all combinations of currently supported versions of Django, Python, and Postgres 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
For Oracle, only the latest version of the free database is tested against the minimum and maximum supported versions of Python, Django and the cx-Oracle driver.
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!
- Usage
- Examples
- Performance
- Eccentric Enumerations
- Reference
- Change Log
- 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)