4

I want to do a query based on two fields of a model, a date, offset by an int, used as a timedelta

model.objects.filter(last_date__gte=datetime.now()-timedelta(days=F('interval')))

is a no-go, as a F() expression cannot be passed into a timedelta

A little digging, and I discovered DateModifierNode - though it seems it was removed in this commit: https://github.com/django/django/commit/cbb5cdd155668ba771cad6b975676d3b20fed37b (from this now-outdated SO question Django: Using F arguments in datetime.timedelta inside a query)

the commit mentions:

The .dates() queries were implemented by using custom Query, QuerySet, and Compiler classes. Instead implement them by using expressions and database converters APIs.

which sounds sensible, and like there should still be a quick easy way - but I've been fruitlessly looking for how to do that for a little too long - anyone know the answer?

Community
  • 1
  • 1
Chozabu
  • 1,015
  • 1
  • 10
  • 33

2 Answers2

5

Ah, answer from the docs: https://docs.djangoproject.com/en/1.9/ref/models/expressions/#using-f-with-annotations

from django.db.models import DateTimeField, ExpressionWrapper, F

Ticket.objects.annotate(
    expires=ExpressionWrapper(
        F('active_at') + F('duration'), output_field=DateTimeField()))

which should make my original query look like

model.objects.annotate(new_date=ExpressionWrapper(F('last_date') + F('interval'), output_field=DateTimeField())).filter(new_date__gte=datetime.now())
Chozabu
  • 1,015
  • 1
  • 10
  • 33
  • 2
    seriously possible for `F('last_date') + F('interval')` and output is datetimefield? and it increased in days same as `timedelta(days=interval)`? – Shift 'n Tab Jan 16 '17 at 10:29
  • Subtracting a number from a date does not work for me as it is not recognized as a subtraction for the days. I'm using mysql. – Johann Jul 12 '19 at 14:46
5

In Django 1.10 there's simpler method to do it but you need to change the model a little: use a DurationField. My model is as follows:

class MyModel(models.Model):

    timeout = models.DurationField(default=86400 * 7)  # default: week
    last = models.DateTimeField(auto_now_add=True)

and the query to find objects where last was before now minus timeout is:

MyModel.objects.filter(last__lt=datetime.datetime.now()-F('timeout'))
Alex
  • 76
  • 1
  • 3
  • Not tested this - but assuming you have ;) looks like a much nicer solution! Will upgrade django soon – Chozabu Dec 16 '16 at 00:29