How to use Count over many fields in Django? How to count row only if given multiple columns are unique?
For example for a model below.
class ProductViewed(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,
blank=True, null=True, related_name="viewed")
ip_address = models.CharField(max_length=255, blank=True, null=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE,
related_name="views")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{str(self.product)} viewed on {self.created_at}'
class Meta:
ordering = ('-created_at', )
verbose_name_plural = "ProductViewed"
I want to achieve followings. Count based on user_id, ip_address, created_at__day. Is there any way to do so?
Currently I could achieve the following
Product.objects.annotate(vcount=Count('views__ip_address'))
One way to do this is to join all the fields that you want to perform the unique count on together using Concat and then counting on the result. The Trunc function is good for extracting part of a datetime
from django.db.models.functions import Trunc, Concat
Product.objects.annotate(
view_day=Trunc('views__created_at', 'day')
).annotate(
user_ip_and_day=Concat('views__user_id', 'views__ip_address', 'view_day', output_field=models.CharField())
).annotate(
vcount=models.Count('user_ip_and_day')
)
Can't you just do this?
Product.objects.annotate(vcount=Count('views__ip_address')).annotate(ucount=Count('views__user_id')).annotate(ccount=Count('views__created_at__day'))
Related
I have a business model as follows:
class Business(models.Model):
class Meta:
verbose_name_plural = "Businesses"
name = models.CharField(max_length=60, null=False, verbose_name="Title")
about = models.TextField(max_length=5000, null=True, verbose_name="Description", blank=True)
upvote = models.ManyToManyField(Account, verbose_name="Upvote Count", blank=True)
The Account model is as follows:
class Account(models.Model):
CHOICES = [('Male', 'Male'),Female', 'Female'),]
class Meta:
verbose_name_plural = "Accounts"
name = models.CharField(max_length=60, null=False, verbose_name="Title")
gender= models.CharField(max_length=6, null=True, verbose_name="Gender", choices=CHOICES)
I am trying to get a QuerySet that will be sorted by gender of the Account.
How do I achieve this?
So far I have achieved sorting by the upvote count.
Business.objects.all().order_by("upvote")
You can use the Sum function [Django docs] with a Conditional Expression [Django docs] to annotate a value according to which you would order:
from django.db.models import Case, Sum, Value, When
Business.objects.annotate(
order_value=Sum(
Case(
When(upvote__gender='Male', then=Value(1)),
When(upvote__gender='Female', then=Value(-1)),
default=Value(0)
)
)
).order_by('order_value')
The above query would give you Business objects with more upvotes by females first and males later, you can reverse the order by writing .order_by('-order_value') instead.
You can access fiels of related models by double underscore. See documentation here.
Try:
Business.objects.all().order_by("upvote__gender")
I want to get players with less than 5 records in the "MatchSup" table for specific matches
Player.objects.filter('age="25"',matchsup__match__in='matchlist').annotate(numviews=Count('matchsup__player__lt=5',)) like this (that's wrong i know)
class Player(models.Model):
name= models.CharField(max_length=64, null=True, blank=True)
age= ...
class MatchSup(models.Model):
player= models.ForeignKey(Player, null=True, blank=True, on_delete=models.SET_NULL)
match = models.ForeignKey('Match', on_delete=models.CASCADE)
annotate the count on the Player model and filter on the count:
from django.db.models import Count
Player.objects.filter(
age=25,
matchsup__match__in=matchlist
).annotate(
match_count=Count('matchsup')
).filter(match_count__lt=5)
Good afternoon,
I am really struggling with getting a sum using Annotate in DJango.
I am using User object and the following models:
class Depts(models.Model):
dept_name = models.CharField(max_length=55)
dept_description = models.CharField(max_length=255)
isBranch = models.BooleanField(default=False)
def __str__(self):
return "{}".format(self.dept_name)
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile')
title = models.CharField(max_length=75)
dept = models.ForeignKey(Depts, on_delete=models.CASCADE, related_name="dept", null=True)
class ActivityLog(models.Model):
activity_datetime = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='activity_user')
activity_category = models.ForeignKey(ActivityCategory, on_delete=models.CASCADE, null=True, related_name='activity_cat')
activity_description = models.CharField(max_length=100, default="Misc Activity")
class ActivityCategory(models.Model):
activity_name = models.CharField(max_length=40)
activity_description = models.CharField(max_length=150)
pts = models.IntegerField()
def __str__(self):
return '%s' % (self.activity_name)
What I need to do is get a group of departments with aggregating the sum of the pts earned by all the users activitylogs.
So a user is part of department, they do activities, each activity is of a type activity_category and has associated points. How can I query using the ORM to get a sum of points for everyone in each department?
Thank you, I cannot seem to wrap my mind around it.
You annotate the departments with the sum:
from django.db.models import Sum
Depts.objects.annotate(
total_pts=Sum('dept__user__activity_user__activity_category__pts')
)
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the Depts model to the UserProfile
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the dept relation to userprofiles.
After setting the related_name='userprofiles', the query is:
from django.db.models import Sum
Depts.objects.annotate(
total_pts=Sum('userprofiles__user__activity_user__activity_category__pts')
)
I have two models related to each other with a many to many. I want to filter for one, Message, based on a field on the other, User.created_at, compared to a field on the first, Message.edit_date.
class Message(Model):
content = CharField(max_length=512, blank=True, null=True)
created_at = models.DateTimeField(blank=True, null=True)
edit_date = models.DateTimeField(blank=True, null=True)
users = models.ManyToManyField('User', related_name='message_user')
class User(Model):
name = content = CharField(max_length=48)
created_at = models.DateTimeField(blank=True, null=True)
Right now I am achieving this by looping over the two models and comparing them in the loop, which is slow.
message_query = Message.objects.none()
for user_name, created_at_date in Users.objects.filter(name='Tina').values_list('id', 'created_at').iterator():
message_query.add(Q(
users=user_id,
edit_date__gte=created_at_date,
), Q.OR)
messages = Message.objects.filter(message_query)
Is there any way to create a filter for the items I'm attempting to filter for in a query?
You can filter on fields on the related model directly using F expressions. Something like this should work:
from django.db.models import F
# This will return all messages where one of the associated users
# was created_at before the message edit_date.
Message.objects.filter(
edit_date__gte=F('users__created_at')
).distinct()
Note that this will return duplicate results if more than one user matches this condition for any given message - hence the distinct() the end.
I want to merge these two QuerySets. HotkeyAndPrefix do not have entries for every Collection in all_collections. This means len(all_collections) >= len(all_collections_hotkeys_and_prefixes). How can i merge these two QuerySets? If there is no entrie found for a Collection in HotkeyAndPrefix I want hotkey = None, prefix=None. Can I achieve this in one query?
models.py:
class Collection(models.Model):
creator = models.ForeignKey(User, blank=True, null=True)
...
class HotkeyAndPrefix(models.Model):
user = models.ForeignKey(User, null=True)
assigned_collection = models.ForeignKey(Collection, null=True)
hotkey = models.CharField(max_length=1, blank=True, null=True)
prefix = models.CharField(max_length=20, blank=True, null=True)
class Meta:
unique_together = ('user', 'assigned_collection')
view.py
admin = User.objects.filter(username='admin')[0]
all_collections = Collection.objects.filter(creator=admin)
current_user = request.user
all_collections_hotkeys_and_prefixes = HotkeyAndPrefix.objects.filter(assigned_collection__in=all_collections, user=current_user)
You need to use exclude() query. You can take the list of values which are in the
HotkeyAndPrefix.objects.filter(assigned_collection__in=all_collections, user=current_user) queryset
using
all_collections_hotkeys_and_prefixes_values = all_collections_hotkeys_and_prefixes.values_list('assigned_collection',flat=True)
and you can filter out the value, not in the all_collections_hotkeys_and_prefixes_values with one more query
all_collections_hotkeys_and_prefixes_excluded = all_collections.exclude(pk__in=all_collections_hotkeys_and_prefixes_values)
now you have two querysets, one of collection for which a user has hotkey/prefix and another for which the user doesn't