DjangoORM:解析自定义数据库函数中的 F 表达式

chu*_*gon 2 django django-orm django-queryset django-postgresql django-1.10

我正在尝试在 Django 中编写一个自定义 PostgreSQL 函数,它将强制日期时间为查询集中指定的时区。我对 db 函数的第一次调用如下所示:

from django.db.models.expressions import Func

class DateTimeInTimezone(Func):
    template="%(expressions)s AT TIME ZONE %(tz_info)s"
Run Code Online (Sandbox Code Playgroud)

该函数适用于简单的情况,我直接将时区字符串传递到函数中,如下所示:

MyModel.objects.filter(timefield__gte=DateTimeInTimezone(Now(), tz_info='EST'))
Run Code Online (Sandbox Code Playgroud)

但是,它不适用于更复杂的情况,即时区是在模型的某些字段上定义的。考虑以下人为的示例:

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
Run Code Online (Sandbox Code Playgroud)

要回答“今天当地时间中午 12 点哪些用户将参加会议?”这一问题,我需要此查询集的一些变体:

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'))
)
Run Code Online (Sandbox Code Playgroud)

然而,按照目前的编写,DateTimeInTimezone将只是将字符串注入F('time_zone')到 sql 中,而不是解析该字段。

是否可以向此函数添加对F 表达式的支持?我还应该考虑其他方法吗?

hyn*_*cer 5

使用参数可以实现一个简单的解决方案arg_joiner

class DateTimeInTimezone(Func):
    function = ''
    arg_joiner = ' AT TIME ZONE '

    def __init__(self, timestamp, tz_info):
        super(DateTimeInTimezone, self).__init__(timestamp, tz_info)
Run Code Online (Sandbox Code Playgroud)

该方法__init__仅用于参数名称清晰、可读性强的目的。那么arity参数是否由 声明并不重要__init__

如果可读性不重要,则oneliner函数对于快速开发很有用:

...filter(
    meetings__start_time__lte=Func(noon_utc, tz_info=F('time_zone'), arg_joiner=' AT TIME ZONE ', function=''),
)
Run Code Online (Sandbox Code Playgroud)

已验证:

>>> 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 ...)
Run Code Online (Sandbox Code Playgroud)