Reputation: 7743
I have a model, call it Foo
, which has an attribute created_at
which notes the time (UTC) when an instance was created.
I would like to annotate a query set over this model with the number of seconds since each item returned was created.
(to be more precise, I would like to "discount" items based on the time they have been open, with a configurable stale-out time, but I can do that computation if I can get the number of seconds since create)
I have tried some obvious things like
Foo.objects.annotate(time_since_create=now-F('created_at'))
and
Foo.objects.annotate(time_since_create=now.timestamp()-F('created_at'))
(where now is
utc.localize(datetime.utcnow())`)
but these get me AttributeError: 'float' object has no attribute 'tzinfo'
, which suggests that DB arithmetic on dates is a little more complex than I'm thinking it ought to be.
Obviously, I can do this arithmetic in python, ie,
[(id, now-created_at) for id, created_at in Foo.objects.values_list("id", "created_at")]
but it would be nicer if I could get this back in the queryset.
Upvotes: 1
Views: 2254
Reputation: 525
One solution you could use is extra in django queryset. You don't need to perform annotate or aggregate if you need difference for all the records.
My database is mysql. So I used TIMESTAMPDIFF in seconds. TIMESTAMPDIFF is from mysql db. You need to use the function available in your db in the below queryset. Hope this helps.
Visit https://docs.djangoproject.com/en/2.2/ref/models/querysets/#s-extra
time_since_create = {"time_since_create": """TIMESTAMPDIFF(SECOND, created_at, now())"""}
objs = Foo.objects.extra(select=time_since_create).values('name', 'time_since_create')
str(objs.query)
If you print the sql query, it will be something like this.
SELECT (TIMESTAMPDIFF(SECOND, created_at, now())) AS `time_since_create`, `foo`.`name` FROM `foo`
Upvotes: 3
Reputation: 2035
First approach
You can filter your query set like so
time_threshold = datetime.now() - timedelta(munutes=40)
Foo.objects.filter(created_at__lte=time_threshold)
output
> <QuerySet [<Foo: 2019-11-18 20:56:16.228271+00:00>, <Foo: 2019-11-18
> 20:56:31.842079+00:00>, <Foo: 2019-11-18 20:56:32.761461+00:00>, <Foo:
> 2019-11-18 20:57:04.612091+00:00>, <Foo: 20 19-11-18
> 20:57:11.401817+00:00>, <Foo: 2019-11-18 21:00:12.794631+00:00>, <Foo:
> 2019-11-18 21:00:14.935930+00:00>, <Foo: 2019-11-18
> 21:00:59.107677+00:00>, <Foo: 2019-11-18 21:01:27.5 90956+00:00>]>
Second approach
You can create a def in your model to take the time elapsed since create and then use it
class Foo(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
def time_elapsed_since_create(self):
return datetime.now(timezone.utc) - self.created_at
def __str__(self):
return str(self.created_at)
Output
>>> bar = Foo.objects.create()
>>> print(bar.time_elapsed_since_create())
0:00:09.480279
>>> print(bar.time_elapsed_since_create())
0:00:10.560474
>>> print(bar.time_elapsed_since_create())
0:00:11.248255
>>> print(type(bar.time_elapsed_since_create()))
<class 'datetime.timedelta'>
Function to get all of the Foos that are older than 60 seconds
>>> for i in Foo.objects.all():
... if i.time_elapsed_since_create() > timedelta(seconds=60):
... print(i.id, i.created_at, i.time_elapsed_since_create())
...
1 2019-11-18 20:56:16.228271+00:00 0:13:12.512974
2 2019-11-18 20:56:31.842079+00:00 0:12:56.899166
3 2019-11-18 20:56:32.761461+00:00 0:12:55.979784
4 2019-11-18 20:57:04.612091+00:00 0:12:24.129154
5 2019-11-18 20:57:11.401817+00:00 0:12:17.340419
6 2019-11-18 21:00:12.794631+00:00 0:09:15.947605
7 2019-11-18 21:00:14.935930+00:00 0:09:13.806306
8 2019-11-18 21:00:59.107677+00:00 0:08:29.634559
9 2019-11-18 21:01:27.590956+00:00 0:08:01.152280
Upvotes: 0