Integrate with …

Tip

EnumField instances ultimately inherit from existing core Django model fields and set the choices attribute. Therefore EnumField should work with any third party libraries and will behave as a core field with a defined choice tuple list would.

However, you may want to take advantage of some of the extra features provided by django-enum. We provide out-of-the-box integration with the following libraries:

enum-properties

Almost any enum.Enum type is supported, so you may make use of enum.Enum extension libraries like enum-properties to define very rich enumeration fields. You will need to install the properties optional dependency set:

pip install "django-enum[properties]"

enum-properties is an extension to enum.Enum that allows properties to be added to enumeration instances using a simple declarative syntax. This is a less awkward and more compatible alternative to dataclass enumerations.

If you find yourself considering a dataclass enumeration, consider using enum-properties instead. Dataclass value types do not work with EnumField. Most libraries that work with enumerations expect the value attribute to be a primitive serializable type.

enum-properties also allows for symmetric properties which compare as equivalent to the enumeration values and can be used to instantiate enumeration instances.

For a real-world example see the properties tutorial.

It should be unnecessary, but if you need to integrate with code that expects an interface fully compatible with Django’s enumeration types (TextChoices and IntegerChoices django-enum provides TextChoices, IntegerChoices, FlagChoices and FloatChoices types that derive from enum-properties and Django’s Choices. For instance, you may be using a third party library that uses isinstance() checks on your enum types instead of duck typing. For compatibility in these cases simply use django-enum’s Choices types as the base class for your enumeration instead:

import typing as t
from django.db import models
from typing_extensions import Annotated
from django_enum import EnumField
from django_enum.choices import TextChoices
from enum_properties import Symmetric, symmetric


class TextChoicesExample(models.Model):

    class Color(TextChoices):

        # no need to specify label because it is built in
        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

        # by default label is symmetric, but case sensitive
        # to make it case-insensitive we can override the property
        # and mark it like this:
        @symmetric(case_fold=True)
        @property
        def label(self) -> str:
            return self._label_

    color = EnumField(Color)

All of the expected enum-properties behavior works:

obj = TextChoicesExample.objects.create(color=TextChoicesExample.Color.RED)

assert obj.color is TextChoicesExample.Color.RED
assert obj.color.label == 'Red'
assert obj.color.rgb == (1, 0, 0)
assert obj.color.hex == 'ff0000'

# enum-properties symmetric properties work as expected
assert obj.color == 'Red'
assert obj.color == (1, 0, 0)
assert obj.color == 'ff0000'

Note

To make your non-choices derived enum quack like one, you will need to add:

  1. a choices property that returns the choices tuple list

  2. a label property that returns the list of labels

  3. a name property that returns the list of names

  4. a value property that returns the list of values

Django Rest Framework

By default DRF ModelSerializer will use a ChoiceField to represent an EnumField. This works great, but it will not accept symmetric enumeration values. A serializer field django_enum.drf.EnumField is provided that will. FlagField fields do not work well with DRF’s builtin MultipleChoiceField so we also provide a django_enum.drf.FlagField.

The dependency on DRF is optional so to use the provided serializer field you must install DRF:

pip install djangorestframework
from django_enum.drf import EnumField, FlagField
from rest_framework import serializers


class ExampleSerializer(serializers.Serializer):

    color = EnumField(TextChoicesExample.Color)
    
    # from the flags tutorial
    constellation = FlagField(Constellation)


ser = ExampleSerializer(
    data={
        'color': (1, 0, 0),
        'constellation': [
            Constellation.GALILEO.name,
            Constellation.GPS.name
        ]
    }
)
assert ser.is_valid()

The serializer django_enum.drf.EnumField accepts any arguments that ChoiceField does. It also accepts the strict parameter which behaves the same way as it does on the model field.

Tip

You only need to use django_enum.drf.EnumField if:

  1. You are integrating with enum-properties and want symmetric properties to work.

  2. You have non-strict model fields and want to allow your API to accept values outside of the enumeration.

The django_enum.drf.EnumField must be used for any FlagField fields. It will accept a composite integer or a list of any values coercible to a flag. The serialized output will be an composite integer holding the full bitfield.

ModelSerializers

An django_enum.drf.EnumFieldMixin class is provided that when added to ModelSerializers will be sure that the serializer instantiates the correct django-enum serializer field type:

from django_enum.drf import EnumFieldMixin
from rest_framework import serializers


class ExampleModelSerializer(EnumFieldMixin, serializers.Serializer):

    class Meta:
        model = TextChoicesExample


ser = ExampleModelSerializer(
    data={
        'color': (1, 0, 0),
    }
)
assert ser.is_valid()

django-filter

As shown above, filtering by any value, enumeration type instance or symmetric value works with Django’s ORM. This is not natively true for the default django_filters.filterset.FilterSet from django-filter. Those filter sets will only be filterable by direct enumeration value by default. An EnumFilter class is provided to enable filtering by symmetric property values, but since the dependency on django-filter is optional, you must first install it:

pip install django-filter
from django_enum.filters import EnumFilter
from django_filters.views import FilterView
from django_filters import FilterSet


class TextChoicesExampleFilterViewSet(FilterView):

    class TextChoicesExampleFilter(FilterSet):

        color = EnumFilter(enum=TextChoicesExample.Color)

        class Meta:
            model = TextChoicesExample
            fields = '__all__'

    filterset_class = TextChoicesExampleFilter
    model = TextChoicesExample

# now filtering by symmetric value in url parameters works:
# e.g.:  /?color=FF0000

A FilterSet class is also provided that uses EnumFilter for EnumField by default. So the above is also equivalent to:

from django_filters.views import FilterView


class TextChoicesExampleFilterViewSet(FilterView):

    class TextChoicesExampleFilter(EnumFilterSet):
        class Meta:
            model = TextChoicesExample
            fields = '__all__'

    filterset_class = TextChoicesExampleFilter
    model = TextChoicesExample

Tip

FilterSet may also be used as a mixin.

FlagFields

An EnumFlagFilter field for flag fields is also provided:

from django_enum.filters import EnumFlagFilter
from django_filters.views import FilterView
from django_filters import FilterSet


class FlagExampleFilterViewSet(FilterView):

    class FlagExampleFilter(FilterSet):

        constellation = EnumFlagFilter(enum=Constellation)

        class Meta:
            model = GNSSReceiver
            fields = '__all__'

    filterset_class = FlagExampleFilter
    model = GNSSReceiver

# now filtering by flags works:
# e.g.:  /?constellation=GPS&constellation=GLONASS