Django, query_set annotation with conditional expression - django

I have a model:
class Document(models.Model):
expiry_date = models.DateField()
How can I build a query which fetches all documents and give them annotation whether the expiry date has passed or not?
I tried this:
today = timezone.now.date()
Document.objects.annotate(
expired=Value(
F('expiry_date')<today,
BooleanField()
)
)
But it raises an error: TypeError: unorderable types: F() < datetime.date()
How can I compare value from F() expression with the date?
Also, I'd like to avoid SQL and .extra()

There's no need to do that in the database. Put it in a model method:
class Document(models.Model):
expiry_date = models.DateField()
def expired(self):
return self.expiry_date < timezone.now.date()

You can use a conditional annotation.
Tested with Django 1.11.10.
from django.db.models import BooleanField, Case, When
from django.utils import timezone
Document.objects.annotate(
expired=Case(
When(expiry_date__lt=timezone.now(), then=True),
default=False,
output_field=BooleanField()
)
).order_by('expired')

This works for Django >= 2, didn't check for previous versions
from django.db import models
from django.db.models import ExpressionWrapper, Q
from django.db.models.functions import Now
Document.objects.annotate(
expired=ExpressionWrapper(Q(expiry_date__lt=Now()),
output_field=models.BooleanField())
)
source:
https://stackoverflow.com/a/57114224/11193405

Related

check if today is due date django

I have a model like this
class Tasks(models.Model):
title = models.CharField(max_length=100,null=True,blank=True)
due_date_time= models.DateTimeField(default=timezone.now)
As due date is a date and time field, how I can check if today is due date of this task , while I am saving time and date both
You can make use of the __date lookup [Django-doc]:
from django.utils.timezone import now
Tasks.objects.filter(
due_date_time__date=now().date()
)
or if you work with timezones, you can work with a range check:
from datetime import timedelta
from django.utils.timezone import now
today = now().replace(hour=0, minute=0, second=0, microsecond=0)
tomorrow = today + timedelta(days=1)
Tasks.objects.filter(
due_date_time__gte=today,
due_date_time__lt=tomorrow
)
Note: normally a Django model is given a singular name, so Task instead of Tasks.

In Django, filter a query set for two datetimes being within one day

I have a Django model like:
from django.db import models
class Person(models.Model):
# ...
last_login = models.DateTimeField(null=True, blank=True)
date_created = models.DateTimeField(auto_now_add=True)
I want to find all Persons where last_login is within one day of date_created.
So far I have:
from datetime import timedelta
from django.db.models import F
Person.objects.annotate(
duration=F("last_login") - F("date_created")
).filter(duration__lte=timedelta(days=1))
But the tail end of the error when I do that is like:
File ".../lib/python3.7/site-packages/django/db/models/fields/__init__.py", line 1316, in to_python
parsed = parse_datetime(value)
File ".../lib/python3.7/site-packages/django/utils/dateparse.py", line 107, in parse_datetime
match = datetime_re.match(value)
TypeError: expected string or bytes-like object
I'm not sure what I'm doing wrong, probably with that timedelta(days=1).
Also (and I don't think this is the immediate error), I'm not sure if that query will take account of the fact that last_login could be None.
You can filter with:
from datetime import timedelta
from django.db.models import F
Person.objects.filter(
last_login__lte=F('date_created') + timedelta(days=1)
)
you can return records if last_login is None/NULL as well, with:
from datetime import timedelta
from django.db.models import F, Q
Person.objects.filter(
Q(last_login__lte=F('date_created') + timedelta(days=1)) |
Q(last_login=None)
)

How to change boolean field "isActual" by date

I'm new in Django Rest Framework. I have this model -
class Post(models.Model):
created = models.DateTimeField(auto_now_add=True)
description = models.CharField(verbose_name='description', db_index=True, max_length=64)
In this model, I want to add field "isActual", which value equal True or False.
His value must be False, if Post created more than 1 month (check field "created").
I don't know how to create it. Is it possible?
First, Create BooleanField() and
Check with current timezone like this Answer and try it.
Hope this helps!
You can annotate your queryset like:
from django.utils.timezone import
from dateutil.relativedelta import relativedelta
from django.db.models import BooleanField, Q, Expression
Post.objects.annotate(
is_actual=ExpressionWrapper(
Q(created__gte=now() - relativedelta(months=1)),
output_field=BooleanField()
)
)
The Post objects that arise from this, will carry an extra attribute is_actual that is True if the post is younger than one month, and False otherwise.
You need to install the python-dateutil package [PiPy] first in order to import the relativedelta [readthedocs.io], with:
pip install python-dateutil

Group by Month and Gender in Django queryset

I have the following queryset, It works well with the grouping by month.
from django.db.models.functions import TruncMonth
queryset = UserProfile.objects.filter(date_created__year='2018')\
.annotate(date=TruncMonth('date_created'))\
.values('date').annotate(total_entries=Count('id'))
What I want is to group also by gender, here is a similar model with the gender field
class UserProfile:
date_created = models.DateTime(auto_now_add=True)
gender = models.CharField(max_length=3,choices=[('F',"Female"),('M',"Male")],default='M')
Expecting result:
May: 5 users [4(Male), 1(Female)]
June: 20 users [15(Male), 5(Female)]
For django < 2.0 you can use Conditional Expressions and Sum() to annotate the values you want:
from django.db.models import Sum, Case, When
from django.db.models.functions import TruncMonth
queryset = UserProfile.objects.filter(date_created__year='2018').annotate(
date=TruncMonth('date_created'),
).values('date').annotate(
total_entries=Count('id'),
total_male=Sum(Case(When(gender='M', then=1), default=0, output_field=models.IntegerField())),
total_female=Sum(Case(When(gender='F', then=1), default=0, output_field=models.IntegerField())),
)
Since django 2.0 you can use Conditional Aggregation:
from django.db.models import Count, Q
from django.db.models.functions import TruncMonth
queryset = UserProfile.objects.filter(date_created__year='2018').annotate(
date=TruncMonth('date_created'),
).values('date').annotate(
total_entries=Count('id'),
total_male=Count('id', filter=Q(gender='M')),
total_female=Count('id', filter=Q(gender='F')),
)

Can we do a Sum on CharField in Django ORM?

My model in Django ORM is this
class Test(Modelbase):
id = models.IntegerField(null=True, blank=True)
amount = models.CharField(max_length=255)
I want to add the amount for list of id's. The only problem is the amount field is CharField. How do I apply sum for the amount field?
Test.objects.filter(id__in=[1,2,3]).aggregate(Sum('amount'))
I am using Django=1.9.1 for this.
you can try do annotate with cast:
from django.db.models import FloatField
from django.db.models.functions import Cast
Test.objects.filter(id__in=[1,2,3]
).annotate(as_float=Cast('amount', FloatField())
).aggregate(Sum('as_float'))
Note for django < 1.10, you should define Cast here the source Cast
Or
from django.db.models import Sum, Func, F
Test.objects.annotate(
num=Func(
F('amount'),
template='%(function)s(%(expressions)s AS %(type)s)',
function='Cast', type='float')
).aggregate(Sum('num'))