使用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
相关产品推荐
相关产品推荐

