Year Field in Django
For those who don't prefer having long choice fields i.e. in @Alasdair solution imagine having a choice field from 1984 to 2019, such a long choice list. How about having a field you can type the year and still have a way to increment or decrements. To solve this my way, use models.PositiveIntegerField with MinValueValidator and MaxValueValidator as shown below:
import datetime
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
def current_year():
return datetime.date.today().year
def max_value_current_year(value):
return MaxValueValidator(current_year())(value)
class EmployeesTrainings(models.Model):
name = models.ForeignKey(employee, default='', blank=True, null=True, on_delete=models.CASCADE)
training_name = models.TextField(max_length=200, default='', blank=False, null=False)
training_year = models.PositiveIntegerField(
default=current_year(), validators=[MinValueValidator(1984), max_value_current_year])
My initial thought was to write a callable that returns the choices, that will be evaluated for each request.
import datetime
def year_choices():
return [(r,r) for r in range(1984, datetime.date.today().year+1)]
def current_year():
return datetime.date.today().year
class MyModel(models.Model):
year = models.IntegerField(_('year'), choices=year_choices, default=current_year)
However this doesn't work, because Django's check framework doesn't allow the year_choices to be used as the default. Even if you could hack the choices to be generated dynamically, it would have the disadvantage that Django would try to create a migration each year when the choices change.
You can avoid this by generating the choices at the form level instead. You can use validators in the model to prevent invalid data. Note that MaxValueValidator
is wrapped in a function max_value_current_year
to avoid a new migration every year.
import datetime
from django.core.validators import MaxValueValidator, MinValueValidator
def current_year():
return datetime.date.today().year
def max_value_current_year(value):
return MaxValueValidator(current_year())(value)
class MyModel(models.Model):
year = models.IntegerField(_('year'), validators=[MinValueValidator(1984), max_value_current_year])
def year_choices():
return [(r,r) for r in range(1984, datetime.date.today().year+1)]
class MyForm(forms.ModelForm):
year = forms.TypedChoiceField(coerce=int, choices=year_choices, initial=current_year)
You can put the options(years) in the form, using the IntegerField min_value and max_value. In model you can use the IntegerField without choices.
- Forms: IntegerField
- More about forms
So, you won't worry about when the year change, because you will only change the options in the form.
If you want to change the year automatically, this could help you: Django forms integerField set max_value on runtime
Keypoints of my Approach
- In the model, the year is an Integerfield.
- In the form, I used a ChoiceField (to keep the user experience simple by using a Select. I didn't want o keep an open input to avoid years like 1990, or 90.)
- And to populate the Choices of the Select, a pretty simple script does the job.
- Datetime gives the updated year.
Here is the code
models.py
year_founded = models.IntegerField(blank=True)
forms.py
from datetime import datetime
year_founded = forms.ChoiceField(
choices=possible_years(((datetime.now()).year), 1900),
label='Año de Fundación',
)
script to populate choices in the form
def possible_years(first_year_in_scroll, last_year_in_scroll):
p_year = []
for i in range(first_year_in_scroll, last_year_in_scroll, -1):
p_year_tuple = str(i), i
p_year.append(p_year_tuple)
return p_year