Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django filter on date difference between columns

Tags:

python

django

As a Django beginner I struggle with a very basic problem: Filter a table based on the date difference of two columns. I could solve this in raw SQL, but I would really want to use basic Django functions.

I have the following model:

  from django.db import models
  import datetime

  class Race(models.Model):
      __tablename__ = 'race'
      name = models.CharField(max_length=200)
      country = models.CharField(max_length=100, null=True)
      start = models.DateField()
      end = models.DateField()

and I want to extract races that last e.g. more than 5 days. I can somehow get a timediff column:

Race.objects.annotate(tdiff=F('end')-F('start')).first()
set1 = Race.objects.annotate(tdiff=F('end')-F('start')).all()
set1.first().tdiff

Now, how do I filter on this column, what I tried is:

min_diff = datetime.timedelta(5)
set1.filter(tdiff__gte=5).first()
set1.all().filter(tdiff__gte=min_diff)
set1.filter(tdiff__gte=min_diff).first()

but this all gives:

TypeError: expected string or bytes-like object

Then I considered using extra to get a where clause:

set2 = Race.objects.annotate(tdiff=F('end')-F('start'))
set2.first().tdiff
set2.all().extra(where=['tdiff>=5'])

resulting in:

ProgrammingError: column "tdiff" does not exist

Questions in the same direction include this one and this one but none really give a solution where you filter on a new column (here tdiff).


When finalizing this question I in the end did get the result that I wanted by:

Race.objects.filter(end__gte=F("start")+5)
print(Race.objects.filter(end__gte=F("start")+5).query)

but I still very much would like to know how to utilize this temporary column tdiff.

Thanks!


Update after answer

The accepted answer gives exactly what I wanted:

from django.db.models import DurationField, F, ExpressionWrapper
import datetime

set4 = Race.objects.annotate(    
    diff=ExpressionWrapper(F('end') - F('start'), output_field=DurationField())).filter(
    diff__gte=datetime.timedelta(5))
len(set4)
# 364
len(Race.objects.filter(end__gte=F("start")+5))
# 364
like image 356
Martin Avatar asked May 30 '18 09:05

Martin


1 Answers

This would do the magic:

from django.db.models import DurationField, F, ExpressionWrapper
import datetime

Race.objects.annotate(
    diff=ExpressionWrapper(F('end') - F('start'), output_field=DurationField())
).filter(diff__gte=datetime.timedelta(5))

This will return all Race instances whose duration is greater than or equal to 5

References:

  • ExpressionWrapper
  • SO Post - How should I use DurationField in my model?-
like image 99
JPG Avatar answered Oct 27 '22 20:10

JPG