Search code examples
djangodatabaseoptimization

Optimized way to handle user graded items?


I'm working on website that allows users to grade items, e.g. books. The grade should be displayed on the book page, but also on the collective page containing all books. The grades should be possible to modify:

class BookUserGrade:
    user = foreign_key_to_user
    book = foreign_key_to_book
    grade = ...

However the operation of requesting the grades each time a user access the booklist (or to lesser degree single book page) seems expensive.

I'm pondering about making an additional two fields on the book itself, number_of_grades and cumulative_grade which are going to be updated only on user grading a book. Maybe also calculated_grade (is it worthy to add this field instead of calculating cumulative_grade / number_of_grades on the fly?

Or perhaps there's another proper way to do so?


Solution

  • It is generally bad practice to store computed fields in the database. It leads to a lot of overhead having to make sure things stay synced.

    There are a couple different ways to tackle this, if you are worried about how expensive it is then the most computationally efficient way to achieve this is to offload the work to the database by writing a custom manager that annotates aggregations of the grade for each book e.g.

    class GradedManager(models.Manager):
        def get_queryset(self):
            return super().get_queryset().annotate(
                cumulative_grade=models.Sum('bookusergrade__grade'),
                grade_count=models.Count('bookusergrade')
            )
    
    
    class Book(models.Model):
        name = models.CharField(max_length=100)
    
        objects = models.Manager()
        graded = GradedManager()
    
    
    class BookUserGrade(models.Model):
        user = models.CharField(max_length=100)
        book = models.ForeignKey(Book, on_delete=models.CASCADE)
        grade = models.IntegerField()
    

    Then when you query this manager like using Book.graded.all() each book in the queryset will have two extra fields 'cumulative_grade' and 'grade_count'