【问题标题】:Django time difference with F objectDjango与F对象的时差
【发布时间】:2013-09-01 04:30:54
【问题描述】:

我有以下型号:

class Assignment(models.Model):
  extra_days = models.IntegerField(default=0)
  due_date = models.DateTimeField()

其中due_date 是作业的截止日期,extra_days 是在截止日期之后为完成作业而给予的额外天数。

我想创建一个查询,它返回due_date + extra_days 大于当前日期的所有行。这就是我正在做的事情:

from django.utils import timezone
from django.db.models import F
from datetime import datetime

cur_date = timezone.make_aware(datetime.now(), timezone.get_default_timezone())
a = Assignment.objects.filter(extra_days__gt=cur_date - F('due_date'))

当我打印a 时,我收到以下错误:

  File "c:\Python27\lib\site-packages\MySQLdb\cursors.py", line 204, in execute
    if not self._defer_warnings: self._warning_check()
  File "c:\Python27\lib\site-packages\MySQLdb\cursors.py", line 117, in _warning
_check
    warn(w[-1], self.Warning, 3)
Warning: Truncated incorrect DOUBLE value: '2013-09-01 02:54:31'

如果我做了一个导致 3.1 天的时差,我假设天差仍然是 3。我认为这样做会更正确:

a = Assignment.objects.filter(due_date__gt=cur_date - timedelta(days=F('extra_days')))

但这也会导致错误。

如何在不编写原始 SQL 查询的情况下做到这一点?

【问题讨论】:

  • 将两个日期转换为timedelta 并减去。
  • 你的意思是这样的:a = Assignment.objects.filter(extra_days__gt=timedelta(cur_date - F('due_date')).days) 我得到这个错误:TypeError: unsupported type for timedelta days component: ExpressionNode
  • 你可以使用 django ORM。参考this

标签: django


【解决方案1】:

这取决于您使用的数据库后端,它似乎是 PostgreSQL。

PostgreSQL 可以直接减去日期,所以下面的方法会起作用:

from django.db.models import F, Func
from django.db.models.functions import Now

class DaysInterval(Func):
    function = 'make_interval'
    template = '%(function)s(days:=%(expressions)s)'

qs = Assignment.objects.annotate(remaining_days=F('due_date') - Now())
qs.filter(remaining_days__lt=DaysInterval(F('extra_days')))

这会产生以下 SQL:

SELECT "assignments_assignment"."id", 
       "assignments_assignment"."extra_days", 
       "assignments_assignment"."due_date", 
       ("assignments_assignment"."due_date" - STATEMENT_TIMESTAMP()) AS "remaining_days" 
FROM   "assignments_assignment" 
WHERE  ("assignments_assignment"."due_date" - STATEMENT_TIMESTAMP())
        < (make_interval(DAYS:="assignments_assignment"."extra_days"))

有关其他数据库后端中的日期差异计算,请参阅 Michael Brooks 创建的 Datediff 函数。

【讨论】:

    【解决方案2】:

    看来我正在尝试做的事情是不可能的。我最终写了一个原始查询:

    cursor.execute("SELECT * FROM app_assignment WHERE DATE_ADD(due_date, INTERVAL extra_days DAYS) > utc_timestamp()")
    

    我很反感不能使用 ORM 来做一些看似简单的事情,以至于我考虑尝试 SQLAlchemy,但原始查询可以正常工作。我总是尝试变通方法以确保我可以使用 ORM,但我将使用原始 SQL 来处理复杂的查询。

    【讨论】:

    • 这个答案现在是不正确的,因为这绝对可以在 Django 中完成,而无需借助原始 SQL,至少在使用 Postgres 时是这样。 See the answer below by @mrts.
    【解决方案3】:

    据我所知,您可以将 F() 对象作为 params 传递给另一个函数,因为 F() 基类是 tree.Node 类型, 用于存储树形图的类,主要用于 ORM 中的过滤器构造。

    参见django/db/models/expression.py 处的 F() 定义和 django/utils/tree.py 处的 Node (django 1.3.4)

    class ExpressionNode(tree.Node):
        ...
    
    class F(ExpressionNode):
        """
        An expression representing the value of the given field.
        """
        def __init__(self, name):
            super(F, self).__init__(None, None, False)
            self.name = name
    
        def __deepcopy__(self, memodict):
            obj = super(F, self).__deepcopy__(memodict)
            obj.name = self.name
            return obj
    
        def prepare(self, evaluator, query, allow_joins):
            return evaluator.prepare_leaf(self, query, allow_joins)
    
        def evaluate(self, evaluator, qn, connection):
            return evaluator.evaluate_leaf(self, qn, connection)
    

    你可以做类似的事情

    Assignment.objects.filter(due_date__gt=F('due_date') - timedelta(days=1))
    

    但不是

    Assignment.objects.filter(due_date__gt=cur_date - timedelta(days=F('extra_days')))
    

    如果我错了,请纠正我。希望这个小小的帮助。

    【讨论】:

    • 谢谢。但最后一个陈述是我在功能上想要做的。日子必须来自数据库。我想如果不使用原始 SQL(这是一个非常简单的查询),这在 Django 中是不可能的。
    【解决方案4】:

    以防万一其他人寻找这个,这里有一些可能值得研究的东西。

    我正在使用 Django 1.4,并且遇到了与 OP 完全相同的问题。似乎问题可能是由于timedeltadatetime 需要在发送到数据库之前进行评估,但F 对象本质上只能在数据库中解决。

    我注意到在 Django 1.8 中,引入了一个新的 DurationField,看起来它可以像 python 的 timedelta 一样直接工作。这应该意味着不需要在 IntegerField 上查找 F 对象的 timedelta,理论上可以使用 DurationField,然后 F 对象根本不需要在 timedelta 中。不幸的是,由于依赖关系,我目前无法将我的项目升级到 1.8 并测试这个理论。

    如果其他人遇到这个问题并且能够测试我的建议,我很想知道。如果我解决了我的依赖关系并且可以升级到 1.8,那么我一定会回贴我的结果。

    【讨论】:

    • Django 1.8 引入了query expressions,这可能就是我要找的。一旦有机会,我想尝试一下。
    • 这是我找到的最佳解决方案!我的代码如下所示:.annotate(date_end=ExpressionWrapper(F('date_start') + Sum('days'), output_field=DateField()))。我不得不使用ExpressionWrapper 来提供output_field 参数,否则Django 会在操作中抱怨混合类型(参见docs)。显然,days 是我模型的DurationField
    • @Filly 我很高兴听到 DurationField 确实适用于这种情况。感谢发帖!
    猜你喜欢
    • 2017-10-08
    • 1970-01-01
    • 2017-03-30
    • 1970-01-01
    • 2011-02-25
    • 1970-01-01
    • 1970-01-01
    • 2016-11-09
    • 1970-01-01
    相关资源
    最近更新 更多