我正在尝试在 Django 中编写一个自定义 PostgreSQL 函数,它将日期时间强制转换为查询集中的指定时区。我对 db 函数的第一次传递看起来像这样:
from django.db.models.expressions import Func
class DateTimeInTimezone(Func):
template="%(expressions)s AT TIME ZONE %(tz_info)s"
此函数适用于我将时区字符串直接传递给函数的简单情况,如下所示:
MyModel.objects.filter(timefield__gte=DateTimeInTimezone(Now(), tz_info='EST'))
然而,它在更复杂的情况下不起作用,在这种情况下,时区是在模型的某个字段上定义的。考虑以下人为的示例:
class User(models.Model):
time_zone = models.CharField()
class Meeting(models.Model):
users = models.ManyToManyField(User, related_name='meetings')
start_time = models.DateTimeField() # in UTC
end_time = models.DateTimeField() # in UTC
要回答“哪些用户将在本地时间今天中午 12 点参加 session ?”这个问题,我需要这个查询集的一些变体:
noon_utc = ...
User.objects.filter(
meetings__start_time__lte=DateTimeInTimezone(noon_utc, tz_info=F('time_zone')),
meetings__end_time__gt=DateTimeInTimezone(noon_utc, tz_info=F('time_zone'))
)
然而,正如目前所写,DateTimeInTimezone
将简单地将字符串 F('time_zone')
注入(inject)到 sql 而不是解析该字段。
是否可以添加对 F Expressions 的支持到这个功能?我应该考虑其他方法吗?
最佳答案
使用 参数 arg_joiner
可以实现一个简单的解决方案:
class DateTimeInTimezone(Func):
function = ''
arg_joiner = ' AT TIME ZONE '
def __init__(self, timestamp, tz_info):
super(DateTimeInTimezone, self).__init__(timestamp, tz_info)
__init__
方法仅用于参数名称清晰的可读性目的。如果参数由 __init__
声明,则 arity
并不重要。
如果可读性不重要,oneliner 函数对于快速开发很有用:
...filter(
meetings__start_time__lte=Func(noon_utc, tz_info=F('time_zone'), arg_joiner=' AT TIME ZONE ', function=''),
)
已验证:
>>> qs = User.objects.filter(...)
>>> print(str(qs.query))
SELECT ... WHERE ("app_meeting"."start_time" <= ((2017-10-03 08:18:12.663640 AT TIME ZONE "app_user"."time_zone")) AND ...)
关于DjangoORM : Resolve F Expression in Custom DB Function,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43397578/