Создание запросов с использованием F () и timedelta в django

#django #orm

#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')))
  

и получил следующее сообщение об ошибке:

Ошибка типа: неподдерживаемый тип для компонента 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 .

Ответ №1:

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'))
  

Как оказалось, это будет работать с PostgreSQL, но не будет работать с SQLite (для которого Django 1.11 поддерживает только и - для timedelta ,
возможно, из-за соответствующего ограничения SQLite).

Ответ №2:

Вы смешиваете два уровня: уровень времени выполнения и уровень базы данных. F функция — это просто помощник, который позволяет вам создавать немного более сложные запросы с помощью django ORM. Вы используете timedelta и F вместе и ожидаете, что django ORM будет достаточно умен, чтобы преобразовать эти вещи в raw SQL, но, как я вижу, это не так. Возможно, я ошибаюсь и чего-то не знаю о django ORM.

В любом случае, вы можете переписать свой вызов ORM с помощью дополнительных extra и создать предложение WHERE вручную, используя собственные функции SQL, которые равны datetime.now() и timedelta .

Ответ №3:

Вы должны расширить 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'))
  

Надеюсь, у вас это сработает. 🙂

Ответ №4:

Я попытался использовать решение Лутца Пречелта, приведенное выше, но получил синтаксическую ошибку MySQL. Это потому, что мы не можем выполнять арифметические операции с ИНТЕРВАЛОМ в MySQL.

Итак, для MySQL мое решение — создать пользовательскую функцию DB:

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

Пример использования:

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

Также вы можете использовать timedelta, он будет преобразован в ИНТЕРВАЛ

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