190👍
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
43👍
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
- [Django]-How to produce a 303 Http Response in Django?
- [Django]-Django fix Admin plural
- [Django]-Django: Get model from string?
8👍
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:
from django.utils.translation import gettext_lazy as _
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.
You need to import from django.utils.translation import gettext_lazy as _
- [Django]-How to use regex in django query
- [Django]-Django TemplateDoesNotExist?
- [Django]-How to completely uninstall a Django app?
7👍
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;-).
- [Django]-Folder Structure for Python Django-REST-framework and Angularjs
- [Django]-Django return file over HttpResponse – file is not served correctly
- [Django]-Django using get_user_model vs settings.AUTH_USER_MODEL
7👍
Here’s a field type I wrote a few minutes ago that I think does what you want. Its constructor requires an argument ‘choices’, which may be either a tuple of 2-tuples in the same format as the choices option to IntegerField, or instead a simple list of names (ie ChoiceField((‘Low’, ‘Normal’, ‘High’), default=’Low’) ). The class takes care of the mapping from string to int for you, you never see the int.
class ChoiceField(models.IntegerField):
def __init__(self, choices, **kwargs):
if not hasattr(choices[0],'__iter__'):
choices = zip(range(len(choices)), choices)
self.val2choice = dict(choices)
self.choice2val = dict((v,k) for k,v in choices)
kwargs['choices'] = choices
super(models.IntegerField, self).__init__(**kwargs)
def to_python(self, value):
return self.val2choice[value]
def get_db_prep_value(self, choice):
return self.choice2val[choice]
- [Django]-How to express a One-To-Many relationship in Django?
- [Django]-What is a django.utils.functional.__proxy__ object and what it helps with?
- [Django]-Whats the difference between using {{STATIC_URL}} and {% static %}
6👍
I appreciate the constant defining way but I believe Enum type is far best for this task. They can represent integer and a string for an item in the same time, while keeping your code more readable.
Enums were introduced to Python in version 3.4. If you are using any lower (such as v2.x) you can still have it by installing the backported package: pip install enum34
.
# myapp/fields.py
from enum import Enum
class ChoiceEnum(Enum):
@classmethod
def choices(cls):
choices = list()
# Loop thru defined enums
for item in cls:
choices.append((item.value, item.name))
# return as tuple
return tuple(choices)
def __str__(self):
return self.name
def __int__(self):
return self.value
class Language(ChoiceEnum):
Python = 1
Ruby = 2
Java = 3
PHP = 4
Cpp = 5
# Uh oh
Language.Cpp._name_ = 'C++'
This is pretty much all. You can inherit the ChoiceEnum
to create your own definitions and use them in a model definition like:
from django.db import models
from myapp.fields import Language
class MyModel(models.Model):
language = models.IntegerField(choices=Language.choices(), default=int(Language.Python))
# ...
Querying is icing on the cake as you may guess:
MyModel.objects.filter(language=int(Language.Ruby))
# or if you don't prefer `__int__` method..
MyModel.objects.filter(language=Language.Ruby.value)
Representing them in string is also made easy:
# Get the enum item
lang = Language(some_instance.language)
print(str(lang))
# or if you don't prefer `__str__` method..
print(lang.name)
# Same as get_FOO_display
lang.name == some_instance.get_language_display()
- [Django]-Django error – matching query does not exist
- [Django]-Form with CheckboxSelectMultiple doesn't validate
- [Django]-Data Mining in a Django/Postgres application
5👍
class Sequence(object):
def __init__(self, func, *opts):
keys = func(len(opts))
self.attrs = dict(zip([t[0] for t in opts], keys))
self.choices = zip(keys, [t[1] for t in opts])
self.labels = dict(self.choices)
def __getattr__(self, a):
return self.attrs[a]
def __getitem__(self, k):
return self.labels[k]
def __len__(self):
return len(self.choices)
def __iter__(self):
return iter(self.choices)
def __deepcopy__(self, memo):
return self
class Enum(Sequence):
def __init__(self, *opts):
return super(Enum, self).__init__(range, *opts)
class Flags(Sequence):
def __init__(self, *opts):
return super(Flags, self).__init__(lambda l: [1<<i for i in xrange(l)], *opts)
Use it like this:
Priorities = Enum(
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High')
)
priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)
- [Django]-Django {% if forloop.first %} question
- [Django]-Passing variable urlname to url tag in django template
- [Django]-Django – how to create a file and save it to a model's FileField?
2👍
Simply replace your numbers with the human readable values you would like. As such:
PRIORITIES = (
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High'),
)
This makes it human readable, however, you’d have to define your own ordering.
- [Django]-What is "load url from future" in Django
- [Django]-How to concatenate strings in django templates?
- [Django]-Multiple Database Config in Django 1.2
2👍
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:
- The first item is the database value
- The second item is a code-readable value
- The third item is a human-readable value
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:
- You can use your human-readable value to actually choose the value of your field (in my case, it’s useful because i’m scraping wild content and storing it in a normalized way)
- A clean value is stored in your database
- You have nothing non-DRY to do 😉
Enjoy 🙂
- [Django]-Where does pip install its packages?
- [Django]-Django: guidelines for speeding up template rendering performance
- [Django]-Django models avoid duplicates
0👍
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)
- [Django]-Django unit tests without a db
- [Django]-Django rest framework lookup_field through OneToOneField
- [Django]-Python Socket.IO client for sending broadcast messages to TornadIO2 server