Django-如何在另一个模型的创建中更新模型的字段?

时间:2018-11-24 16:40:30

标签: python django django-models django-rest-framework

我有两个模型,一个产品模型和一个评级模型。我想要完成的是,每次创建“评级”时,通过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(创建后?)信号?

1 个答案:

答案 0 :(得分:1)

  

做到这一点的最佳方法是什么?我是否使用post_save(创建后发布?)信号?

问题不在于我想如何从技术上做到这一点,而更多的是如何使这种健壮。毕竟,创建重要的评分不仅是重要的:如果人们更改了评分或删除了评分,那么平均评分也需要更新。甚至有可能使用级联定义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的属性为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以强制进行重新计算,例如使用{{1} }等。但是请注意,可以绕过信号(例如,使用查询集的post_save或使用.update(..)),因此定期同步平均评分仍然是一个好主意。