Queryset of people with a birthday in the next X days

前端 未结 6 1849
春和景丽
春和景丽 2021-01-03 13:29

how do i get queryset of people with a birthday in the next X days? I saw this answer, but it does not suit me, because gets people only with current year of birth.

相关标签:
6条回答
  • 2021-01-03 13:56

    Assuming it's datetime field do something like this (using future_date from dimosaur answer):

    Profile.objects.get(
        Q(birthday__lte=future_date),
        Q(birthday__gte=datetime.date.today())
    )
    
    0 讨论(0)
  • 2021-01-03 13:56

    If X is a constant that you know:

    import datetime
    future_date = datetime.date.today() + datetime.timedelta(days=X)
    Profile.objects.filter(
        birth_date__month=future_date.month,
        birth_date__day=future_date.day
    )
    

    Something like that.

    0 讨论(0)
  • 2021-01-03 13:59

    Assuming a model like this--

    class Person(models.Model):
        name = models.CharField(max_length=40)
        birthday = models.DateTimeField() # their next birthday
    

    The next step would be to create a query filtering out any records with birthdays having a month and day in between (now.month, now.day) and (then.month, then.day). You can actually access the month and day attributes of the datetime object using the queryset API by passing Person.objects.filter a keyword argument like this: "birthday__month." I tried this with an actual queryset API method like "birthday__month__gte" and it failed though. So I would suggest simply generating a literal list of month/day tuples representing each (month, day) in the date range you want records for, then compose them all into a query with django.db.models.Q, like so:

    from datetime import datetime, timedelta
    import operator
    
    from django.db.models import Q
    
    def birthdays_within(days):
    
        now = datetime.now()
        then = now + timedelta(days)
    
        # Build the list of month/day tuples.
        monthdays = [(now.month, now.day)]
        while now <= then:
            monthdays.append((now.month, now.day))
            now += timedelta(days=1)
    
        # Tranform each into queryset keyword args.
        monthdays = (dict(zip(("birthday__month", "birthday__day"), t)) 
                     for t in monthdays)
    
    
        # Compose the djano.db.models.Q objects together for a single query.
        query = reduce(operator.or_, (Q(**d) for d in monthdays))
    
        # Run the query.
        return Person.objects.filter(query)
    

    After debugging, this should return a queryset with each person who has a birthday with month and day equal to any of the months or days in the specified list of tuples.

    0 讨论(0)
  • 2021-01-03 13:59

    I have tried to do it in a really silly way, but seems it works:

    import datetime
    from django.db.models import Q
    
    x = 5
    q_args = ''
    
    for d in range(x):
        future_date = datetime.date.today() + datetime.timedelta(days=d)
        q_args += 'Q(birth_date__month=%d, birth_date__day=%d)%s' % (
            future_date.month,
            future_date.day,
            ' | ' if d < x - 1 else ''
        )
    
    people = People.objects.filter(eval(q_args))
    
    0 讨论(0)
  • 2021-01-03 13:59

    I was unsatisfied with all replies here. They are all a variant on "check one date/year by one in a range...", making a long, ugly queries. Here is a simple solution, if one is willing to denormalize a bit:

    Change your model so instead of just datetime birthdate(yyyy, mm, dd) holding the real date you add a datetime birthday(DUMMY_YEAR, mm, dd) column. So every person in your DB will have saved its real birth date, and then a another birth date with a fixed year, shared with everyone else. Don't show this second field to users, though, and don't allow them to edit it.

    Once you edited your model, make sure the birthdate and birthday are always connected by extending models.Model save method in your class:

    def save(self, *args, **kwargs):
        self.birthday = datetime.date(BIRTHDAY_YEAR, 
             self.birthdate.month, self.birthdate.day)
        super(YOUR_CLASS, self).save(*args, **kwargs)
    

    And once you ensured that whenever a date is saved as birthdate, the birthday is updated too, you can filter it with just birthday__gte/birthday__lte. See an excerpt from my admin filter, where I take care of a year boundary:

    def queryset(self, request, queryset):
        if self.value() == 'today':
            # if we are looking for just today, it is simple
            return queryset.filter(birthday = datetime.date(
                    BIRTHDAY_YEAR, now().month, now().day
                ))
    
        if self.value() == 'week':
            # However, if we are looking for next few days,
            # we have to bear in mind what happens on the eve
            # of a new year. So if the interval we are looking at
            # is going over the new year, break the search into
            # two with an OR.
    
            future_date = (now() + datetime.timedelta(days=7)).date()
            if (now().year == future_date.year):
                return queryset.filter(
                        Q(birthday__gte = datetime.date(
                            BIRTHDAY_YEAR, now().month, now().day
                        )) &
                        Q(birthday__lte = datetime.date(
                            BIRTHDAY_YEAR,
                            future_date.month,
                            future_date.day)
                        )
                    )
            else:
                return queryset.filter(
                        # end of the old year
                        Q(birthday__gte = datetime.date(
                            BIRTHDAY_YEAR, now().month, now().day
                        )) &
                        Q(birthday__lte = datetime.date(BIRTHDAY_YEAR,12, 31)) |
                        # beginning of the new year
                        Q(birthday__gte = datetime.date(BIRTHDAY_YEAR, 1, 1)) &
                        Q(birthday__lte = datetime.date(BIRTHDAY_YEAR,
                            future_date.month,
                            future_date.day)
                        )
                    )
    

    In case you wonder what the Q() is, look on Complex lookups with Q objects

    0 讨论(0)
  • 2021-01-03 14:09

    I can think of 2 ways without using custom queries, both with "problems"

    1) Not efficient as it does 1 query per day

    start = datetime.date.today()
    max_days = 14
    days = [ start + datetime.timedelta(days=i) for i in xrange(0, max_days) ]
    
    birthdays = []
    for d in days:
        for p in Profile.objects.filter(birthday__month=d.month, birthday__day=d.day):
            birthdays.append(p)
    
    print birthdays
    

    2) Single query, but requires a model change. You would need to add bday_month and bday_day integer fields. These can obviously be populated automatically from the real date.

    The limitation of this example is that you can only check against 2 months, start month and the end month. Setting 29 days you could jump over february, showing only Jan 31 and Mar 1.

    from django.db.models import Q    
    start = datetime.date.today()
    end = start + datetime.timedelta(days=14)
    
    print Profile.objects.filter(
        Q(bday_month=start.month) & Q(bday_day__gte=start.day) | 
        Q(bday_month=end.month) & Q(bday_day__lte=end.day)
    )
    
    0 讨论(0)
提交回复
热议问题