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?
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'