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 -r django_enum_choices/requirements.txt

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.0.tar.gz (11.6 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.0-py2.py3-none-any.whl (17.5 kB view details)

Uploaded Python 2Python 3

File details

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

File metadata

  • Download URL: django_enum_choices-1.0.0.tar.gz
  • Upload date:
  • Size: 11.6 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.0.tar.gz
Algorithm Hash digest
SHA256 04bc167ac3006b341c4e77b454b2d70efc68ad0bd8882d794e82833e578dc407
MD5 247af170b4933bd2f1d060bfe44a7728
BLAKE2b-256 d8b81b6c8499f3baf310dc4e9c74626398edb4766eac40f7de95d7f7ceba0b94

See more details on using hashes here.

File details

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

File metadata

  • Download URL: django_enum_choices-1.0.0-py2.py3-none-any.whl
  • Upload date:
  • Size: 17.5 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.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 8c6908606cb4118b5bb31d94a9bb30b611ca1fd53e0aabe9f3e3e7525ce929fa
MD5 2dc1c797ec12f0d1202995d205ed51d1
BLAKE2b-256 1a10c1b7e05564d8dc1cba612c6bedba1f093e76fe546f53d4ec07bbe9423d1e

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