Reputation: 33
Given the following, how can I make a query which returns a list of the average rating per user in friendlist?
models.py
class Beer(models.Model):
id = models.BigIntegerField(primary_key=True)
name = models.CharField(max_length=150)
...
class Checkin(models.Model):
id = models.IntegerField(primary_key=True)
rating = models.FloatField(blank=True, null=True)
user = models.ForeignKey(User, on_delete=CASCADE)
...
class FriendList(models.Model):
user = models.OneToOneField(User, on_delete=CASCADE, primary_key=True)
friend = models.ManyToManyField(User, related_name="friends")
database (postgresql)
user | beer | rating |
---|---|---|
1 | 1 | 4.2 |
1 | 1 | 3.5 |
1 | 1 | 4.0 |
2 | 1 | 4.1 |
2 | 1 | 3.7 |
My current query to get all friends checkins:
Checkin.objects.filter(beer=1, user__in=friends.friend.all())
Which gives me something like:
[{user: 1, rating: 4.2}, {user: 1, rating: 3.5},...,{user: 2, rating: 4.1}...]
What I want is:
[{user: 1, avg_rating: 4.1}, {user: 2, avg_rating: 3.8}]
Upvotes: 1
Views: 314
Reputation: 476709
It makes more sense to .annotate(…)
[Django-doc] the User
objects, so:
from django.db.models import Avg
friends.friend.filter(
checkin__beer_id=1
).annotate(
rating=Avg('checkin__rating')
)
Where checkin__
is the related_query_name=…
[Django-doc] for the user
from Checkin
to the User
model. If you did not specify a related_query_name=…
, then it will use the value for the related_name=…
[Django-doc], and if that one is not specified either, it will use the name of the source model in lowercase, so checkin
.
The User
objects that arise from this queryset will have an extra attribute .rating
that contains the average rating
over the Checkin
s for that beer_id
.
You can determine the average of these averages with an .aggregate(…)
call [Django-doc]:
from django.db.models import Avg
friends.friend.filter(
checkin__beer_id=1
).annotate(
rating=Avg('checkin__rating')
).aggregate(
all_users_avg_rating=Avg('rating'),
number_of_users=Count('pk')
)
This will return a dictionary with two elements: all_users_avg_rating
will map to the average of these averages, and number_of_users
will return the number of distinct users.
Note: It is normally better to make use of the
settings.AUTH_USER_MODEL
[Django-doc] to refer to the user model, than to use theUser
model [Django-doc] directly. For more information you can see the referencing theUser
model section of the documentation.
Upvotes: 1