【问题标题】:Queryset of people with a birthday in the next X days在接下来的 X 天内过生日的人的查询集
【发布时间】:2011-09-02 00:05:05
【问题描述】:

我如何在接下来的 X 天内获得生日的人的查询集?我看到了this 的回答,但它不适合我,因为只能获取当前出生年份的人。

【问题讨论】:

  • 不了解 Django,但您不会只寻找 birthday <= (today + X days) 的等价物吗?

标签: python django datetime django-models django-queryset


【解决方案1】:

假设这样的模型--

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

下一步将创建一个查询,过滤掉所有生日在 (now.month, now.day) 和 (then.month, then.day) 之间的月份和日期的记录。您实际上可以使用查询集 API 访问 datetime 对象的月份和日期属性,方法是向 Person.objects.filter 传递一个像这样的关键字参数:“birthday__month”。我尝试了一个实际的查询集 API 方法,比如“birthday__month__gte”,但它失败了。所以我建议简单地生成一个月/日元组的文字列表,表示您想要记录的日期范围内的每个(月,日),然后使用 django.db.models.Q 将它们全部组合成一个查询,如下所示:

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)

调试后,这应该返回一个查询集,每个人的生日月日等于指定元组列表中的任何月或日。

【讨论】:

  • 谢谢,我认为这个解决方案是迄今为止提交的所有解决方案中最正确的。
  • 我唯一要改变的是将datetime.now()替换为timezone.now()
【解决方案2】:

假设它是 datetime 字段做这样的事情(使用来自 dimosaur 答案的 future_date):

Profile.objects.get(
    Q(birthday__lte=future_date),
    Q(birthday__gte=datetime.date.today())
)

【讨论】:

  • 这只会获取当前出生年份的人。
【解决方案3】:

在不使用自定义查询的情况下,我可以想到 2 种方法,两者都有“问题”

1) 效率不高,因为它每天执行 1 次查询

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) 单个查询,但需要更改模型。您需要添加 bday_month 和 bday_day 整数字段。这些显然可以根据实际日期自动填充。

此示例的限制是您只能检查 2 个月,即开始月份和结束月份。设置 29 天,您可以跳过 2 月,仅显示 1 月 31 日和 3 月 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)
)

【讨论】:

  • 查看 django.db.models.Q,您可以使用它来将多个查询参数组合到一个查询中。如果您将多个约束与 Q 一起“或”在我的答案中,您的第一个解决方案将使用单个查询。
【解决方案4】:

如果 X 是一个你知道的常数:

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
)

类似的东西。

【讨论】:

  • 这将导致只有一天,我需要今天和 X 天之间的范围。
【解决方案5】:

我试图以一种非常愚蠢的方式做到这一点,但似乎可行:

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))

【讨论】:

  • 我想它可以转向昂贵的数据库查询:D
【解决方案6】:

我对这里的所有回复都不满意。它们都是“在一个范围内逐个检查一个日期/年份......”的变体,提出了一个长而丑陋的查询。这是一个简单的解决方案,如果有人愿意去规范化一点:

更改您的模型,以便添加一个datetime birthday(DUMMY_YEAR, mm, dd) 列,而不是只保留datetime birthdate(yyyy, mm, dd) 的真实日期。因此,您数据库中的每个人都将保存其真实出生日期,然后保存另一个具有固定年份的出生日期,与其他人共享。但是,不要向用户显示第二个字段,也不允许他们编辑它。

编辑模型后,确保 birthdatebirthday 始终通过在您的类中扩展 models.Model 保存方法连接:

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

一旦您确保无论何时将日期保存为生日,生日也会更新,您可以仅使用 birthday__gte/birthday__lte 过滤它。查看我的管理过滤器的摘录,我在其中处理了一年的界限:

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)
                    )
                )

如果您想知道Q() 是什么,请查看Complex lookups with Q objects

【讨论】:

    猜你喜欢
    • 2011-08-21
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 1970-01-01
    • 1970-01-01
    • 2011-08-11
    • 2016-08-22
    • 1970-01-01
    相关资源
    最近更新 更多