Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django ORM: Filter on timedelta of Datetime fields

I am trying to fetch posts based on the time difference of two DateTimeFields, say, posts that were deleted in less than 10 minutes after they were posted.

class Post(models.Model):
    ...
    time_posted = models.DateTimeField()
    time_deleted = models.DateTimeField(blank=True, null=True)

With the above model in hand, I tried;

from datetime import timedelta

Post.objects.exclude(deleted__isnull=True).annotate(
    delta=F('time_deleted') - F('time_posted')
).filter(delta__lt=timedelta(minutes=10))

and got a TypeError: expected string or buffer. Then I thought it may be the change of type (DateTime objects yielding Time object) so I tried it with ExpressionWrapper:

Post.objects.exclude(deleted__isnull=True).annotate(
    delta=models.ExpressionWrapper(
        F('time_deleted') - F('time_posted'), 
        output_field=models.TimeField())
    ).filter(delta__gt=timedelta(minutes=10))

But this caused the same exception too.

Any help is much appreciated.

EDIT

According to @ivan's suggestion, I tried DurationField() instead. I no longer get the exception but the delta is always 0.

>>> post = Post.objects.exclude(deleted__isnull=True).annotate(
        delta=ExpressionWrapper(F('deleted') - F('time'), 
        output_field=DurationField())
    ).first()
>>> post.time_posted
datetime.datetime(2015, 8, 24, 13, 26, 50, 857326, tzinfo=<UTC>)
>>> post.time_deleted
datetime.datetime(2015, 8, 24, 13, 27, 30, 521569, tzinfo=<UTC>)
>>> post.delta
datetime.timedelta(0)
like image 861
onurmatik Avatar asked Sep 26 '22 16:09

onurmatik


1 Answers

The output_field kwarg should be DurationField instead, because it stores datetime.timedelta in Django, while TimeField stores datetime.time.

There is a caveat though:

Arithmetic with DurationField works in most cases. However on all databases other than PostgreSQL, comparing the value of a DurationField to arithmetic on DateTimeField instances will not work as expected.

In the SQLite backend DurationField is represented by bigint which stores microseconds:

class DatabaseWrapper(BaseDatabaseWrapper):
    vendor = 'sqlite'
    # ...
    data_types = {
        # ...
        'DecimalField': 'decimal',
        'DurationField': 'bigint',
        'FileField': 'varchar(%(max_length)s)',
        # ...
    }

So because you are using SQLite you actually need delta to be a value in microseconds. See this answer for a Func which does that.

like image 138
Ivan Avatar answered Sep 30 '22 07:09

Ivan