Eccentric Enumerations

Python’s Enum type is extremely lenient. Enumeration values may be any hashable type and values of the same enumeration may be of different types.

For use in databases it is recommended to use more strict enumeration types that only allow a single value type of either string or integer. If additional properties need to be associated with enumeration values, a library like enum-properties should be used to store them on the enumeration value classes.

However, the goal of django-enum is to provide as complete a bridge as possible between Python and the database so eccentric enumerations are supported with caveats. The following enumeration value types are supported out of the box, and map to the obvious Django model field type:

  • int

  • str

  • float

  • datetime.date

  • datetime.datetime

  • datetime.time

  • datetime.timedelta

  • decimal.Decimal

While it is mostly not advisable to use eccentric enumerations, there may be some compelling reasons to do so. For example, it may make sense in situations where the database will be used in a non-Python context and the enumeration values need to retain their native meaning.

Mixed Value Enumerations

Mixed value enumerations are supported. For example:

from enum import Enum

class EccentricEnum(Enum):

    NONE = None
    VAL1 = 1
    VAL2 = '2.0'
    VAL3 = 3.0
    VAL4 = Decimal('4.5')

EnumField will determine the most appropriate database column type to store the enumeration by trying each of the supported primitive types in order and selecting the first one that is symmetrically coercible to and from each enumeration value. None values are allowed and do not take part in the primitive type selection. In the above example, the database column type would be a string.

Note

If none of the supported primitive types are symmetrically coercible EnumField will not be able to determine an appropriate column type and a ValueError will be raised.

In these cases, or to override the primitive type selection made by EnumField, pass the primitive parameter. It may be necessary to extend one of the supported primitives to make it coercible. It may also be necessary to override the Enum’s _missing_ method:

# eccentric will be a string
eccentric_str = EnumField(EccentricEnum)

# primitive will be a float
eccentric_float = EnumField(EccentricEnum, primitive=float)

In the above case since None is an enumeration value, EnumField will automatically set null=True on the model field.

Custom Enumeration Values

Warning

There is almost certainly a better way to do what you might be trying to do by writing a custom enumeration value - for example consider using enum-properties to make your enumeration types more robust by pushing more of this functionality on the Enum class itself.

If you must use a custom value type, you can by specifying a symmetrically coercible primitive type. For example Path is already symmetrically coercible to str so this works:

class MyModel(models.Model):

    class PathEnum(Enum):

        USR = Path('/usr')
        USR_LOCAL = Path('/usr/local')
        USR_LOCAL_BIN = Path('/usr/local/bin')

    path = EnumField(PathEnum, primitive=str)

A fully custom value might look like the following (admittedly contrived) example:

class StrProps:
    """
    Wrap a string with some properties.
    """

    _str = ''

    def __init__(self, string):
        self._str = string

    def __str__(self):
        """ coercion to str - str(StrProps('str1')) == 'str1' """
        return self._str

    @property
    def upper(self):
        return self._str.upper()

    @property
    def lower(self):
        return self._str.lower()

    def __eq__(self, other):
        """ Make sure StrProps('str1') == 'str1' """
        if isinstance(other, str):
            return self._str == other
        if other is not None:
            return self._str == other._str
        return False

    def deconstruct(self):
        """Necessary to construct choices and default in migration files"""
        return 'my_module.StrProps', (self._str,), {}


class MyModel(models.Model):

    class StrPropsEnum(Enum):

        STR1 = StrProps('str1')
        STR2 = StrProps('str2')
        STR3 = StrProps('str3')

    str_props = EnumField(StrPropsEnum, primitive=str)