How to get average from set of objects in Django?

Question:

I have a simple rating system for a property. You give it a mark out of 5 (stars). The models are defined like this

def Property(models.Model)
    # stuff here

def Rating(models.Model)
    property = models.ForeignKey(Property)
    stars = models.IntegerField()

What I want to do is get a property, find all the Rating objects, collect them, then get the average ‘stars’ from them.

Any ideas how to do this?

Asked By: dotty

||

Answers:

You should use Aggregation(doc):

from django.db.models import Avg

p = Property.objects.get(...)
stars_average = p.rating_set.aggregate(Avg('stars')).values()[0]

A little bit unsure about my example though.

Answered By: user257858

You can use aggregate() and annotate() with Avg() to average the stars in Rating model and property by property as shown below. *I need to use order_by('pk') with annotate() otherwise values are printed in descending order:

from django.db.models import Avg

# Average the stars in "Rating" model
print(Rating.objects.aggregate(Avg('stars'))) 

print()

# Average the stars in "Rating" model property by property
for obj in Property.objects.all(): 
    print(
        Rating.objects.filter(property=obj)
                      .aggregate(Avg('stars'))
    )

print()

# Average the stars in "Rating" model property by property
for obj in Property.objects.all(): 
    print(obj.rating_set.aggregate(Avg('stars')))

print()

# Average the stars in "Rating" model property by property
qs = Property.objects.annotate(Avg('rating__stars')).order_by('pk')
for obj in qs:
    print(obj.rating__stars__avg)

Then, these below are outputted on console:

{'stars__avg': 3.7}

{'stars__avg': 3.2}
{'stars__avg': 3.6}
{'stars__avg': 4.0}

{'stars__avg': 3.2}
{'stars__avg': 3.6}
{'stars__avg': 4.0}

3.2
3.6
4.0

And, you can change the default key stars__avg for stars column to starsAvg as shown below:

from django.db.models import Avg

# Average the stars in "Rating" model
print(Rating.objects.aggregate(starsAvg=Avg('stars'))) 
                               # ↑ Here
print()

# Average the stars in "Rating" model property by property
for obj in Property.objects.all(): 
    print(
        Rating.objects.filter(property=obj)
                      .aggregate(starsAvg=Avg('stars'))
    )                            # ↑ Here

print()

# Average the stars in "Rating" model property by property
for obj in Property.objects.all(): 
    print(obj.rating_set.aggregate(starsAvg=Avg('stars')))
                                   # ↑ Here
print()

# Average the stars in "Rating" model property by property
qs = Property.objects.annotate(starsAvg=Avg('rating__stars')).order_by('pk')
for obj in qs:                 # ↑ Here
    print(obj.starsAvg)
              # ↑ Here

Then, the default key is changed as shown below:

{'starsAvg': 3.7}

{'starsAvg': 3.2}
{'starsAvg': 3.6}
{'starsAvg': 4.0}

{'starsAvg': 3.2}
{'starsAvg': 3.6}
{'starsAvg': 4.0}

3.2
3.6
4.0
Answered By: Kai – Kazuya Ito