31π
django > 3.0 β have built-in support for Enums
from django.utils.translation import gettext_lazy as _
class Student(models.Model):
class YearInSchool(models.TextChoices):
FRESHMAN = "FR", _("Freshman")
SOPHOMORE = "SO", _("Soph*m*re")
JUNIOR = "JR", _("Junior")
SENIOR = "SR", _("Senior")
GRADUATE = "GR", _("Graduate")
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
def is_upperclass(self):
return self.year_in_school in {
self.YearInSchool.JUNIOR,
self.YearInSchool.SENIOR,
}
Problem in your code is that INITIATED = "INITIATED",
a comma after INITIATED
option and other options. when we add comma after any string it will become a tuple. See an example below
s = 'my str'
print(type(s))
# output: str
s = 'my str',
print(type(s))
# output: tuple
#models.py
class Transaction(models.Model):
trasaction_status = models.CharField(max_length=255, choices=TransactionStatus.choices())
transaction_type = models.CharField(max_length=255, choices=TransactionType.choices())
#enums.py
class TransactionType(Enum):
IN = "IN"
OUT = "OUT"
@classmethod
def choices(cls):
print(tuple((i.name, i.value) for i in cls))
return tuple((i.name, i.value) for i in cls)
class TransactionStatus(Enum):
INITIATED = "INITIATED"
PENDING = "PENDING"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
ERROR = "ERROR"
@classmethod
def choices(cls):
print(tuple((i.value, i.name) for i in cls))
return tuple((i.value, i.name) for i in cls)
For django > 3.0 https://docs.djangoproject.com/en/4.0/ref/models/fields/#field-choices-enum-types
206π
Django 3.0 has built-in support for Enums
Example:
from django.utils.translation import gettext_lazy as _ class Student(models.Model): class YearInSchool(models.TextChoices): FRESHMAN = 'FR', _('Freshman') SOPHOMORE = 'SO', _('Soph*m*re') JUNIOR = 'JR', _('Junior') SENIOR = 'SR', _('Senior') GRADUATE = 'GR', _('Graduate') year_in_school = models.CharField( max_length=2, choices=YearInSchool.choices, default=YearInSchool.FRESHMAN, )
These work similar to enum from Pythonβs standard library, but with some modifications:
- Enum member values are a tuple of arguments to use when constructing the concrete data type. Django supports adding an extra string value to the end of this tuple to be used as the human-readable name, or
label
. Thelabel
can be a lazy translatable string. Thus, in most cases, the member value will be a(value, label)
two-tuple. If a tuple is not provided, or the last item is not a (lazy) string, the label is automatically generated from the member name.- A
.label
property is added on values, to return the human-readable name.
A number of custom properties are added to the enumeration classes β.choices
,.labels
,.values
, and.names
β to make it easier to access lists of those separate parts of the enumeration. Use.choices
as a suitable value to pass to choices in a field definition.- The use of
enum.unique()
is enforced to ensure that values cannot be defined multiple times. This is unlikely to be expected in choices for a field.
For more info, check the documentation
Note:
As @Danielle Madeley pointed out, if you try to access the year_in_school
attribute directly Django still returns the raw string instead of the Enum object:
>>> student.year_in_school
'FR'
What I usually do is to create a helper method that returns the Enum object:
class Student(models.Model):
...
def get_year_in_school(self) -> YearInSchool:
# Get value from choices enum
return self.YearInSchool[self.year_in_school]
- [Django]-Remove pk field from django serialized objects
- [Django]-Why is factory_boy superior to using the ORM directly in tests?
- [Django]-Django filter queryset __in for *every* item in list
66π
For Django 2.x and lower:
You define an Enum
by setting the various options as documented here:
class TransactionStatus(Enum):
INITIATED = "INITIATED"
PENDING = "PENDING"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
ERROR = "ERROR"
Note there are no commas! This allows you later in your code to refer to TransactionStatus.ERROR
or TransactionStatus.PENDING
.
The rest of your code is correct. You get the choices
by creating tuples of option.name
, option.value
.
UPDATE: For Django 3.x and higher, use the built-in types TextChoices
, IntegerChoices
and Choices
as described here. That way you donβt have to construct the choices
tuple yourself.
- [Django]-How to automate createsuperuser on django?
- [Django]-Rendering a template variable as HTML
- [Django]-Django: Fat models and skinny controllers?
5π
An example from my project:
import enum
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _
class NotificationTemplate(models.Model):
class Meta:
verbose_name = _('notification template')
verbose_name_plural = _('notification templates')
@enum.unique
class Name(str, enum.Enum):
ONBOARDING = 'onboarding'
TG_ERROR = 'tg_error'
FB_ERROR = 'fb_error'
@classmethod
def choices(cls):
return [(item.value, item.name) for item in cls]
@enum.unique
class Type(int, enum.Enum):
PUSH = 1
EMAIL = 2
TELEGRAM = 3
VK = 4
OTHER = 5
@classmethod
def choices(cls):
return [(item.value, item.name) for item in cls]
name = models.CharField(_('notification name'), max_length=64, unique=True, choices=Name.choices(), default=Name.ONBOARDING)
template_type = ArrayField(models.PositiveSmallIntegerField(_('type'), choices=Type.choices()))
max_count = models.PositiveSmallIntegerField(default=1)
def __str__(self):
return self.Name(self.name).name
- [Django]-What is "load url from future" in Django
- [Django]-Migrating Django fixtures?
- [Django]-Modulus % in Django template
4π
If you are receiving this error:
βchoicesβ must be an iterable containing (actual value, human readable name) tuples
And are using Django3, then you are probably running into the same issue I did: The "Enums" have to be embedded in the model where you are trying to use them and ca not be declared outside of the model. For example, this will not work:
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Soph*m*re')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
class Student(models.Model):
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
Where as this example from the docs will:
class Student(models.Model):
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Soph*m*re')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
- [Django]-How to concatenate strings in django templates?
- [Django]-Iterate over model instance field names and values in template
- [Django]-CSS styling in Django forms
3π
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Soph*m*re')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
For above Django 3.0, You can use the above example.
For Integer Choices you can use the below code.
class Suit(models.IntegerChoices):
DIAMOND = 1
SPADE = 2
HEART = 3
CLUB = 4
suit = models.IntegerField(choices=Suit.choices)
- [Django]-How to check Django version
- [Django]-Django simple_tag and setting context variables
- [Django]-Django 2, python 3.4 cannot decode urlsafe_base64_decode(uidb64)
2π
By the way Djanog also supports the Python 3βs auto() as the Enum value. You can use the following helperclass to make your life easier.
from django.db.models.enums import TextChoices
class AutoEnumChoices(TextChoices):
def _generate_next_value_(name, start, count, last_values): # @NoSelf
return name.lower()
@property
def choices(cls): # @NoSelf
empty = [(None, cls.__empty__)] if hasattr(cls, '__empty__') else []
return empty + [(member.value, member.label) for member in cls]
Then use it in your choices definition:
class TransferBasicStatus(AutoEnumChoices):
NONE = auto()
WAITING = auto()
PENDING = auto()
PROGRESS = auto()
SUCCESS = auto()
DECLINED = auto()
ENDED = 'ended', _('Ended - The transfer has ended with mixed states')
- [Django]-Django substr / substring in templates
- [Django]-How to force application version on AWS Elastic Beanstalk
- [Django]-What's the difference between CharField and TextField in Django?
1π
According to your reference from https://hackernoon.com/using-enum-as-model-field-choice-in-django-92d8b97aaa63. The choices should be list of tuple, while yours will return a tuple of tuple.
More over i is different from i.name. Try:
#enums.py
class TransactionType(Enum):
IN = "IN"
OUT = "OUT"
@classmethod
def choices(cls):
return [(i, i.value) for i in cls]
- [Django]-Django-tables2: How to use accessor to bring in foreign columns?
- [Django]-Class has no objects member
- [Django]-List_display β boolean icons for methods
1π
The django-enum package makes this extremely easy:
from django.db import models
from django_enum import EnumField
class MyModel(models.Model):
class TextEnum(models.TextChoices):
VALUE0 = 'V0', 'Value 0'
VALUE1 = 'V1', 'Value 1'
VALUE2 = 'V2', 'Value 2'
class IntEnum(models.IntegerChoices):
ONE = 1, 'One'
TWO = 2, 'Two',
THREE = 3, 'Three'
# this is equivalent to:
# CharField(max_length=2, choices=TextEnum.choices, null=True, blank=True)
txt_enum = EnumField(TextEnum, null=True, blank=True)
# this is equivalent to
# PositiveSmallIntegerField(choices=IntEnum.choices)
int_enum = EnumField(IntEnum)
EnumField
is more than just an alias. The fields are now assignable and
accessible as their enumeration type rather than by-value:
instance = MyModel.objects.create(
txt_enum=MyModel.TextEnum.VALUE1,
int_enum=3 # by-value assignment also works
)
assert instance.txt_enum == MyModel.TextEnum('V1')
assert instance.txt_enum.label == 'Value 1'
assert instance.int_enum == MyModel.IntEnum['THREE']
assert instance.int_enum.value == 3
django-enum also provides IntegerChoices
and TextChoices
types that extend from enum-properties which makes possible very rich enumeration fields.
from enum_properties import s
from django_enum import TextChoices # use instead of Django's TextChoices
from django.db import models
class TextChoicesExample(models.Model):
class Color(TextChoices, s('rgb'), s('hex', case_fold=True)):
# name value label rgb hex
RED = 'R', 'Red', (1, 0, 0), 'ff0000'
GREEN = 'G', 'Green', (0, 1, 0), '00ff00'
BLUE = 'B', 'Blue', (0, 0, 1), '0000ff'
# any named s() values in the Enum's inheritance become properties on
# each value, and the enumeration value may be instantiated from the
# property's value
color = EnumField(Color)
instance = TextChoicesExample.objects.create(
color=TextChoicesExample.Color('FF0000')
)
assert instance.color == TextChoicesExample.Color('Red')
assert instance.color == TextChoicesExample.Color('R')
assert instance.color == TextChoicesExample.Color((1, 0, 0))
# direct comparison to any symmetric value also works
assert instance.color == 'Red'
assert instance.color == 'R'
assert instance.color == (1, 0, 0)
# save by any symmetric value
instance.color = 'FF0000'
# access any enum property right from the model field
assert instance.color.hex == 'ff0000'
# this also works!
assert instance.color == 'ff0000'
# and so does this!
assert instance.color == 'FF0000'
instance.save()
# filtering works by any symmetric value or enum type instance
assert TextChoicesExample.objects.filter(
color=TextChoicesExample.Color.RED
).first() == instance
assert TextChoicesExample.objects.filter(color=(1, 0, 0)).first() == instance
assert TextChoicesExample.objects.filter(color='FF0000').first() == instance
- [Django]-Django select_for_update cannot be used outside of a transaction
- [Django]-Django β what is the difference between render(), render_to_response() and direct_to_template()?
- [Django]-Django Rest Framework File Upload
0π
You can try doing something like this based on examples from docs.:
from enum import Enum
class BaseEnum(Enum):
def __new__(cls, *args):
obj = object.__new__(cls)
obj._value_ = args[0]
obj.display_name = args[1]
return obj
@classmethod
def model_choices(cls):
return [(cls.__members__[member].value, cls.__members__[member].display_name)
for member in cls.__members__.keys()]
which would result in:
>>> class TransactionType(BaseEnum):
... IN = ('in', 'In')
... OUT = ('out', 'Out')
...
>>> TransactionType.IN.value
'in'
>>> TransactionType.IN.display_name
'In'
>>> TransactionType.model_choices()
[('in', 'In'), ('out', 'Out')]
which could be used as an argument for a fieldβs choices.
- [Django]-List_display β boolean icons for methods
- [Django]-Django migrate βfake and βfake-initial explained
- [Django]-How can I avoid "Using selector: EpollSelector" log message in Django?
0π
It is also possible to write:
class Transaction(models.Model):
class TransactionStatus(Enum):
initiated = ('in', 'Initiated')
pending = ('pe', 'Pending')
completed = ('co', 'Completed')
failed = ('fa', 'Failed')
error = ('er', 'Error')
@classmethod
def get_value(cls, member):
return cls[member].value[0]
class TransactionType(Enum):
_in = ('in', 'In')
out = ('ou', 'Out')
@classmethod
def get_value(cls, member):
return cls[member].value[0]
trasaction_status = models.CharField(max_length=2, choices=[x.value for x in TransactionStatus])
transaction_type = models.CharField(max_length=2, choices=[x.value for x in TransactionType])
With get_value
you can write for example:
Transaction.objects.filter(status=Transaction.TransactionStatus.get_value('initialited'))
- [Django]-Reference list item by index within Django template?
- [Django]-Parsing unicode input using python json.loads
- [Django]-Why am I getting this error in Django?
0π
@paras
you have to change your model
@classmethod
def choices(cls):
print(tuple((i.value, i.name) for i in cls))
return tuple((i.value, i.name) for i in cls)
it worked for me.
- [Django]-What is the Simplest Possible Payment Gateway to Implement? (using Django)
- [Django]-Django migration fails with "__fake__.DoesNotExist: Permission matching query does not exist."
- [Django]-Django Queryset with year(date) = '2010'
0π
If you must use Enum, than one of possible ways would be converting Enum to Choices with this code:
from django.db import models
from django.db.models.enums import ChoicesMeta
MyTypeWithChoices = types.new_class(
"MyTypeWithChoices",
(models.TextChoices,),
kwds={"metaclass": ChoicesMeta},
exec_body=lambda ns: ns.update({d.name: d.value for d in SomeEnum}),
)
One problem with that approach is that tools like Pyright and IDEs would not provide static type hints at all for dynamic class.
- [Django]-Should I be adding the Django migration files in the .gitignore file?
- [Django]-Django: Get list of model fields?
- [Django]-Django check for any exists for a query