When you have a model field with a choices option you tend to have some magic values associated with human readable names. Is there in Django a convenient way to set these f
I'd probably set up the reverse-lookup dict once and for all, but if I hadn't I'd just use:
thing.priority = next(value for value, name in Thing.PRIORITIES
if name=='Normal')
which seems simpler than building the dict on the fly just to toss it away again;-).
My answer is very late and might seem obvious to nowadays-Django experts, but to whoever lands here, i recently discovered a very elegant solution brought by django-model-utils: https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices
This package allows you to define Choices with three-tuples where:
So here's what you can do:
from model_utils import Choices
class Thing(models.Model):
PRIORITIES = Choices(
(0, 'low', 'Low'),
(1, 'normal', 'Normal'),
(2, 'high', 'High'),
)
priority = models.IntegerField(default=PRIORITIES.normal, choices=PRIORITIES)
thing.priority = getattr(Thing.PRIORITIES.Normal)
This way:
Enjoy :)
Do as seen here. Then you can use a word that represents the proper integer.
Like so:
LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
(LOW, 'Low'),
(NORMAL, 'Normal'),
(HIGH, 'High'),
)
Then they are still integers in the DB.
Usage would be thing.priority = Thing.NORMAL
As of Django 3.0, you can use:
class ThingPriority(models.IntegerChoices):
LOW = 0, 'Low'
NORMAL = 1, 'Normal'
HIGH = 2, 'High'
class Thing(models.Model):
priority = models.IntegerField(default=ThingPriority.LOW, choices=ThingPriority.choices)
# then in your code
thing = get_my_thing()
thing.priority = ThingPriority.HIGH
Originally I used a modified version of @Allan's answer:
from enum import IntEnum, EnumMeta
class IntegerChoiceField(models.IntegerField):
def __init__(self, choices, **kwargs):
if hasattr(choices, '__iter__') and isinstance(choices, EnumMeta):
choices = list(zip(range(1, len(choices) + 1), [member.name for member in list(choices)]))
kwargs['choices'] = choices
super(models.IntegerField, self).__init__(**kwargs)
def to_python(self, value):
return self.choices(value)
def get_db_prep_value(self, choice):
return self.choices[choice]
models.IntegerChoiceField = IntegerChoiceField
GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')
class Gear(Item, models.Model):
# Safe to assume last element is largest value member of an enum?
#type = models.IntegerChoiceField(GEAR, default=list(GEAR)[-1].name)
largest_member = GEAR(max([member.value for member in list(GEAR)]))
type = models.IntegerChoiceField(GEAR, default=largest_member)
def __init__(self, *args, **kwargs):
super(Gear, self).__init__(*args, **kwargs)
for member in GEAR:
setattr(self, member.name, member.value)
print(Gear().HEAD, (Gear().HEAD == GEAR.HEAD.value))
Simplified with the django-enumfields
package package which I now use:
from enumfields import EnumIntegerField, IntEnum
GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')
class Gear(Item, models.Model):
# Safe to assume last element is largest value member of an enum?
type = EnumIntegerField(GEAR, default=list(GEAR)[-1])
#largest_member = GEAR(max([member.value for member in list(GEAR)]))
#type = EnumIntegerField(GEAR, default=largest_member)
def __init__(self, *args, **kwargs):
super(Gear, self).__init__(*args, **kwargs)
for member in GEAR:
setattr(self, member.name, member.value)
Model's choices option accepts a sequence consisting itself of iterables of exactly two items (e.g. [(A, B), (A, B) ...]) to use as choices for this field.
In addition, Django provides enumeration types that you can subclass to define choices in a concise way:
class ThingPriority(models.IntegerChoices):
LOW = 0, _('Low')
NORMAL = 1, _('Normal')
HIGH = 2, _('High')
class Thing(models.Model):
priority = models.IntegerField(default=ThingPriority.NORMAL, choices=ThingPriority.choices)
Django supports adding an extra string value to the end of this tuple to be used as the human-readable name, or label. The label can be a lazy translatable string.
# in your code
thing = get_thing() # instance of Thing
thing.priority = ThingPriority.LOW
Note: you can use that using ThingPriority.HIGH
, ThingPriority.['HIGH']
, or ThingPriority(0)
to access or lookup enum members.