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