在django使用F()和timedelta进行查询

时间:2011-05-28 00:26:40

标签: django orm

我有以下型号:

class Process(models.Model):
  title = models.Charfield(max_length=255)
  date_up = models.DateTimeField(auto_now_add=True)
  days_activation = models.PositiveSmallIntegerField(default=0)

现在,我需要根据Process的值查询已过期的所有days_activation个对象。

我试过

from datetime import datetime, timedelta

Process.objects.filter(date_up__lte=datetime.now()-timedelta(days=F('days_activation')))

并收到以下错误消息:

  

TypeError:timedelta days组件的不支持类型:F

我当然可以用Python做到这一点:

filter (lambda x: x.date_up<=datetime.now() - timedelta(days=x.days_activation), 
        Process.objects.all ()), 

但我真的需要制作django.db.models.query.QuerySet

4 个答案:

答案 0 :(得分:1)

您正在混合两个层:运行时层和数据库层。 F函数只是一个帮助器,它允许您使用django ORM构建稍微复杂的查询。您正在使用timedeltaF,并期望django ORM足够聪明,可以将这些内容转换为原始SQL,但正如我所见,它不能。也许我错了,对django ORM一无所知。

无论如何,您可以使用额外的extra重写ORM调用,并使用等于datetime.now()timedelta的本机SQL函数手动构建WHERE子句。

答案 1 :(得分:1)

您必须扩展Aggregate。如下所示:

from django.db import models as DM

class BaseSQL(object):
    function = 'DATE_SUB'
    template = '%(function)s(NOW(), interval %(expressions)s day)'

class DurationAgr(BaseSQL, DM.Aggregate):
    def __init__(self, expression, **extra):
        super(DurationAgr, self).__init__(
            expression,
            output_field=DM.DateTimeField(),
            **extra
        )

Process.objects.filter(date_up__lte=DurationAgr('days_activation'))

希望它会对你有用。 :)

答案 2 :(得分:0)

7天== 1天* 7

F是深黑色的Django魔法和遇到它的物体 必须属于适当的魔法圈来处理它。

在您的情况下,django.db.models.query.filter知道F,但datetime.timedelta却不知道。 因此,您需要将F保留在timedelta参数列表之外。 幸运的是,timedelta * int支持乘以F, 以下是可行的:

Process.objects.filter(date_up__lte=datetime.now()-timedelta(days=1)*F('days_activation'))

事实证明,这是will work with PostgreSQL,但不适用于SQlite(Django 1.11仅为+支持-timedelta, 也许是因为相应的SQlite限制。)

答案 3 :(得分:0)

我尝试使用上述Lutz Prechelt的解决方案,但收到MySQL语法错误。 这是因为我们无法在MySQL中使用INTERVAL执行算术运算。

因此,对于MySQL,我的解决方案是创建一个自定义DB函数:

class MysqlSubDate(Func):
    function = 'SUBDATE'
    output_field = DateField()

用法示例:

.annotate(remainded_days=MysqlSubDate('end_datetime', F('days_activation')))

您还可以使用timedelta,它将转换为INTERVAL

.annotate(remainded_days=MysqlSubDate('end_datetime', datetime.timedelta(days=10)))