'Django: how to count the ratings and reviews of a post using django?

i have a model course and i also have a model review and rating that allows users rate a particular course. now i want to count all the ratings on each course when i filter. NOTE: in the courses detail view i figured out a way to count the ratings like this rating_count = CourseRating.objects.filter(course=course, rating__in=["3.0", "4.0", "5.0"]).count(). This only worked in the detail view because i get the course first using course = Course.objects.get(slug=course_slug).

Now i want to count the rating in course lists view, how can i do this while using filter?

this is how the detail view looks like

@login_required
def course_details(request, course_slug):
    user = request.user
    course = Course.objects.get(slug=course_slug)
    reviews_count = CourseRating.objects.filter(active=True, course=course).count()
    rating_count = CourseRating.objects.filter(course=course, rating__in=["3.0", "4.0", "5.0"]).count()
    

this is how the list view look like NOTE: this is where i want to count all the rating of each course

def index(request):
    courses = Course.objects.filter(course_publish_status="published").order_by('?')

models.py

class Course(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    course_title = models.CharField(max_length=100, null=True, blank=True)
    slug = models.SlugField(unique=True)
    course_category = models.ForeignKey(Category, on_delete=models.DO_NOTHING, null=True, blank=True, related_name="courses")
    course_publish_status = models.CharField(max_length=10000, choices=COURSE_PUBLISH_STATUS, default="in_review")
    


class CourseRating(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True, related_name="ratings")
    rating = models.CharField(max_length=1000, choices=USER_COURSE_RATING)
    review = models.TextField()
    date = models.DateTimeField(auto_now_add=True)
    active = models.BooleanField(default=False)
    
    def __str__(self):
        return f"{self.course.course_title} - {self.rating}"
    
    class Meta:
        verbose_name = "Course Reviews and Ratings"


Solution 1:[1]

What you're looking for is Aggregation the docs have some pretty good (and quick) examples of how to achieve it. You're looking to use an .annotate with the Count object. Judging by your filtering for ratings, you'll also want to use the filter= parameter of the Count object. I also highly suggest you look into how to properly use the Q() object.

With all that being said, an example to achieve what you're looking for:

courses = Course.objects.filter(
    course_publish_status="published",
).annotate(
    rating_count=Count(
        'ratings', 
        filter=Q(ratings__rating__in=["3.0", "4.0", "5.0"])
    )
).order_by("?")

Keep in mind that I considered you have a related_name="ratings" on your ForeignKey. For your next questions I strongly suggest sharing the models you're working with as well (or at least the relevant portions of them).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 henriquesalvaro