Home > database >  Annotate QuerySet with the maximum of a date field and a manually created date
Annotate QuerySet with the maximum of a date field and a manually created date

Time:01-05

I have the following model:

class Item(models.Model):
    last_used = models.DateTimeField()
    created_at = models.DateTimeField()

I'm trying now to look at the average "lifetime" of items per month. Lifetime is how many days items are used, on average. I can calculate the overall average with the following:

from django.db.models import IntegerField, F, Avg
from django.db.models.functions import Cast, ExtractDay, TruncDate

items = Item.objects.annotate(days_used=Cast(ExtractDay(TruncDate(F('last_used')) - TruncDate(F('created_at'))), IntegerField()) 1)
avg = items.aggregate(Avg('days_used'))['days_used__avg']

Now when I want to calculate this per month, I would like to annotate the query with a new field that is the maximum of created_at or the beginning of the month in question. This is needed so that the maximum lifetime value for all months is the number of days in that month.

There's a function, Greatest that I think could be used for this, but I'm not sure how. Assuming we're interested in December, 2021, this is what I have at the moment:

from django.db.models.functions import Greatest
target_month = 12
items = items.annotate(created_max=Greatest(TruncDate(F('created_at')), TruncDate(F(timezone.datetime(2021, target_month, 1)))

But using timezone.datetime is causing an error:

AttributeError: 'datetime.datetime' object has no attribute 'split'

NOTE: I'm aware that last_used may cause the value to go over the number of days but that's another issue we're tackling.

Solved

Thanks to @Eugenij this using Value instead of F works:

items.annotate(created_max=Greatest(TruncDate(F('created_at')), Value(timezone.datetime(2021, target_month, 1)))

CodePudding user response:

You should use django.db.models.Value expression instead of django.db.models.F for constant values.

from django.db.models.functions import Greatest
target_month = 12
items = items.annotate(created_max=Greatest(TruncDate(F('created_at')), TruncDate(Value(timezone.datetime(2021, target_month, 1)))
  •  Tags:  
  • Related