Migrating to native Django Choices¶
Since version 3.0, Django offers native choices enums (mostly equivalent) to the functionality that this library offers. See the django docs for more details.
We provide some automated tooling to facilitate migrating and instructions for possible hurdles.
Generating equivalent native code¶
For trivial usage where you just define the choices as a constant, there is a management
command since version 2.0 to generate the equivalent code. It supports str
and int
for values types.
Ensure you have
djchoices
added to yourINSTALLED_APPS
setting.Run the command
python manage.py generate_native_django_choices
The command essentially discovers all subclasses of DjangoChoices
and introspects
them to generate the equivalent native choices code. This depends on the classes being
imported during Django’s initialization phase. This should be the case for almost all
usages, as the choices need to be imported to be picked up by models.
Possible options for the command:
--no-wrap-gettext
: do not wrap the choice labels in a function call to mark them translatable.--gettext-alias
: when wrapping the labels, you can specify the name of the function call/alias to wrap with, e.g.gettext_lazy
. It defaults to the common pattern of_
. You need to ensure the necessary imports are present in the module.
Public API¶
The
choices
class attribute behaves the same in native choices.
Migrating non-trivial usage¶
Django-choices offered some class attributes that need to be updated too when migrating to native choices.
DjangoChoices.labels
¶
This is roughly equivalent to:
dict(zip(Native.names, Native.labels))
Notable differences:
for a value like
'option1'
without explicit label, Django Choices produces'option1'
as label, while Django produces'option 1'
. A value like'option_1'
results in the same label.It may not play nice with the empty option in native choices.
ChoiceItem.order
¶
The management command emits the generated native choices in the configured order.
If you need access to the order, you can leverage enumerate(Native.values)
to loop
over tuples of (order, value)
.
DjangoChoices.values
¶
This is equivalent to:
dict(zip(Native.values, Native.labels))
DjangoChoices.validator
¶
Django has been performing this out of the box on model fields since at least Django 1.3 - you don’t need it.
DjangoChoices.attributes
¶
This is roughly equivalent to:
native = dict(zip(Native.values, Native.names))
Remarks:
It may not play nice with the empty option in native choices.
DjangoChoices.get_choice
¶
There is no direct equivalent, however you can access the enum instance and look up properties:
an_enum_value = Native[Native.some_value]
print(an_enum_value.value)
print(an_enum_value.label)
DjangoChoices.get_order_expression
¶
There is no equivalent, but you should easily be able to add this as your own class method/mixin:
from django.db.models import Case, IntegerField, Value, When
@classmethod
def get_order_expression(cls, field_name):
whens = []
for order, value in enumerate(cls.values()):
whens.append(
When(**{field_name: value, "then": Value(order)})
)
return Case(*whens, output_field=IntegerField())
Custom attributes¶
It’s recommended to keep a separate dictionary with a mapping of choice values to the additional attributes. You could consider dataclasses to model this too.