Timedelta between two fields - django

I'm looking for objects where the timedelta between two fields is greater than a certain number of days.
Baiscally I have a date when a letter is sent, and a date when an approval is received. When no approval is received in let's say 30 days, then these objects should be included in the queryset.
I can do something like the below, where the delta is something static.
However I don't need datetime.date.today() as a start but need to compare against the other object.
delta = datetime.date.today() - datetime.timedelta(30)
return qs.filter(letter_sent__isnull=False)\
.filter(approval_from__isnull=True)\
.filter(letter_sent__gte=delta)
Any pointer how to do this?

Sounds like you want to annotate with an F object. Something like this:
from django.db.models import DurationField, ExpressionWrapper, F
delta = datetime.timedelta(days=30)
expression = F('approval_from') - F('letter_sent')
wrapped_expression = ExpressionWrapper(expression, DurationField())
qs = qs.annotate(delta=wrapped_expression)
qs = qs.filter(delta__gte=delta)

You can also make it the other way around. Just keep F("num_days") outside of timedelta because timedelta doesn't know about F().
from datetime import timedelta
expression = F('approval_from') - timedelta(days=1) * F("num_days")
wrapped_expression = ExpressionWrapper(expression, DateTimeField())
qs = qs.annotate(letter_sent_annotation=wrapped_expression)
qs = qs.filter(letter_sent__gte=letter_sent_annotation)

Related

Django - Add Nearest Monday to Queryset

I have an Order model like so:
class Order(models.Model):
created_at = models.DateTimeField(...)
An order can be created at any time, but all orders get shipped out on the following Monday.
How can I add an extra field to my orders queryset called assembly_date that reflects the next Monday (date the order should be shipped)?
I tried creating a custom OrderManager like so, but am not sure how to correctly set the assembly_date:
from django.db.models import F, ExpressionWrapper, DateField
from django.db.models.functions import ExtractWeekDay
class OrderManager(models.Manager):
def get_queryset():
# need help with logic here:
return self.super().get_queryset().annotate(
assembly_date = ExpressionWrapper(
F("created_at") - ExtractWeekDay("created_at"),
output_field = DateField(),
)
)
But this operation results in the following error:
django.db.utils.ProgrammingError: operator does not exist: timestamp with time zone - double precision
LINE 1: ...E NULL END) * 2628000.0) * INTERVAL '1 seconds')) - EXTRACT(...
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
Keep in mind, I want to be able to filter all orders based on their assembly_date.
Basically you need to dynamically generate timedelta inside of annotate. But as far as I know, there is no way you can apply isoweekday() to a datetimefield inside of annotate.
You can have another field as assembly_date in your model, and use it directly to query.
from datetime import timedelta, date
class Order(models.Model):
created_at = models.DateTimeField(...)
assembly_date = models.DateTimeField(...)
def save(self, *args, **kwargs):
weekday = self.created_at.isoweekday() # 1 is Monday, 2 is Tuesday.
daysdiff = 8 - weekday
self.assembly_date = self.created_at + timedelta(days= daysdiff)
super(Order, self).save(*args, **kwargs)

Adding a field to queryset in django

I have a model whose fields are datetimefields start_time and end_time
I want to display on the API- start_time, end_time and the difference between them
However, I don't want to use for or while loop to achieve this because it is too slow
How can I get the difference on the API without looping
You can annotate the queryset with the difference, and then use an ExpressionWrapper to convert this to a DurationField, and thus obtain a timedelta objects:
from django.db.models import DurationField, ExpressionWrapper, F
MyModel.objects.annotate(
time_diff=ExpressionWrapper(
F('end_time')-F('start_time'),
output_field=DurationField()
)
)
The MyModels that arise from this queryset will thus have an extra attribute time_diff that is a timedelta field containing the difference between end_time, and start_time.

Django model elapsed time between two DateTimeFields

I am pretty new to django and haven't been able to find a way to get the elapsed time between two DateTimeFields and save it to another model.
from django.db import models
class Timesheet(models.Model):
startTime = models.DateTimeField(auto_now_add=True)
endTime = models.DateTimeField(blank=True, null=True)
duration = models.DateTimeField(endTime - startTime)
def _str_(self):
return self.startTime
How can I make duration = endTime - startTime?
I am also using a PostgreSQL database.
I wouldn't use a dedicated model field for the duration.
I would use a property on the model instead for the same functionality.
Something like:
#property
def duration(self)
return self.end_time - self.startime
Lucas has a good idea of using an annotation, but if you have a Timesheet instance somewhere that didn't come from that object manager and was not previously annotated, you would have to do a separate database hit to actually annotate it.
This property is used as such:
some_timesheet_instance.duration
Use annotate() to compute the duration field at query time for each object in the queryset
from django.db.models import F, ExpressionWrapper, fields
timesheets = Timesheet.objects.annotate(
duration=ExpressionWrapper(
F('endTime') - F('startTime'),
output_field=fields.DurationField()
)
)
timesheets[0].duration # datetime.timedelta(0, 722, 18373)
Is possible perform another queryset methods over annotations like filter(), order_by(), aggregate(), etc.
timesheets.order_by('-duration')
timesheets.aggregate(Avg('duration')) # {'duration__avg': datetime.timedelta(0, 26473, 292625)}
duration = timesheet.end_time - timesheet.start_time
When you substract two datetime instances you don't get another datetime instance but a timedelta instace, which is just the days, seconds and microseconds difference between the two datetimes. You can't store a timedelta in a DateTimefield, but you can use an IntegerField, for example:
days_in_seconds = duration.days * 86400 # days difference by seconds in a day
duration_in_seconds = duration.seconds + days_in_seconds # duration in seconds
When you want to access the duration as timedelta you just do:
import datetime
duration = datetime.timedelta(seconds=timesheet.duration)
You can also store it as FloatField as suggested in this question.

How to annotate a difference of datetime in days

I have a Booking model that has start and end datetime fields. I want to know how many days a booking covers. I can do this in Python but I need this value for further annotations.
Here's what I've tried:
In [1]: Booking.objects.annotate(days=F('end')-F('start'))[0].days
Out[1]: datetime.timedelta(16, 50400)
There are a few problems here:
I want an integer (or other number type I can use in calculations) of days as the output, not a timedelta. Setting output_field doesn't do anything meaningful here.
My sums are based on datetimes. Subtractions like this, without removing the time could lead to the whole number of days being off.
In Python I would do (end.date() - start.date()).days + 1. How can I do that in-database, preferably through the ORM (eg database functions), but a RawSQL would suffice to get this out the door?
I've written a couple of database functions to cast and truncate the dates to solve both problems under PostgreSQL. The DATE_PART and DATE_TRUNC internal function I'm using are DB-specific ☹
from django.db.models import Func
class DiffDays(Func):
function = 'DATE_PART'
template = "%(function)s('day', %(expressions)s)"
class CastDate(Func):
function = 'date_trunc'
template = "%(function)s('day', %(expressions)s)"
Then I can:
In [25]: Booking.objects.annotate(days=DiffDays(CastDate(F('end'))-CastDate(F('start'))) + 1)[0].days
Out[25]: 18.0
There is another, easy solution of this problem. You can use:
from django.db.models import F
from django.db.models.functions import ExtractDay
and then:
Booking.objects.annotate(days=(ExtractDay(F('end')-F('start'))+1))[0].days
If you are using MYSQL database, You could do it using Custom DB Function as,
from django.db.models.functions import Func
class TimeStampDiff(Func):
class PrettyStringFormatting(dict):
def __missing__(self, key):
return '%(' + key + ')s'
def __init__(self, *expressions, **extra):
unit = extra.pop('unit', 'day')
self.template = self.template % self.PrettyStringFormatting({"unit": unit})
super().__init__(*expressions, **extra)
function = 'TIMESTAMPDIFF'
template = "%(function)s(%(unit)s, %(expressions)s)"
Usage
from django.db.models import F, IntegerField
booking_queryset = Booking.objects.annotate(
days=TimeStampDiff(F('start'), F('end'), output_field=IntegerField()))
if booking_queryset.exist():
print(booking_queryset[0].__dict__)

Recursive query: comparing one field to another of the same class

I have a model similar to this one:
class Trip(models.Model):
departure = models.DateTimeField()
arrival = models.DateTimeField()
And I want to make a query that returns objects where the arrival is at least 2 hours later than the departure.
Trip.objects.filter(arrival__gt = "departure" + timedelta(hours=2))
Is this even possible? thanks
You are looking for filters that reference fields ont the model
But what if you want to compare the value of a model field with
another field on the same model?
See this sample:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
For your case:
from django.db.models import F
Trip.objects.filter(arrival__gt = F('departure') + timedelta(hours=2))