Django model choices as Enum
Choices for Django model fields as enumeration
pip install django-echoices
.pandoc
is installed./pypi_packager.sh
pip install dist/django_echoices-x.y.z-[...].wheel
, where x.y.z
must be replaced by the actual[...]
depends on your packaging configurationFirst, define your choices enumeration (in your models.py
for example):
from echoices.enums import EChoice
class EStates(EChoice):
# format is: (value -> char or str or int, label -> str)
CREATED = ('c', 'Created')
SUBMITTED = ('s', 'Submitted')
Then, either use a regular model field:
from django.db import models
class MyModel(models.Model):
state = models.CharField(max_length=EStates.max_value_length(),
choices=EStates.choices(),
default=EStates.CREATED.value)
Note: If your value is an int
, you can use models.IntegerField
instead.
You can also use specialized field. Using such a field, you will then only handle Echoice
instances.
from django.db import models
from echoices.fields import make_echoicefield
class MyModel(models.Model):
# `max_length` is set automatically
state = make_echoicefield(EStates, default=EStates.CREATED)
Note: MyModel.state
will be Estates
instance stored in a EStatesField
field. See documentation
for more details.
WARNING: requires special handling of migrations. Read more in the documentation.
You can add your own fields to the value
and label
ones. To do so, you have to override the init() and your
signature must look like: self, value, label, *args
where you replace *args
with your own positional arguments, as
you would do when defining a custom Enum. Do not call the super().init(), as value
and label
are already set
internally by EChoice
.
As when dealing with a derived Enum, you can also add your own methods.
from echoices.enums import EChoice
class EMyChoices(EChoice):
"""Another variant of EChoice with additionnal content"""
MY_CHOICE = (1, 'First choice', 'my additional value')
def __init__(self, value, label, my_arg):
self.my_arg = my_arg
# Note: super().__init__() shall *not* be called!
def show_myarg(self):
"""Used as: EMyChoices.MY_CHOICE.show_myarg()"""
print(self.my_arg)
@classmethod
def show_all(cls):
"""Used as: EMyChoices.show_all()"""
print(", ".join([e.my_arg for e in list(cls)]))
Assume a Context(dict(estates=myapp.models.EStates))
is provided to the following templates.
Fields of the EChoice
can be accessed in the templates as:
{{ estates.CREATED.value }}
{{ estates.CREATED.label }}
EChoice
can also be enumerated:
{% for state in estates %}
{{ state.value }}
{{ state.label }}
{% endfor %}
enums.EChoice
Base enum type. Each enum element is a tuple (value, label)
, where [t]he first element
in each tuple is the actual value to be set on the model, and the second element is the human-readable name
doc. Values must be unique. Can be
derived for further customization.
enums.EOrderedChoice
Supports ordering of elements. EOrderedChoice.choices()
takes an extra optional argument,order
, which supports three values: ‘sorted’, ‘reverse’ or ‘natural’ (default). If sorted
, the choices are ordered
according to their value. If reverse
, the choices are ordered according to their value as if each comparison were
reversed. If natural
, the order is the one used when instantiating the enumeration.
enums.EAutoChoice
Generates auto-incremented numeric values. It’s then used like:
from echoices.enums import EAutoChoice
class EStates(EAutoChoice):
# format is: label -> str
CREATED = 'Created'
SUBMITTED = 'Submitted'
EChoice.__getitem__()
, such that you can retrieve an EChoice
instance using EChoice['my_value']
choices()
generates the choices as expected by a Django model fieldmax_value_length()
returns the max length for the Django model field, if the values are stringsvalues()
returns a list of all the valuesget(value, default=None)
returns the EChoice instance having that value, else returns the defaultfields.EChoiceField
via fields.make_echoicefield()
Deal directly with the enum instances instead of their DB storage value. The specialized field will be derived from amodels.Field
subclass, the internal representation is deduced from the value type. So for example if the values are
strings, then the the EChoiceField
will subclass models.CharField
; and if the values are integers then it will bemodels.IntegerField
. Actually supports str
, int
, float
and (non-null) bool
as enum values.
make_echoicefield()
will return an instance of EChoiceField
which subclasses a field type from models.CharField
.
The exact name of the field type will be MyEnumNameField
in Django >= 1.9, note the suffixed ‘Field’. For earlier
versions of Django, it will be EChoiceField
.
Thus, MyModel.my_echoice_field
will be an EChoice
instance stored in an EChoiceField
field.
Since the field is generated with the help of a factory function, it does not exist as is as a field class inechoices.fields
. But, when generating a migration file, Django will set the class of the field as the resulting class
from make_echoicefield()
, which does not exist in echoices.fields
. This will cause the Django server to crash, as
an AttributeError: module 'echoices.fields' has no attribute 'MyEnumNameField'
exception will be raised.
To prevent this, you have to edit the migration file and replace the instantiation of the non-existing class with a call
to make_echoicefield()
, with the same parameters as when defining the field in your model.
For example, assume you have the following model defined in models.py
:
from django.db import models
from echoices.fields import make_echoicefield
class MyModel(models.Model):
state = make_echoicefield(EStates, default=EStates.CREATED)
Then you would replace the generated field instantiation statement in migrations/0001_initial.py
migrations.CreateModel(
name='MyModel',
fields=[
# Replace the statement below
('state', echoices.fields.EStatesField(
echoices=app.models.EStates,
default=app.models.EStates(1))
),
],
with
('state', echoices.fields.make_echoicefield(
echoices=app.models.EStates,
default=app.models.EStates.CREATED)
),
fields.MultipleEChoiceField
Similar to previous fields, but supports multiple values to be selected.
Not yet implemented.
Assume a Context(dict(estates=myapp.models.EStates))
is provided to the following templates.
Fields of the EChoice
can be accessed in the templates as:
{{ estates.CREATED.value }}
{{ estates.CREATED.label }}
EChoice
can also be enumerated:
{% for state in estates %}
{{ state.value }}
{{ state.label }}
{% endfor %}