【问题标题】:Django - How to update a field for a model on another model's creation?Django - 如何在创建另一个模型时更新模型的字段?
【发布时间】:2018-11-24 16:40:30
【问题描述】:

我有两个模型,一个产品模型和一个评级模型。我想要完成的是,每次创建“评级”时,通过 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_save,虽然评分数量不是很大,你也可以去掉average_rating字段,在查询Products的时候计算这个(所以@ 987654327@Products)。这更安全,因为例如,如果评级被删除或更新,那么平均值也需要重新计算。

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


【解决方案1】:

最好的方法是什么?我是否使用 post_save(创建后?)信号?

我认为问题不在于如何在技术上做到这一点,而在于如何使这个健壮。毕竟,重要的不仅仅是创建新的评分:如果人们更改评分或删除评分,那么平均评分也需要更新。甚至有可能,如果您使用级联定义 ForeignKey,则删除与 Rating 相关的内容可能会导致删除多个评级,从而更新多个 Products。因此,使平均值保持同步可能变得非常困难。特别是如果您允许其他程序操作数据库。

因此,计算平均评分可能会更好。以聚合为例:

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

或者如果你想在一个QuerySet中加载多个Products,你可以做一个.annotate(..)来批量计算平均评分:

Product.objects.annotate(
    average_rating=Avg('rating__rating')
)

这里Products 将有一个属性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(..)),因此定期同步平均评分仍然是一个好主意。

【讨论】:

  • 您好,Willem,感谢您详细而彻底的回复!我已将聚合方法添加到我的模型 (@property),但是当我在管理面板上查看我的 /api/products 时没有看到 average_rating 字段?如何使该字段在管理面板中可见并让我的 API 在 GET 上将 average_rating 返回到 /products ?
  • @noobprogrammer:您需要在序列化程序中添加一个额外的字段:stackoverflow.com/a/36697562/67579
猜你喜欢
  • 2017-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-10
  • 2014-12-25
  • 1970-01-01
  • 1970-01-01
  • 2023-02-11
相关资源
最近更新 更多