Skip to main content

A custom Django field able to use subclasses of Python's internal `Enum` class as choices

Project description

django-enum-choices

A custom Django choice field to use with Python enums.

PyPI version

Table of Contents

Installation

pip install django-enum-choices

Basic Usage

from django.db import models

from django_enum_choices.fields import EnumChoiceField


from enum import Enum

class MyEnum(Enum):
    A = 'a'
    B = 'b'


class MyModel(models.Model):
    enumerated_field = EnumChoiceField(MyEnum)

Model creation

instance = MyModel.objects.create(enumerated_field=MyEnum.A)

Changing enum values

instance.enumerated_field = MyEnum.B
instance.save()

Filtering

MyModel.objects.filter(enumerated_field=MyEnum.A)

Postgres ArrayField Usage

from django.db import models
from django.contrib.postgres.fields import ArrayField

from django_enum_choices.fields import EnumChoiceField

from enum import Enum

class MyEnum(Enum):
    A = 'a'
    B = 'b'

class MyModelMultiple(models.Model):
    enumerated_field = ArrayField(
        base_field=EnumChoiceField(MyEnum)
    )

Model Creation

instance = MyModelMultiple.objects.create(enumerated_field=[MyEnum.A, MyEnum.B])

Changing enum values

instance.enumerated_field = [MyEnum.B]
instance.save()

Usage with Django Rest Framework

Using a subclass of serializers.Serializer

from rest_framework import serializers

from django_enum_choices.serializers import EnumChoiceField

class MySerializer(serializers.Serializer):
    enumerated_field = EnumChoiceField(MyEnum)

# Serialization:
serializer = MySerializer({
    'enumerated_field': MyEnum.A
})
data = serializer.data  # {'enumerated_field': 'a'}

# Deserialization:
serializer = MySerializer(data={
    'enumerated_field': 'a'
})
serializer.is_valid()
data = serializer.validated_data  # OrderedDict([('enumerated_field', <MyEnum.A: 'a'>)])

Using a subclass of serializers.ModelSerializer

from rest_framework import serializers

from django_enum_choices.serializers import EnumChoiceField

class MyModelSerializer(serializers.ModelSerializer):
    enumerated_field = EnumChoiceField(MyEnum)

    class Meta:
        model = MyModel
        fields = ('enumerated_field', )

# Serialization:
instance = MyModel.objects.create(enumerated_field=MyEnum.A)
serializer = MyModelSerializer(instance)
data = serializer.data  # {'enumerated_field': 'a'}

# Saving:
serializer = MyModelSerializer(data={
    'enumerated_field': 'a'
})
serializer.is_valid()
serializer.save()

Caveat

If you don't explicitly specify the enumerated_field = EnumChoiceField(MyEnum), then you need to include the EnumChoiceModelSerializerMixin:

from rest_framework import serializers

from django_enum_choices.serializers import EnumChoiceModelSerializerMixin

class ImplicitMyModelSerializer(
    EnumChoiceModelSerializerMixin,
    serializers.ModelSerializer
):
    class Meta:
        model = MyModel
        fields = ('enumerated_field', )

By default ModelSerializer.build_standard_field coerces any field that has a model field with choices to ChoiceField which returns the value directly.

Since enum values resemble EnumClass.ENUM_INSTANCE they won't be able to be encoded by the JSONEncoder when being passed to a Response.

That's why we need the mixin.

Serializing PostgreSQL ArrayField

django-enum-choices exposes a MultipleEnumChoiceField that can be used for serializing arrays of enumerations.

Using a subclass of serializers.Serializer

from rest_framework import serializers

from django_enum_choices.serializers import MultipleEnumChoiceField

class MultipleMySerializer(serializers.Serializer):
    enumerated_field = MultipleEnumChoiceField(MyEnum)

# Serialization:
serializer = MultipleMySerializer({
    'enumerated_field': [MyEnum.A, MyEnum.B]
})
data = serializer.data  # {'enumerated_field': ['a', 'b']}

# Deserialization:
serializer = MultipleMySerializer(data={
    'enumerated_field': ['a', 'b']
})
serializer.is_valid()
data = serializer.validated_data  # OrderedDict([('enumerated_field', [<MyEnum.A: 'a'>, <MyEnum.B: 'b'>])])

Using a subclass of serializers.ModelSerializer

class ImplicitMultipleMyModelSerializer(
    EnumChoiceModelSerializerMixin,
    serializers.ModelSerializer
):
    class Meta:
        model = MyModelMultiple
        fields = ('enumerated_field', )

# Serialization:
instance = MyModelMultiple.objects.create(enumerated_field=[MyEnum.A, MyEnum.B])
serializer = ImplicitMultipleMyModelSerializer(instance)
data = serializer.data  # {'enumerated_field': ['a', 'b']}

# Saving:
serializer = ImplicitMultipleMyModelSerializer(data={
    'enumerated_field': ['a', 'b']
})
serializer.is_valid()
serializer.save()

The EnumChoiceModelSerializerMixin does not need to be used if enumerated_field is defined on the serializer class explicitly.

Customizing readable values

If a get_readable_value method is provided, django_enum_choices will use it to produce the readable values that are written in the database:

class CustomReadableValueEnum(Enum):
    A = 'a'
    B = 'b'

    @classmethod
    def get_readable_value(cls, choice):
        return cls(choice).value.upper()

Using the above class as an enum_class argument to django_enum_choices.fields.EnumChoiceField will produce the choices for the database as (('a', 'A'), ('b', 'B'))

Implementation details

  • EnumChoiceField is a subclass of CharField.
  • Only subclasses of Enum are valid arguments for EnumChoiceField.
  • choices are generated from the values of the enumeration, like (str(value), str(value)), meaning you can put any valid Python object there.
  • max_length, if passed, is ignored. max_length is automatically calculated from the longest choice.

For example, lets have the following case:

class Value:
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return self.value


class CustomObjectEnum(Enum):
    A = Value(1)
    B = Value('B')


class SomeModel(models.Model):
    enumerated_field = EnumChoiceField(CustomObjectEnum)

We'll have the following:

  • SomeModel.enumerated_field.choices == (('1', '1'), ('B', 'B'))
  • SomeModel.enumerated_field.max_length == 3

Using Python's enum.auto

enum.auto can be used for shorthand enumeration definitions:

from enum import Enum, auto

class AutoEnum(Enum):
    A = auto()  # 1
    B = auto()  # 2

class SomeModel(models.Model):
    enumerated_field = EnumChoiceField(Enum)

This will result in the following:

  • SomeModel.enumerated_field.choices == (('1', '1'), ('2', '2'))

Overridinng auto behaviour Custom values for enumerations, created by auto, can be defined by subclassing an Enum that defines _generate_next_value_:

class CustomAutoEnumValueGenerator(Enum):
    def _generate_next_value_(name, start, count, last_values):
        return {
            'A': 'foo',
            'B': 'bar'
        }[name]


class CustomAutoEnum(CustomAutoEnumValueGenerator):
    A = auto()
    B = auto()

The above will assign the values mapped in the dictionary as values to attributes in CustomAutoEnum.

Development

Prerequisites

  • SQLite3
  • PostgreSQL server
  • Python >= 3.5 virtual environment
git clone https://github.com/HackSoftware/django-enum-choices.git
cd django_enum_choices
pip install -e .[dev]

Linting and running the tests:

tox

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_enum_choices-1.0.2.tar.gz (17.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_enum_choices-1.0.2-py2.py3-none-any.whl (20.2 kB view details)

Uploaded Python 2Python 3

File details

Details for the file django_enum_choices-1.0.2.tar.gz.

File metadata

  • Download URL: django_enum_choices-1.0.2.tar.gz
  • Upload date:
  • Size: 17.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.32.2 CPython/3.6.7

File hashes

Hashes for django_enum_choices-1.0.2.tar.gz
Algorithm Hash digest
SHA256 8496e4c4fc589d53ae82ad2fea15599a7b97c60bdd2304f8136a814d58743087
MD5 23506dc0f2b403f6ecc9b2e99a0ea239
BLAKE2b-256 82dc622efed0e0f9d75afcc2fc13c9b353357fffd6af385190f1016b2621d75d

See more details on using hashes here.

File details

Details for the file django_enum_choices-1.0.2-py2.py3-none-any.whl.

File metadata

  • Download URL: django_enum_choices-1.0.2-py2.py3-none-any.whl
  • Upload date:
  • Size: 20.2 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.32.2 CPython/3.6.7

File hashes

Hashes for django_enum_choices-1.0.2-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 5634f51971a0f7640f87e725acc3bd8ada18ded47a4ca449acdb0e2c15bedb27
MD5 a24ad7aa4aff72dc8f4241afd5620803
BLAKE2b-256 2c061a50d3557c94a88d9a6f85e92976d8f01ac3d019bc581a7c296dae5c00d4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page