Джанго - Как обновить поле для модели при создании другой модели? - PullRequest
0 голосов
/ 24 ноября 2018

У меня есть две модели, модель продукта и модель рейтинга.Я хочу, чтобы каждый раз, когда создается «Оценка» через API POST для конечной точки, созданной с помощью DRF, я хотел вычислить и обновить поле average_rating в связанном Продукте.

class Product(models.Model):
    name = models.CharField(max_length=100)
    ...
    average_rating = models.DecimalField(max_digits=3, decimal_places=2)

    def __str__(self):
        return self.name

class Rating(models.Model):
    rating = models.IntegerField()
    product = models.ForeignKey('Product', related_name='ratings')

    def __str__(self):
        return "{}".format(self.rating)

Каков наилучший способ сделать это?Я использую сигнал post_save (Post create?)?

1 Ответ

0 голосов
/ 24 ноября 2018

Каков наилучший способ сделать это?Я использую сигнал 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(..)), и, таким образом, все же будет хорошей идеей периодически синхронизировать средние оценки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...