Only showing year in django admin, a YearField instead of DateField?

23,622

Solution 1

I found this solution which solves the whole thing quite elegantly I think (not my code):

import datetime
YEAR_CHOICES = []
for r in range(1980, (datetime.datetime.now().year+1)):
    YEAR_CHOICES.append((r,r))

year = models.IntegerField(_('year'), choices=YEAR_CHOICES, default=datetime.datetime.now().year)

Edit the range start to extend the list :-)

Solution 2

Also, and this isn't that relevant for your case, but is useful being aware of, don't use datetime.date.today(), or datetime.datetime.now() as defaults. This is executed once, when the server is started up.

You are much better off passing the callables in:

date = models.DateField(default=datetime.date.today)

Note, you could use a lambda to make it relative:

date = models.DateField(default=lambda : datetime.date.today() - datetime.timedelta(days=6210))

Of course, this is naive, and assumes there have been 5 leap years in the past 17 years.

Solution 3

You can also do this:

YEARS = (
    ("1990", "1990"),
    ("1991", "1991"),
    ("1992", "1992"),
    # P.e. generate a list from 1960 to date.today().year
    # The reason why they choice key and text value are the
    # same is that if you generate from 1960 to 2060 it will collide.
    #
    # E.g
    # from datetime import datetime
    # def tuplify(x): return (x,x)   # str(x) if needed
    # current_year = datetime.now().year
    # YEARS = map(tuplify, range(1930, current_year + 1))  # range(1,4) gives [1,2,3]
)

class Whatever(models.Model):
    # Show a list with years
    birthdate = models.IntegerField(max_length=2, choices=YEARS)

I hope this will help you.

Solution 4

For a solution not involving Model choices:

from django.core.validators import MinValueValidator, MaxValueValidator

class Person(models.Model):
    year = models.PositiveIntegerField(
            validators=[
                MinValueValidator(1900), 
                MaxValueValidator(datetime.now().year)],
            help_text="Use the following format: <YYYY>")

That'll also create a placeholder in the input field with the value of help_text.

Share:
23,622
odinho - Velmont
Author by

odinho - Velmont

Interested in free open source software, and the open web.

Updated on February 25, 2020

Comments

  • odinho - Velmont
    odinho - Velmont over 4 years

    I've got a model where I need to store birth year. I'm using django admin. The person using this will be filling out loads of people every day, and DateField() shows too much (not interested in the day/month).

    This is a mockup model showing how it is now:

    class Person(models.Model):
      name = models.CharField(max_length=256)
      born = models.IntegerField(default=lambda: date.today().year - 17)
    

    As you can see, most of the people is 17 years old, so I'm getting their birth year as default.

    Can I do this better? How can I make a YearField out of the DateField? If making a YearField I can maybe even make some "easy tags" like the "now" that date has. (Of course, a specialized BornYearField would have easy buttons for 1989, 1990, 1991 and other common years)

  • odinho - Velmont
    odinho - Velmont over 14 years
    Yes, I knew that problem. However, I thought since this is a year field merely to help the people putting in new members all day, it only needs update once a year. It just needs to «almost hit» the avarage age. But thank you, of course a lambda solution is better.
  • Ad N
    Ad N over 11 years
    Is it just me or this upvoted answer is not about the question ?
  • freethebees
    freethebees over 8 years
    Django ignores max_length for IntegerField, and will warn you in Django 1.8+. See this for more.
  • Bobort
    Bobort over 8 years
    You could it all on one line: YEAR_CHOICES = [(r,r) for r in range(1984, datetime.date.today().year+1)]
  • askvictor
    askvictor almost 8 years
    the default should probably look like default=lambda: datetime.datetime.now().year otherwise it will only update the default when the service is restarted
  • askvictor
    askvictor almost 8 years
    Also, f you want migrations to work, then you can't use a lambda, so you'd need to define a simple function def current_year(): ... and use that (without brackets) instead default=current_year
  • Q Caron
    Q Caron almost 8 years
    Your YEARS tuple first element should be an integer as birthdate's type is IntegerField. You could use a list comprehension here to build the choices and have a fairly simple statement.
  • T. Christiansen
    T. Christiansen over 6 years
    You should remove max_length=4. It is ignored for IntergerField.
  • bhattraideb
    bhattraideb about 4 years
    I created function like: def YEAR_CHOICES(): year_choices = [] for r in range(1980, (datetime.datetime.now().year + 1)): year_choices.append((r, r)) return year_choices and using in class like: class Education(models.Model): year_of_passing = models.IntegerField(_('year_of_passing'), choices=YEAR_CHOICES, default=datetime.datetime.now().year) But its throwing error: resume.Education.year_of_passing: (fields.E004) 'choices' must be an iterable (e.g., a list or tuple). What wrong I am doing?
  • mapto
    mapto about 2 years
    anyone could explain what _('year') stands for?