Package: django-enum-choices
django-enum-choices (DEPRECATED)A custom Django choice field to use withPython enums.⚠️ Disclaimer ⚠️Starting with version 3.0, Django started supportingEnumerations for model field choicesand we recommend using this as a native Django feature, instead ofdjango-enum-choicesIf you are usingdjango-enum-choicesand you want to upgrade your project to Django >=3.0you can refer to the guide in the wiki:Migrating to Django 3Table of Contentsdjango-enum-choicesTable of ContentsInstallationBasic UsageChoice buildersChanging/Removing options from enumerationsChanging optionsRemoving optionsUsage inside the admin panelUsage with formsUsage withdjango.forms.ModelFormUsage withdjango.forms.FormUsage withdjango-filterBy using aMetainner class and inheriting fromEnumChoiceFilterMixinBy declaring the field explicitly on theFilterSetPostgres ArrayField UsageUsage with Django Rest FrameworkUsingserializers.ModelSerializerwithEnumChoiceModelSerializerMixinUsingserializers.ModelSerializerwithoutEnumChoiceModelSerializerMixinUsing a subclass ofserializers.SerializerSerializing PostgreSQL ArrayFieldImplementation detailsUsing Python'senum.autoDevelopmentInstallationpipinstalldjango-enum-choicesBasic UsagefromenumimportEnumfromdjango.dbimportmodelsfromdjango_enum_choices.fieldsimportEnumChoiceFieldclassMyEnum(Enum):A='a'B='b'classMyModel(models.Model):enumerated_field=EnumChoiceField(MyEnum)Model creationinstance=MyModel.objects.create(enumerated_field=MyEnum.A)Changing enum valuesinstance.enumerated_field=MyEnum.Binstance.save()FilteringMyModel.objects.filter(enumerated_field=MyEnum.A)Choice buildersEnumChoiceFieldextendsCharFieldand generates choices internally. Each choice is generated using something, called achoice_builder.A choice builder function looks like that:defchoice_builder(enum:Enum)->Tuple[str,str]:# Some implementationIf achoice_builderargument is passed to a model'sEnumChoiceField,django_enum_choiceswill use it to generate the choices.
Thechoice_buildermust be a callable that accepts an enumeration choice and returns a tuple,
containing the value to be saved and the readable value.By defaultdjango_enum_choicesuses one of the four choice builders defined indjango_enum_choices.choice_builders, namedvalue_value.It returns a tuple containing the enumeration's value twice:fromdjango_enum_choices.choice_buildersimportvalue_valueclassMyEnum(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_valueattribute_valuevalue_attributeattribute_attributeFor example:fromdjango_enum_choices.choice_buildersimportattribute_valueclassMyEnum(Enum):A='a'B='b'classCustomReadableValueEnumModel(models.Model):enumerated_field=EnumChoiceField(MyEnum,choice_builder=attribute_value)The resulting choices forenumerated_fieldwill be(('A', 'a'), ('B', 'b'))You can also define your own choice builder:classMyEnum(Enum):A='a'B='b'defchoice_builder(choice:Enum)->Tuple[str,str]:returnchoice.value,choice.value.upper()+choice.valueclassCustomReadableValueEnumModel(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 fromchoice_buildertuple will be cast to strings before being used.Changing/Removing options from enumerationsAt any given point of time all instances of a model that hasEnumChoiceFieldmust have a value that is currently present in the enumeration.
When changing or removing an option from the enumeration, a custom database migration must be made prior to the enumeration change.Changing optionsWhen chaging options we'll need several operations:Inserting a new option with the new value that we wantMigrating all instances from the old option to the new oneRemoving the old option and renaming the old oneRemoving the custom data migration code, so migrations can be run on a clean database without anAttributeErrorocurringExample:Initial setup:classMyEnum(Enum):A='a'B='b'# Desired change:# A = 'a_updated'classMyModel(models.Model):enumerated_field=EnumChoiceField(MyEnum)Insert a new option with the desired new value:classMyEnum:A_UPDATED='a_updated'A='a'B='b'pythonmanage.pymakemigrationsMigrate model instancespythonmanage.pymakemigrationsapp_label--empty# migration_name.pydefforwards(apps,schema_editor):MyModel=apps.get_model('app_label','MyModel')MyModel.objects.filter(enumerated_field=MyEnum.A).update(enumerated_field=MyEnum.A_UPDATED)classMigration(migrations.Migration):...operations=[migrations.RunPython(forwards),]pythonmanage.pymigrateRemove old option and rename new oneclassMyEnum:A='a_updated'B='b'pythonmanage.pymakemigrations
pythonmanage.pymigrateRemove custom data migration code# migration_name.pydefforwards(apps,schema_editor):passclassMigration(migrations.Migration):...operations=[migrations.RunPython(forwards),]Removing optionsRemoving options from the enumeration includes several operations as well:Optional: Making the field nullable (if we want our existing instances' values to beNone)Migrating all instances to a new option (or None)Removing the option from the enumerationRemoving the custom data migration code, so migrations can be run on a clean database without anAttributeErrorocurringExample:Initial setup:classMyEnum(Enum):A='a'B='b'# Desired change:# class MyEnum(Enum):#     A = 'a'classMyModel(models.Model):enumerated_field=EnumChoiceField(MyEnum)Optional: Make the field nullable (if you want your existing instances to have aNonevalue)classMyModel(models.Model):enumerated_field=EnumChoiceField(MyEnum,blank=True,null=True)pythonmanage.pymakemigrationsMigrate model instancespythonmanage.pymakemigrationsapp_label--empty# migration_name.pydefforwards(apps,schema_editor):MyModel=apps.get_model('app_label','MyModel')MyModel.objects.filter(enumerated_field=MyEnum.B).update(enumerated_field=MyEnum.A)# OR MyModel.objects.filter(enumerated_field=MyEnum.B).update(enumerated_field=None)classMigration(migrations.Migration):...operations=[migrations.RunPython(forwards),]pythonmanage.pymigrateRemove old optionclassMyEnum:A='apythonmanage.pymakemigrations
pythonmanage.pymigrateRemove custom data migration code# migration_name.pydefforwards(apps,schema_editor):passclassMigration(migrations.Migration):...operations=[migrations.RunPython(forwards),]Usage in the admin panelModel fields, defined asEnumChoiceFieldcan be used with almost all of the admin panel's
standard functionallities.One exception from this their usage inlist_filter.If you need anEnumChoiceFieldinside aModelAdmin'slist_filter, you can use the following
options:Define the entry insite the list filter as a tuple, containing the field's name anddjango_enum_choices.admin.EnumChoiceListFilterfromdjango.contribimportadminfromdjango_enum_choices.adminimportEnumChoiceListFilterfrom.modelsimportMyModel@admin.register(MyModel)classMyModelAdmin(admin.ModelAdmin):list_filter=[('enumerated_field',EnumChoiceListFilter)]SetDJANGO_ENUM_CHOICES_REGISTER_LIST_FILTERinside your settings toTrue, which will automatically set theEnumChoiceListFilterclass to alllist_filterfields that are instances ofEnumChoiceField. This way, they can be declared directly in thelist_filteriterable:fromdjango.contribimportadminfrom.modelsimportMyModel@admin.register(MyModel)classMyModelAdmin(admin.ModelAdmin):list_filter=('enumerated_field',)Usage with formsThere are 2 rules of thumb:If you use aModelForm, everything will be taken care of automatically.If you use aForm, you need to take into account whatEnumandchoice_builderyou are using.Usage withdjango.forms.ModelFormfrom.modelsimportMyModelclassModelEnumForm(forms.ModelForm):classMeta:model=MyModelfields=['enumerated_field']form=ModelEnumForm({'enumerated_field':'a'})form.is_valid()print(form.save(commit=True))# <MyModel: MyModel object (12)>Usage withdjango.forms.FormIf you are using the defaultvalue_valuechoice builder, you can just do that:fromdjango_enum_choices.formsimportEnumChoiceFieldfrom.enumerationsimportMyEnumclassStandardEnumForm(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:from.enumerationsimportMyEnumdefcustom_choice_builder(choice):return'Custom_'+choice.value,choice.valueclassCustomChoiceBuilderEnumForm(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 withdjango-filterAs with forms, there are 2 general rules of thumb:If you have declared anEnumChoiceFieldin theMeta.fieldsfor a givenMeta.model, you need to inheritEnumChoiceFilterMixinin your filter class & everything will be taken care of automatically.If you are declaring an explicit field, without a model, you need to specify theEnumclass & thechoice_builder, if a custom one is used.By using aMetainner class and inheriting fromEnumChoiceFilterMixinimportdjango_filtersasfiltersfromdjango_enum_choices.filtersimportEnumChoiceFilterMixinclassImplicitFilterSet(EnumChoiceFilterSetMixin,filters.FilterSet):classMeta:model=MyModelfields=['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'>]>Thechoice_builderargument can be passed todjango_enum_choices.filters.EnumChoiceFilteras well when using the field explicitly. When usingEnumChoiceFilterSetMixin, thechoice_builderis determined from the model field, for the fields defined inside theMetainner class.importdjango_filtersasfiltersfromdjango_enum_choices.filtersimportEnumChoiceFilterdefcustom_choice_builder(choice):return'Custom_'+choice.value,choice.valueclassExplicitCustomChoiceBuilderFilterSet(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 theFilterSetimportdjango_filtersasfiltersfromdjango_enum_choices.filtersimportEnumChoiceFilterclassExplicitFilterSet(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 UsageYou can useEnumChoiceFieldas a child field of an PostgresArrayField.fromdjango.dbimportmodelsfromdjango.contrib.postgres.fieldsimportArrayFieldfromdjango_enum_choices.fieldsimportEnumChoiceFieldfromenumimportEnumclassMyEnum(Enum):A='a'B='b'classMyModelMultiple(models.Model):enumerated_field=ArrayField(base_field=EnumChoiceField(MyEnum))Model Creationinstance=MyModelMultiple.objects.create(enumerated_field=[MyEnum.A,MyEnum.B])Changing enum valuesinstance.enumerated_field=[MyEnum.B]instance.save()Usage with Django Rest FrameworkAs with forms & filters, there are 2 general rules of thumb:If you are using aModelSerializerand you inheritEnumChoiceModelSerializerMixin, everything will be taken care of automatically.If you are using aSerializer, you need to take theEnumclass &choice_builderinto acount.Usingserializers.ModelSerializerwithEnumChoiceModelSerializerMixinfromrest_frameworkimportserializersfromdjango_enum_choices.serializersimportEnumChoiceModelSerializerMixinclassImplicitMyModelSerializer(EnumChoiceModelSerializerMixin,serializers.ModelSerializer):classMeta:model=MyModelfields=('enumerated_field',)By defaultModelSerializer.build_standard_fieldcoerces any field that has a model field with choices toChoiceFieldwhich returns the value directly.Since enum values resembleEnumClass.ENUM_INSTANCEthey won't be able to be encoded by theJSONEncoderwhen being passed to aResponse.That's why we need the mixin.When using theEnumChoiceModelSerializerMixinwith DRF'sserializers.ModelSerializer, thechoice_builderis automatically passed from the model field to the serializer field.Usingserializers.ModelSerializerwithoutEnumChoiceModelSerializerMixinfromrest_frameworkimportserializersfromdjango_enum_choices.serializersimportEnumChoiceFieldclassMyModelSerializer(serializers.ModelSerializer):enumerated_field=EnumChoiceField(MyEnum)classMeta:model=MyModelfields=('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 customchoice_builder, you need to pass that too.defcustom_choice_builder(choice):return'Custom_'+choice.value,choice.valueclassCustomChoiceBuilderSerializer(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 ofserializers.Serializerfromrest_frameworkimportserializersfromdjango_enum_choices.serializersimportEnumChoiceFieldclassMySerializer(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 customchoice_builder, you need to pass that too.Serializing PostgreSQL ArrayFielddjango-enum-choicesexposes aMultipleEnumChoiceFieldthat can be used for serializing arrays of enumerations.Using a subclass ofserializers.Serializerfromrest_frameworkimportserializersfromdjango_enum_choices.serializersimportMultipleEnumChoiceFieldclassMultipleMySerializer(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 ofserializers.ModelSerializerclassImplicitMultipleMyModelSerializer(EnumChoiceModelSerializerMixin,serializers.ModelSerializer):classMeta:model=MyModelMultiplefields=('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()TheEnumChoiceModelSerializerMixindoes not need to be used ifenumerated_fieldis defined on the serializer class explicitly.Implementation detailsEnumChoiceFieldis a subclass ofCharField.Only subclasses ofEnumare valid arguments forEnumChoiceField.max_length, if passed, is ignored.max_lengthis automatically calculated from the longest choice.choicesare generated using a specialchoice_builderfunction, which accepts an enumeration and returns a tuple of 2 items.Four choice builder functions are defined insidedjango_enum_choices.choice_buildersBy default thevalue_valuechoice builder is used. It produces the choices from the values in the enumeration class, like(enumeration.value, enumeration.value)choice_buildercan be overriden by passing a callable to thechoice_builderkeyword argument ofEnumChoiceField.All values returned from the choice builderwill be cast to stringswhen generating choices.For example, lets have the following case:classValue:def__init__(self,value):self.value=valuedef__str__(self):returnself.valueclassCustomObjectEnum(Enum):A=Value(1)B=Value('B')# The default choice builder `value_value` is being usedclassSomeModel(models.Model):enumerated_field=EnumChoiceField(CustomObjectEnum)We'll have the following:SomeModel.enumerated_field.choices == (('1', '1'), ('B', 'B'))SomeModel.enumerated_field.max_length == 3Using Python'senum.autoenum.autocan be used for shorthand enumeration definitions:fromenumimportEnum,autoclassAutoEnum(Enum):A=auto()# 1B=auto()# 2classSomeModel(models.Model):enumerated_field=EnumChoiceField(Enum)This will result in the following:SomeModel.enumerated_field.choices == (('1', '1'), ('2', '2'))OverridinngautobehaviourCustom values for enumerations, created byauto, can be defined by
subclassing anEnumthat defines_generate_next_value_:classCustomAutoEnumValueGenerator(Enum):def_generate_next_value_(name,start,count,last_values):return{'A':'foo','B':'bar'}[name]classCustomAutoEnum(CustomAutoEnumValueGenerator):A=auto()B=auto()The above will assign the values mapped in the dictionary as values to attributes inCustomAutoEnum.DevelopmentPrerequisitesSQLite3PostgreSQL serverPython >= 3.5 virtual environmentFork the repositorygitclonehttps://github.com/your-user-name/django-enum-choices.gitdjango-enum-choices-yournamecddjango-enum-choices-yourname
gitremoteaddupstreamhttps://github.com/HackSoftware/django-enum-choices.gitInstall the requirements:pipinstall-e.[dev]Linting and running the tests:tox