Metadata-Version: 2.1
Name: django_enum_choices
Version: 2.1.1
Summary: A custom Django field able to use subclasses of Python's internal `Enum` class as choices
Home-page: https://github.com/HackSoftware/django-enum-choices
Author: Vasil Slavov
Author-email: vasil.slavov@hacksoft.io
License: MIT
Description: 
        # django-enum-choices
        
        A custom Django choice field to use with [Python enums.](https://docs.python.org/3/library/enum.html)
        
        [![PyPI version](https://badge.fury.io/py/django-enum-choices.svg)](https://badge.fury.io/py/django-enum-choices)
        
        ## Table of Contents
        
        - [django-enum-choices](#django-enum-choices)
          - [Table of Contents](#table-of-contents)
          - [Installation](#installation)
          - [Basic Usage](#basic-usage)
          - [Choice builders](#choice-builders)
          - [Usage inside the admin panel](#usage-in-the-admin-panel)
          - [Usage with forms](#usage-with-forms)
            - [Usage with `django.forms.ModelForm`](#usage-with-djangoformsmodelform)
            - [Usage with `django.forms.Form`](#usage-with-djangoformsform)
          - [Usage with `django-filter`](#usage-with-django-filter)
            - [By using a `Meta` inner class and inheriting from `EnumChoiceFilterMixin`](#by-using-a-meta-inner-class-and-inheriting-from-enumchoicefiltermixin)
            - [By declaring the field explicitly on the `FilterSet`](#by-declaring-the-field-explicitly-on-the-filterset)
          - [Postgres ArrayField Usage](#postgres-arrayfield-usage)
          - [Usage with Django Rest Framework](#usage-with-django-rest-framework)
            - [Using `serializers.ModelSerializer` with `EnumChoiceModelSerializerMixin`](#using-serializersmodelserializer-with-enumchoicemodelserializermixin)
            - [Using `serializers.ModelSerializer` without `EnumChoiceModelSerializerMixin`](#using-serializersmodelserializer-without-enumchoicemodelserializermixin)
            - [Using a subclass of `serializers.Serializer`](#using-a-subclass-of-serializersserializer)
            - [Serializing PostgreSQL ArrayField](#serializing-postgresql-arrayfield)
          - [Implementation details](#implementation-details)
          - [Using Python's `enum.auto`](#using-pythons-enumauto)
          - [Development](#development)
        
        ## Installation
        
        ```bash
        pip install django-enum-choices
        ```
        
        ## Basic Usage
        
        ```python
        from enum import Enum
        
        from django.db import models
        
        from django_enum_choices.fields import EnumChoiceField
        
        
        class MyEnum(Enum):
            A = 'a'
            B = 'b'
        
        
        class MyModel(models.Model):
            enumerated_field = EnumChoiceField(MyEnum)
        ```
        
        **Model creation**
        
        ```python
        instance = MyModel.objects.create(enumerated_field=MyEnum.A)
        ```
        
        **Changing enum values**
        
        ```python
        instance.enumerated_field = MyEnum.B
        instance.save()
        ```
        
        **Filtering**
        
        ```python
        MyModel.objects.filter(enumerated_field=MyEnum.A)
        ```
        
        ## Choice builders
        
        `EnumChoiceField` extends `CharField` and generates choices internally. Each choice is generated using something, called a `choice_builder`.
        
        A choice builder function looks like that:
        
        ```python
        def choice_builder(enum: Enum) -> Tuple[str, str]:
            # Some implementation
        ```
        
        If a `choice_builder` argument is passed to a model's `EnumChoiceField`, `django_enum_choices` will use it to generate the choices.
        The `choice_builder` must be a callable that accepts an enumeration choice and returns a tuple,
        containing the value to be saved and the readable value.
        
        By default `django_enum_choices` uses one of the four choice builders defined in `django_enum_choices.choice_builders`, named `value_value`.
        
        It returns a tuple containing the enumeration's value twice:
        
        ```python
        from django_enum_choices.choice_builders import value_value
        
        class MyEnum(Enum):
            A = 'a'
            B = 'b'
        
        print(value_value(MyEnum.A))  # ('a', 'a')
        ```
        
        You can use one of the four default ones that fits your needs:
        
        * `value_value`
        * `attribute_value`
        * `value_attribute`
        * `attribute_attribute`
        
        For example:
        
        ```python
        from django_enum_choices.choice_builders import attribute_value
        
        class MyEnum(Enum):
            A = 'a'
            B = 'b'
        
        class CustomReadableValueEnumModel(models.Model):
            enumerated_field = EnumChoiceField(
                MyEnum,
                choice_builder=attribute_value
            )
        ```
        
        The resulting choices for `enumerated_field` will be `(('A', 'a'), ('B', 'b'))`
        
        You can also define your own choice builder:
        
        ```python
        class MyEnum(Enum):
            A = 'a'
            B = 'b'
        
        def choice_builder(choice: Enum) -> Tuple[str, str]:
            return choice.value, choice.value.upper() + choice.value
        
        class CustomReadableValueEnumModel(models.Model):
            enumerated_field = EnumChoiceField(
                MyEnum,
                choice_builder=choice_builder
            )
        ```
        
        Which will result in the following choices `(('a', 'Aa'), ('b', 'Bb'))`
        
        The values in the returned from `choice_builder` tuple will be cast to strings before being used.
        
        
        ## Usage in the admin panel
        
        Model fields, defined as `EnumChoiceField` can be used with almost all of the admin panel's
        standard functionallities.
        
        One exception from this their usage in `list_filter`.
        
        If you need an `EnumChoiceField` inside a `ModelAdmin`'s `list_filter`, you can use the following
        options:
        
        * Define the entry insite the list filter as a tuple, containing the field's name and `django_enum_choices.admin.EnumChoiceListFilter`
        
        ```python
        from django.contrib import admin
        
        from django_enum_choices.admin import EnumChoiceListFilter
        
        from .models import MyModel
        
        @admin.register(MyModel)
        class MyModelAdmin(admin.ModelAdmin):
            list_filter = [('enumerated_field', EnumChoiceListFilter)]
        ```
        
        * Set `DJANGO_ENUM_CHOICES_REGISTER_LIST_FILTER` inside your settings to `True`, which will automatically set the `EnumChoiceListFilter` class to all
        `list_filter` fields that are instances of `EnumChoiceField`. This way, they can be declared directly in the `list_filter` iterable:
        
        ```python
        from django.contrib import admin
        
        from .models import MyModel
        
        @admin.register(MyModel)
        class MyModelAdmin(admin.ModelAdmin):
            list_filter = ('enumerated_field', )
        ```
        
        
        ## Usage with forms
        
        There are 2 rules of thumb:
        
        1. If you use a `ModelForm`, everything will be taken care of automatically.
        2. If you use a `Form`, you need to take into account what `Enum` and `choice_builder` you are using.
        
        
        ### Usage with `django.forms.ModelForm`
        
        ```python
        from .models import MyModel
        
        class ModelEnumForm(forms.ModelForm):
            class Meta:
                model = MyModel
                fields = ['enumerated_field']
        
        form = ModelEnumForm({
            'enumerated_field': 'a'
        })
        
        form.is_valid()
        
        print(form.save(commit=True))  # <MyModel: MyModel object (12)>
        ```
        
        ### Usage with `django.forms.Form`
        
        If you are using the default `value_value` choice builder, you can just do that:
        
        ```python
        from django_enum_choices.forms import EnumChoiceField
        
        from .enumerations import MyEnum
        
        class StandardEnumForm(forms.Form):
            enumerated_field = EnumChoiceField(MyEnum)
        
        form = StandardEnumForm({
            'enumerated_field': 'a'
        })
        form.is_valid()
        
        print(form.cleaned_data)  # {'enumerated_field': <MyEnum.A: 'a'>}
        ```
        
        If you are passing a different choice builder, you have to also pass it to the form field:
        
        ```python
        from .enumerations import MyEnum
        
        def custom_choice_builder(choice):
            return 'Custom_' + choice.value, choice.value
        
        class CustomChoiceBuilderEnumForm(forms.Form):
            enumerated_field = EnumChoiceField(
                MyEnum,
                choice_builder=custom_choice_builder
            )
        
        form = CustomChoiceBuilderEnumForm({
            'enumerated_field': 'Custom_a'
        })
        
        form.is_valid()
        
        print(form.cleaned_data)  # {'enumerated_field': <MyEnum.A: 'a'>}
        ```
        
        ## Usage with `django-filter`
        
        As with forms, there are 2 general rules of thumb:
        
        1. If you have declared an `EnumChoiceField` in the `Meta.fields` for a given `Meta.model`, you need to inherit `EnumChoiceFilterMixin` in your filter class & everything will be taken care of automatically.
        2. If you are declaring an explicit field, without a model, you need to specify the `Enum` class & the `choice_builder`, if a custom one is used.
        
        ### By using a `Meta` inner class and inheriting from `EnumChoiceFilterMixin`
        
        ```python
        import django_filters as filters
        
        from django_enum_choices.filters import EnumChoiceFilterMixin
        
        class ImplicitFilterSet(EnumChoiceFilterSetMixin, filters.FilterSet):
            class Meta:
                model = MyModel
                fields = ['enumerated_field']
        
        filters = {
            'enumerated_field': 'a'
        }
        filterset = ImplicitFilterSet(filters)
        
        print(filterset.qs.values_list('enumerated_field', flat=True))
        # <QuerySet [<MyEnum.A: 'a'>, <MyEnum.A: 'a'>, <MyEnum.A: 'a'>]>
        ```
        
        The `choice_builder` argument can be passed to `django_enum_choices.filters.EnumChoiceFilter` as well when using the field explicitly. When using `EnumChoiceFilterSetMixin`, the `choice_builder` is determined from the model field, for the fields defined inside the `Meta` inner class.
        
        ```python
        import django_filters as filters
        
        from django_enum_choices.filters import EnumChoiceFilter
        
        def custom_choice_builder(choice):
            return 'Custom_' + choice.value, choice.value
        
        class ExplicitCustomChoiceBuilderFilterSet(filters.FilterSet):
            enumerated_field = EnumChoiceFilter(
                MyEnum,
                choice_builder=custom_choice_builder
            )
        
        filters = {
            'enumerated_field': 'Custom_a'
        }
        filterset = ExplicitCustomChoiceBuilderFilterSet(filters, MyModel.objects.all())
        
        print(filterset.qs.values_list('enumerated_field', flat=True))  # <QuerySet [<MyEnum.A: 'a'>, <MyEnum.A: 'a'>, <MyEnum.A: 'a'>]>
        ```
        
        
        ### By declaring the field explicitly on the `FilterSet`
        
        ```python
        import django_filters as filters
        
        from django_enum_choices.filters import EnumChoiceFilter
        
        class ExplicitFilterSet(filters.FilterSet):
            enumerated_field = EnumChoiceFilter(MyEnum)
        
        
        filters = {
            'enumerated_field': 'a'
        }
        filterset = ExplicitFilterSet(filters, MyModel.objects.all())
        
        print(filterset.qs.values_list('enumerated_field', flat=True))  # <QuerySet [<MyEnum.A: 'a'>, <MyEnum.A: 'a'>, <MyEnum.A: 'a'>]>
        ```
        
        ## Postgres ArrayField Usage
        
        You can use `EnumChoiceField` as a child field of an Postgres `ArrayField`.
        
        ```python
        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**
        
        ```python
        instance = MyModelMultiple.objects.create(enumerated_field=[MyEnum.A, MyEnum.B])
        ```
        
        **Changing enum values**
        
        ```python
        instance.enumerated_field = [MyEnum.B]
        instance.save()
        ```
        
        ## Usage with Django Rest Framework
        
        As with forms & filters, there are 2 general rules of thumb:
        
        1. If you are using a `ModelSerializer` and you inherit `EnumChoiceModelSerializerMixin`, everything will be taken care of automatically.
        2. If you are using a `Serializer`, you need to take the `Enum` class & `choice_builder` into acount.
        
        ### Using `serializers.ModelSerializer` with `EnumChoiceModelSerializerMixin`
        
        ```python
        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.
        
        When using the `EnumChoiceModelSerializerMixin` with DRF's `serializers.ModelSerializer`, the `choice_builder` is automatically passed from the model field to the serializer field.
        
        ### Using `serializers.ModelSerializer` without `EnumChoiceModelSerializerMixin`
        
        ```python
        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()
        ```
        
        If you are using a custom `choice_builder`, you need to pass that too.
        
        ```python
        def custom_choice_builder(choice):
            return 'Custom_' + choice.value, choice.value
        
        class CustomChoiceBuilderSerializer(serializers.Serializer):
            enumerted_field = EnumChoiceField(
                MyEnum,
                choice_builder=custom_choice_builder
            )
        
        serializer = CustomChoiceBuilderSerializer({
            'enumerated_field': MyEnum.A
        })
        
        data = serializer.data # {'enumerated_field': 'Custom_a'}
        ```
        
        ### Using a subclass of `serializers.Serializer`
        
        ```python
        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'>)])
        ```
        
        If you are using a custom `choice_builder`, you need to pass that too.
        
        ### Serializing PostgreSQL ArrayField
        
        `django-enum-choices` exposes a `MultipleEnumChoiceField` that can be used for serializing arrays of enumerations.
        
        **Using a subclass of `serializers.Serializer`**
        
        ```python
        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`**
        
        ```python
        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.
        
        ## Implementation details
        
        * `EnumChoiceField` is a subclass of `CharField`.
        * Only subclasses of `Enum` are valid arguments for `EnumChoiceField`.
        * `max_length`, if passed, is ignored. `max_length` is automatically calculated from the longest choice.
        * `choices` are generated using a special `choice_builder` function, which accepts an enumeration and returns a tuple of 2 items.
          * Four choice builder functions are defined inside `django_enum_choices.choice_builders`
          * By default the `value_value` choice builder is used. It produces the choices from the values in the enumeration class, like `(enumeration.value, enumeration.value)`
          * `choice_builder` can be overriden by passing a callable to the `choice_builder` keyword argument of `EnumChoiceField`.
          * All values returned from the choice builder **will be cast to strings** when generating choices.
        
        For example, lets have the following case:
        
        ```python
        class Value:
            def __init__(self, value):
                self.value = value
        
            def __str__(self):
                return self.value
        
        
        class CustomObjectEnum(Enum):
            A = Value(1)
            B = Value('B')
        
        	# The default choice builder `value_value` is being used
        
        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:
        
        ```python
        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_`:
        
        ```python
        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
        
        ```bash
        git clone https://github.com/HackSoftware/django-enum-choices.git
        cd django_enum_choices
        pip install -e .[dev]
        ```
        
        Linting and running the tests:
        ```bash
        tox
        ```
        
Platform: UNKNOWN
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Framework :: Django
Classifier: Framework :: Django :: 1.11
Classifier: Framework :: Django :: 2.1
Classifier: Framework :: Django :: 2.2
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.5.0
Description-Content-Type: text/markdown
Provides-Extra: dev
