Using dateadd in django filter
Question:
I have a model that defines subscription periods by start date and duration (in days):
class SubscriptionProduct(models.Model):
start_date = models.DateField()
duration = models.IntegerField()
I need to be able to filter subscriptions that are currently active, e.g. start_date < now < start_date+duration
I can’t find the django way to do it. I can use raw SQL statements that use postgres’ DATEADD equivalent of INTERVAL but i’d prefer to use something builtin and non db specific.
I assume ideally i’m looking for a dateadd annotation method. Something like:
SubscriptionProduct.objects.annotate(end_date=DateAdd('start_date','duration').filter(start_date__lt=datetime.now, end_date__gte=datetime.now)
Answers:
I ended up writing a custom Func expression that does exacly what I was looking for.
This is very Postgresql specific and a bit hacky but it works, even when used in more complex queries than the one illustrated above.
class DateAdd(Func):
"""
Custom Func expression to add date and int fields as day addition
Usage: SubscriptionProduct.objects.annotate(end_date=DateAdd('start_date','duration')).filter(end_date__gt=datetime.now)
"""
arg_joiner = " + CAST("
template = "%(expressions)s || ' days' as INTERVAL)"
output_field = DateTimeField()
Note that I had to do the arg_joiner trick in order for both field names to be resolved properly when used in subselect expressions
here is the version for MySQL:
from django.db.models import Func, DateTimeField
class DateAdd(Func):
function = 'DATE_ADD'
template = "%(function)s(%(expressions)s, INTERVAL %(days)i DAY)"
output_field = DateTimeField()
and then
queryset.annotate(days_added=DateAdd(f'date_field_name', days=1))
In my way, this is work version:
class DateAdd(Func):
function = 'DATE_ADD' # function name
arg_joiner = ', INTERVAL ' # joiner, work as simple python join). Example: 'expressions[0], INTERVAL expressions[1]'
template = "%(function)s(%(expressions)s DAY)" # template of query part
output_field = DateField() # result db type
Example:
SubscriptionProduct.objects.filter(end_date=DateAdd('start_date', 'duration'))
Result:
SELECT ...
FROM subscription_product
WHERE end_date = DATE_ADD(start_date, INTERVAL duration DAY)
I have a model that defines subscription periods by start date and duration (in days):
class SubscriptionProduct(models.Model):
start_date = models.DateField()
duration = models.IntegerField()
I need to be able to filter subscriptions that are currently active, e.g. start_date < now < start_date+duration
I can’t find the django way to do it. I can use raw SQL statements that use postgres’ DATEADD equivalent of INTERVAL but i’d prefer to use something builtin and non db specific.
I assume ideally i’m looking for a dateadd annotation method. Something like:
SubscriptionProduct.objects.annotate(end_date=DateAdd('start_date','duration').filter(start_date__lt=datetime.now, end_date__gte=datetime.now)
I ended up writing a custom Func expression that does exacly what I was looking for.
This is very Postgresql specific and a bit hacky but it works, even when used in more complex queries than the one illustrated above.
class DateAdd(Func):
"""
Custom Func expression to add date and int fields as day addition
Usage: SubscriptionProduct.objects.annotate(end_date=DateAdd('start_date','duration')).filter(end_date__gt=datetime.now)
"""
arg_joiner = " + CAST("
template = "%(expressions)s || ' days' as INTERVAL)"
output_field = DateTimeField()
Note that I had to do the arg_joiner trick in order for both field names to be resolved properly when used in subselect expressions
here is the version for MySQL:
from django.db.models import Func, DateTimeField
class DateAdd(Func):
function = 'DATE_ADD'
template = "%(function)s(%(expressions)s, INTERVAL %(days)i DAY)"
output_field = DateTimeField()
and then
queryset.annotate(days_added=DateAdd(f'date_field_name', days=1))
In my way, this is work version:
class DateAdd(Func):
function = 'DATE_ADD' # function name
arg_joiner = ', INTERVAL ' # joiner, work as simple python join). Example: 'expressions[0], INTERVAL expressions[1]'
template = "%(function)s(%(expressions)s DAY)" # template of query part
output_field = DateField() # result db type
Example:
SubscriptionProduct.objects.filter(end_date=DateAdd('start_date', 'duration'))
Result:
SELECT ...
FROM subscription_product
WHERE end_date = DATE_ADD(start_date, INTERVAL duration DAY)