Каков наилучший способ сделать это?Я использую сигнал post_save (Post create?)?
Проблема не в том, как сделать это технически, я думаю, а в том, как сделать это надежным .В конце концов, важно не только создавать новые рейтинги: если люди меняют свой рейтинг или удаляют рейтинг, то средний рейтинг также должен обновляться.Возможно даже, что если вы определите ForeignKey
с помощью каскада, то удаление чего-либо, связанного с Rating
, может привести к удалению нескольких рейтингов и, следовательно, обновлению нескольких Product
с.Таким образом, получить среднее значение в синхронизации может быть довольно сложно.Особенно, если вы позволите другим программам манипулировать базой данных.
Поэтому может быть лучше рассчитать средний рейтинг.Например, с агрегатом:
from django.db.models import Avg
class Product(models.Model):
name = models.CharField(max_length=100)
@property
def average_rating(self):
return self.ratings.aggregate(average_rating=Avg('rating'))['average_rating']
def __str__(self):
return self.name
Или, если вы хотите загрузить несколько Product
с в QuerySet
, вы можете сделать .annotate(..)
, чтобы рассчитать средний рейтинг навалом:
Product.objects.annotate(
average_rating=Avg('rating__rating')
)
Здесь Product
s будет иметь атрибут average_rating
, который является средней оценкой связанных оценок.
В случае, если количество оценок может быть огромнымЭто может занять значительное время для расчета среднего.В этом случае я предлагаю добавить поле и использовать периодическое задание для обновления рейтинга.Например:
from django.db.models import Avg, OuterRef, Subquery
class Product(models.Model):
name = models.CharField(max_length=100)
avg_rating=models.DecimalField(
max_digits=3,
decimal_places=2,
null=True,
default=None
)
@property
def average_rating(self):
return self.avg_rating or self.ratings.aggregate(average_rating=Avg('rating'))['average_rating']
@classmethod
def update_averages(cls):
subq = cls.objects.filter(
id=OuterRef('id')
).annotate(
avg=Avg('rating__rating')
).values('avg')[:1]
cls.objects.update(
avg_rating=Subquery(subq)
)
def __str__(self):
return self.name
Затем вы можете периодически звонить Product.update_averages()
, чтобы обновить средние оценки всех продуктов.Если вы создаете, обновляете или удаляете рейтинг, вы можете установить для поля avg_rating
соответствующего товара (ов) значение None
, чтобы принудительно пересчитать, например, с помощью post_save
и т. Д. Но обратите вниманиечто сигналы могут быть обойдены (например, с помощью .update(..)
набора запросов или bulk_create(..)
), и, таким образом, все же будет хорошей идеей периодически синхронизировать средние оценки.