68👍
According to the documentation:
Field.choices
An iterable (e.g., a list or tuple) consisting itself of
iterables of exactly two items (e.g. [(A, B), (A, B) …]) to use as
choices for this field. If this is given, the default form widget will
be a select box with these choices instead of the standard text field.The first element in each tuple is the actual value to be stored, and
the second element is the human-readable name.
So, your code is correct, except that you should either define variables JANUARY
, FEBRUARY
etc. or use calendar
module to define MONTH_CHOICES
:
import calendar
...
class MyModel(models.Model):
...
MONTH_CHOICES = [(str(i), calendar.month_name[i]) for i in range(1,13)]
month = models.CharField(max_length=9, choices=MONTH_CHOICES, default='1')
228👍
I think no one actually has answered to the first question:
Why did they create those variables?
Those variables aren’t strictly necessary. It’s true. You can perfectly do something like this:
MONTH_CHOICES = (
("JANUARY", "January"),
("FEBRUARY", "February"),
("MARCH", "March"),
# ....
("DECEMBER", "December"),
)
month = models.CharField(max_length=9,
choices=MONTH_CHOICES,
default="JANUARY")
Why using variables is better? Error prevention and logic separation.
JAN = "JANUARY"
FEB = "FEBRUARY"
MAR = "MAR"
# (...)
MONTH_CHOICES = (
(JAN, "January"),
(FEB, "February"),
(MAR, "March"),
# ....
(DEC, "December"),
)
Now, imagine you have a view where you create a new Model instance. Instead of doing this:
new_instance = MyModel(month='JANUARY')
You’ll do this:
new_instance = MyModel(month=MyModel.JAN)
In the first option you are hardcoding the value. If there is a set of values you can input, you should limit those options when coding.
Also, if you eventually need to change the code at the Model layer, now you don’t need to make any change in the Views layer.
- [Django]-How to add url parameters to Django template url tag?
- [Django]-What is reverse()?
- [Django]-Google Static Maps URL length limit
147👍
For Django3.0+, use models.TextChoices
(see docs-v3.0 for enumeration types)
from django.db import models
class MyModel(models.Model):
class Month(models.TextChoices):
JAN = "1", "JANUARY"
FEB = "2", "FEBRUARY"
MAR = "3", "MAR"
# (...)
month = models.CharField(
max_length=2,
choices=Month.choices,
default=Month.JAN
)
Usage::
>>> obj = MyModel.objects.create(month='1')
>>> assert obj.month == obj.Month.JAN == '1'
>>> assert MyModel.Month(obj.month) is obj.Month.JAN
>>> assert MyModel.Month(obj.month).value is '1'
>>> assert MyModel.Month(obj.month).label == 'JANUARY'
>>> assert MyModel.Month(obj.month).name == 'JAN'
>>> assert MyModel.objects.filter(month=MyModel.Month.JAN).count() >= 1
>>> obj2 = MyModel(month=MyModel.Month.FEB)
>>> assert obj2.get_month_display() == obj2.Month(obj2.month).label
Let’s say we known the label is ‘JANUARY’, how to get the name ‘JAN’ and the value ‘1’?
label = "JANUARY"
name = {i.label: i.name for i in MyModel.Month}[label]
print(repr(name)) # 'JAN'
value = {i.label: i.value for i in MyModel.Month}[label]
print(repr(value)) # '1'
Personally, I would rather use models.IntegerChoices
class MyModel(models.Model):
class Month(models.IntegerChoices):
JAN = 1, "JANUARY"
FEB = 2, "FEBRUARY"
MAR = 3, "MAR"
# (...)
month = models.PositiveSmallIntegerField(
choices=Month.choices,
default=Month.JAN
)
- [Django]-Malformed Packet: Django admin nested form can't submit, connection was reset
- [Django]-PyCharm: DJANGO_SETTINGS_MODULE is undefined
- [Django]-Modulus % in Django template
16👍
Jan, 2023 Update:
The new way is using models.TextChoices as shown below which is built-in so you don’t need to install any packages. *The 1st value is the actual value and the 2nd value is displayed on Django Admin:
# "models.py"
from django.db import models
class MyModel(models.Model):
class Months(models.TextChoices):
# Actual value ↓ # ↓ Displayed on Django Admin
JANUARY = 'JAN', 'January'
FEBRUARY = 'FEB', 'February'
MARCH = 'MAR', 'March'
APRIL = 'APR', 'April'
MAY = 'MAY', 'May'
month = models.CharField(
max_length=3,
choices=Months.choices,
default=Months.APRIL
)
class YearInSchool(models.TextChoices):
# Actual value ↓ # ↓ Displayed on Django Admin
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.SOPHOMORE,
)
I also rewrote the code above in the old way which is also built-in.
# "models.py"
from django.db import models
class MyModel(models.Model):
# ↓ Actual value
JANUARY = 'JAN'
FEBRUARY = 'FEB'
MARCH = 'MAR'
APRIL = 'APR'
MAY = 'MAY'
MONTHS = [ # ↓ Displayed on Django Admin
(JANUARY, 'January'),
(FEBRUARY, 'February'),
(MARCH, 'March'),
(APRIL, 'April'),
(MAY, 'May'),
]
month = models.CharField(
max_length=3,
choices=MONTHS,
default=APRIL # Or "default=MONTHS[3]"
)
# ↓ Actual value
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
GRADUATE = 'GR'
YEAR_IN_SCHOOL_CHOICES = [
# ↓ Displayed on Django Admin
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Soph*m*re'),
(JUNIOR, 'Junior'),
(SENIOR, 'Senior'),
(GRADUATE, 'Graduate'),
]
year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=SOPHOMORE # Or "default=YEAR_IN_SCHOOL_CHOICES[1]"
)
- [Django]-Django :How to integrate Django Rest framework in an existing application?
- [Django]-Why is __init__ module in django project loaded twice
- [Django]-Django Rest Framework – Authentication credentials were not provided
13👍
The cleanest solution is to use the django-model-utils
library:
from model_utils import Choices
class Article(models.Model):
STATUS = Choices('draft', 'published')
status = models.CharField(choices=STATUS, default=STATUS.draft, max_length=20)
https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices
- [Django]-Django-reversion and related model
- [Django]-What is the purpose of adding to INSTALLED_APPS in Django?
- [Django]-Django – how to create a file and save it to a model's FileField?
9👍
I would suggest to use django-model-utils instead of Django built-in solution.
The main advantage of this solution is the lack of string declaration duplication. All choice items are declared exactly once. Also this is the easiest way for declaring choices using 3 values and storing database value different than usage in source code.
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
class MyModel(models.Model):
MONTH = Choices(
('JAN', _('January')),
('FEB', _('February')),
('MAR', _('March')),
)
# [..]
month = models.CharField(
max_length=3,
choices=MONTH,
default=MONTH.JAN,
)
And with usage IntegerField instead:
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
class MyModel(models.Model):
MONTH = Choices(
(1, 'JAN', _('January')),
(2, 'FEB', _('February')),
(3, 'MAR', _('March')),
)
# [..]
month = models.PositiveSmallIntegerField(
choices=MONTH,
default=MONTH.JAN,
)
- This method has one small disadvantage: in any IDE (eg. PyCharm) there will be no code completion for available choices (it’s because those values aren’t standard members of Choices class).
- [Django]-Django import error – No module named core.management
- [Django]-Extend base.html problem
- [Django]-Django – filtering on foreign key properties
4👍
You can’t have bare words in the code, that’s the reason why they created variables (your code will fail with NameError
).
The code you provided would create a database table named month
(plus whatever prefix django adds to that), because that’s the name of the CharField
.
But there are better ways to create the particular choices you want. See a previous Stack Overflow question.
import calendar
tuple((m, m) for m in calendar.month_name[1:])
- [Django]-Django – Circular model import issue
- [Django]-Programmatically saving image to Django ImageField
- [Django]-Django logging of custom management commands
2👍
$ pip install django-better-choices
For those who are interested, I have created django-better-choices
library, that provides a nice interface to work with Django choices for Python 3.7+. It supports custom parameters, lots of useful features and is very IDE friendly.
You can define your choices as a class:
from django_better_choices import Choices
class PAGE_STATUS(Choices):
CREATED = 'Created'
PENDING = Choices.Value('Pending', help_text='This set status to pending')
ON_HOLD = Choices.Value('On Hold', value='custom_on_hold')
VALID = Choices.Subset('CREATED', 'ON_HOLD')
class INTERNAL_STATUS(Choices):
REVIEW = 'On Review'
@classmethod
def get_help_text(cls):
return tuple(
value.help_text
for value in cls.values()
if hasattr(value, 'help_text')
)
Then do the following operations and much much more:
print( PAGE_STATUS.CREATED ) # 'created'
print( PAGE_STATUS.ON_HOLD ) # 'custom_on_hold'
print( PAGE_STATUS.PENDING.display ) # 'Pending'
print( PAGE_STATUS.PENDING.help_text ) # 'This set status to pending'
'custom_on_hold' in PAGE_STATUS.VALID # True
PAGE_STATUS.CREATED in PAGE_STATUS.VALID # True
PAGE_STATUS.extract('CREATED', 'ON_HOLD') # ~= PAGE_STATUS.VALID
for value, display in PAGE_STATUS:
print( value, display )
PAGE_STATUS.get_help_text()
PAGE_STATUS.VALID.get_help_text()
And of course, it is fully supported by Django and Django Migrations:
class Page(models.Model):
status = models.CharField(choices=PAGE_STATUS, default=PAGE_STATUS.CREATED)
Full documentation here: https://pypi.org/project/django-better-choices/
- [Django]-AngularJS with Django – Conflicting template tags
- [Django]-Select distinct values from a table field
- [Django]-How to format time in django-rest-framework's serializer?