【发布时间】:2011-09-02 00:05:05
【问题描述】:
我如何在接下来的 X 天内获得生日的人的查询集?我看到了this 的回答,但它不适合我,因为只能获取当前出生年份的人。
【问题讨论】:
-
不了解 Django,但您不会只寻找
birthday <= (today + X days)的等价物吗?
标签: python django datetime django-models django-queryset
我如何在接下来的 X 天内获得生日的人的查询集?我看到了this 的回答,但它不适合我,因为只能获取当前出生年份的人。
【问题讨论】:
birthday <= (today + X days) 的等价物吗?
标签: python django datetime django-models django-queryset
假设这样的模型--
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()。
假设它是 datetime 字段做这样的事情(使用来自 dimosaur 答案的 future_date):
Profile.objects.get(
Q(birthday__lte=future_date),
Q(birthday__gte=datetime.date.today())
)
【讨论】:
在不使用自定义查询的情况下,我可以想到 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)
)
【讨论】:
如果 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
)
类似的东西。
【讨论】:
我试图以一种非常愚蠢的方式做到这一点,但似乎可行:
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))
【讨论】:
我对这里的所有回复都不满意。它们都是“在一个范围内逐个检查一个日期/年份......”的变体,提出了一个长而丑陋的查询。这是一个简单的解决方案,如果有人愿意去规范化一点:
更改您的模型,以便添加一个datetime birthday(DUMMY_YEAR, mm, dd) 列,而不是只保留datetime birthdate(yyyy, mm, dd) 的真实日期。因此,您数据库中的每个人都将保存其真实出生日期,然后保存另一个具有固定年份的出生日期,与其他人共享。但是,不要向用户显示第二个字段,也不允许他们编辑它。
编辑模型后,确保 birthdate 和 birthday 始终通过在您的类中扩展 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
【讨论】: