Group by Month and Gender in Django queryset - django

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')),
)

Related

Filtering Posts greater than certain number of likes

I have a question about filtering Posts by Like count greater than a given number.
What I want to achieve is on DJANGO SHELL when I type post.likes is greater than 10 I want to see all posts with more than 10 likes.
How can I do that?
Thanks
models.py
class Post(models.Model,HitCountMixin):
likes = models.ManyToManyField(User, related_name="likes", blank=True)
You can work with a .annotate() [Django-doc] and then .filter(…) [Django-doc]:
from django.db.models import Count
Post.objects.annotate(
nlikes=Count('likes')
).filter(
nlikes__gt=10
)
as of django-3.2, we can replace the .annotate(…) with .alias() [Django-doc]:
from django.db.models import Count
Post.objects.alias(
nlikes=Count('likes')
).filter(
nlikes__gt=10
)

Django / PostGreSQL: Create queryset grouped by 'date' when each row has a different timezone

Let's say I have two models:
from django.db import model
class Company(model.Model):
name = models.TextField()
timezone = models.TextField()
class Sale(models.Model):
amount = models.IntegerField()
company = models.ForeignKey('Company')
time = models.DateTimeField()
I want to create a queryset grouped by date and company, where date refers to the calendar date of the sale at the timezone specified on the Company object.
This query:
result = Sale.objects.values(
'company', 'time__date'
).aggregate(
models.Sum('amount')
)
This returns the data in a format that works for me. However, the sales are grouped by UTC day. I want them grouped by the timezone on the Company objects.
What is the cleanest, quickest way to do this?
I know I could dump the entire set of values into Python, like this:
result = Sale.objects.values(
'amount', 'company__timezone', 'time'
).order_by(
'company_timezone'
)
for r in result:
r.date = r.time.astimezone(pytz.timezone(r.company_timezone)).date()
and then groupby, but is there a better way?
The solution is to use the TruncDate function, and pass the timezone string as an argument.
from django.db.models.functions import TruncDate
from django.db.models import F
...
local_time_daily_sales = Sale.objects.annotate(
date=TruncDate(tzinfo=F('company__timezone'))
).values(
date
).annotate(Sum('amount'))

Merge Date Time in Django ORM

I was trying to combine date and time. For that i was using datetime.combine. But it is giving AttributeError module 'datetime' has no attribute 'combine'
TimesheetEntry.objects.filter(
timesheet_users = user_id
).order_by(
'-id'
).annotate(
timesheet_clock_in=datetime.combine('timesheet_clock_in_date', 'timesheet_clock_in_time')
).annotate(
timesheet_clock_out=datetime.combine('timesheet_clock_out_date', 'timesheet_clock_out_time')
).values_list(
'timesheet_clock_in',
'timesheet_clock_out',
'timesheet_jobs',
'timesheet_note',
'timesheet_clock_in_by'
)
I know the error is in annotate but i don't how to solve it. Query works without annotate
In addition to this answer, use F() expressionas
from django.db.models import F
from datetime import datetime
TimesheetEntry.objects.filter(
timesheet_users=user_id
).order_by(
'-id'
).annotate(
timesheet_clock_in=datetime.combine(F('timesheet_clock_in_date'), F('timesheet_clock_in_time'))
).annotate(
timesheet_clock_out=datetime.combine(F('timesheet_clock_out_date'), F('timesheet_clock_out_time'))
).values_list(
'timesheet_clock_in',
'timesheet_clock_out',
'timesheet_jobs',
'timesheet_note',
'timesheet_clock_in_by'
)
Did you import datetime correctly?
import datetime
# The datetime module
or
from datetime import datetime
# The datetime class in the datetime module
In the first case you should call datetime.datetime.combine (with 2x datetime). In the second case you can call datetime.combine directly.
date.combine() does not work because it raises argument 1 must be datetime.date, not F
Here is how we approached this issue:
from django.db.models import Value
from django.db.models.functions import Cast, Concat
TimesheetEntry.objects.filter(
timesheet_users=user_id
).order_by(
'-id'
).annotate(
timesheet_clock_in=Cast(
Concat('timesheet_clock_in_date', Value(" "), 'timesheet_clock_in_time', output_field=DateTimeField()),
output_field=DateTimeField()
)
timesheet_clock_out=Cast(
Concat('timesheet_clock_out_date', Value(" "), 'timesheet_clock_out_time', output_field=DateTimeField()),
output_field=DateTimeField()
)
).values_list(
'timesheet_clock_in',
'timesheet_clock_out',
'timesheet_jobs',
'timesheet_note',
'timesheet_clock_in_by'
)
You can do it in the database instead of in the ORM:
TimesheetEntry.objects.filter(
timesheet_users = user_id
).order_by(
'-id'
).annotate(
timesheet_clock_in=ExpressionWrapper(F("timesheet_clock_in_date") + F("timesheet_clock_in_time"), output_field=DateTimeField())
).annotate(
timesheet_clock_out=ExpressionWrapper(F("timesheet_clock_out_date") + F("timesheet_clock_out_time"), output_field=DateTimeField())
).values_list(
'timesheet_clock_in',
'timesheet_clock_out',
'timesheet_jobs',
'timesheet_note',
'timesheet_clock_in_by'
)
+ is the Postgres operator for date + time → timestamp so make sure you're using the correct operator for your database.
Also as a note to anyone doing this: Be careful of timezone issues when combining date and time into timestamps.

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'))

Django, query_set annotation with conditional expression

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