You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用django-star-ratings计算Series模型的平均评分问题

计算Django Series模型的平均评分(基于关联Issue的star-ratings评分)

嘿,我刚好处理过类似的需求,给你几个实用的方案来实现这个功能,基于你现有的模型和django-star-ratings插件:

方案1:在Series模型中添加动态计算的属性(简单直接)

这种方法不需要额外的数据库字段,每次访问时动态计算平均评分,适合访问量不大的场景:

from django.db import models
from django.db.models import Avg

class Series(models.Model):
    name = models.CharField(max_length=140)
    slug = models.SlugField(max_length=140, unique=True)

    @property
    def average_rating(self):
        # 导入Rating模型(放在方法内避免循环导入)
        from star_ratings.models import Rating
        # 获取当前系列下所有Issue对应的评分记录
        issue_ratings = Rating.objects.filter(
            # 关联到当前Series的所有Issue的ID
            object_id__in=self.issue_set.values_list('id', flat=True),
            # 确保是Issue模型的评分(content_type的model字段是小写)
            content_type__model='issue'
        )
        if issue_ratings.exists():
            # 计算所有Issue评分的平均值,保留两位小数
            return round(issue_ratings.aggregate(Avg('average'))['average__avg'], 2)
        # 如果没有任何评分,返回0.0
        return 0.0

使用的时候直接在模板或视图中调用{{ series.average_rating }}即可。

方案2:查询时用annotate批量计算(高效列表查询)

如果需要在列表页展示所有Series的平均评分,用annotate+Subquery的方式批量计算,比逐个访问属性更高效:

from django.db.models import Avg, OuterRef, Subquery
from star_ratings.models import Rating
from .models import Series

# 查询所有Series并附带平均评分
series_with_ratings = Series.objects.annotate(
    average_rating=Avg(
        Subquery(
            Rating.objects.filter(
                # 关联到当前Series下的每个Issue
                object_id=OuterRef('issue__id'),
                content_type__model='issue'
            ).values('average')  # 提取每个Issue的评分值
        )
    )
)

# 之后可以直接访问每个series的average_rating字段
for series in series_with_ratings:
    print(f"{series.name}: {series.average_rating or 0.0}")

方案3:用信号缓存平均评分(高性能场景)

如果你的站点访问量较大,频繁计算平均评分会影响性能,建议在Series模型中添加一个字段存储缓存的平均评分,然后通过Django信号自动更新:

第一步:修改Series模型

class Series(models.Model):
    name = models.CharField(max_length=140)
    slug = models.SlugField(max_length=140, unique=True)
    # 添加存储平均评分的字段,设置为不可编辑(由信号自动更新)
    average_rating = models.FloatField(default=0.0, editable=False)

第二步:编写信号处理函数

from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from star_ratings.models import Rating
from .models import Series

@receiver([post_save, post_delete], sender=Rating)
def update_series_rating(sender, instance, **kwargs):
    # 只处理Issue模型的评分更新
    if instance.content_type.model != 'issue':
        return
    
    # 获取评分对应的Issue和其所属Series
    issue = instance.content_object
    series = issue.series
    
    # 重新计算该Series的平均评分
    all_issue_ratings = Rating.objects.filter(
        object_id__in=series.issue_set.values_list('id', flat=True),
        content_type__model='issue'
    )
    
    if all_issue_ratings.exists():
        avg_rating = all_issue_ratings.aggregate(Avg('average'))['average__avg']
        series.average_rating = round(avg_rating, 2)
    else:
        series.average_rating = 0.0
    
    # 保存更新后的Series
    series.save()

这种方法的好处是:只有当Issue的评分发生变化(新增、修改、删除)时,才会重新计算并更新Series的平均评分,平时访问直接读取缓存字段,性能最优。

注意事项

  • 确保content_type__model='issue'中的issue是你Issue模型的小写名称,Django的ContentType模型存储的模型名称都是小写的。
  • 如果你的Issue模型有自定义的app标签,可能需要加上content_type__app_label='your_app_name'来精确匹配。

内容的提问来源于stack exchange,提问作者Brian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:06:43