Django中使用average_rating属性排序文章报错的解决方法咨询
问题描述
尝试按文章的平均评分对Article模型实例排序时触发FieldError,错误提示无法将average_rating解析为字段——因为该值是模型的@property属性,属于Python层面的计算值,无法被Django ORM直接用于数据库层面的排序。
相关代码
Models代码
class Article(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articles') caption = models.CharField(max_length=250) @property def average_rating(self): return self.articlecomments.all().aggregate(Avg('rate')).get('rate__avg', 0.00) class ArticleComment(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) article = models.ForeignKey(Article, on_delete=models.CASCADE, null=True, blank=True, related_name='articleratings') author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='articleratings_author') rate = models.IntegerField(default='0')
ViewSet代码
class ArticleByCategoryViewSet(viewsets.ModelViewSet): permission_classes = (IsAuthenticated,) queryset = Article.objects.all().order_by('-average_rating') serializer_class = ArticleSerializer pagination_class = StandardResultsSetPagination
错误信息
raise FieldError("Cannot resolve keyword '%s' into field. " django.core.exceptions.FieldError: Cannot resolve keyword 'average_rating' into field. Choices are:......
解决方案
Django ORM无法直接使用模型的@property属性进行排序,可通过以下几种方式解决:
方法1:ORM聚合查询实现数据库层面排序
直接在查询时通过ORM聚合计算平均评分,将其作为临时字段参与排序,同时该字段可直接用于后续序列化:
from django.db.models import Avg class ArticleByCategoryViewSet(viewsets.ModelViewSet): permission_classes = (IsAuthenticated,) queryset = Article.objects.annotate( average_rating=Avg('articleratings__rate') ).order_by('-average_rating') serializer_class = ArticleSerializer pagination_class = StandardResultsSetPagination
注意:原代码存在关联名称不匹配问题——
ArticleComment的related_name是articleratings,但Article的average_rating属性中调用的是self.articlecomments.all(),需统一两者名称:要么把ArticleComment的related_name改为articlecomments,要么把模型属性中的调用改为self.articleratings.all(),否则聚合查询会报错。
若想保留@property属性,可修改为优先使用annotate生成的字段:
@property def average_rating(self): if hasattr(self, 'average_rating'): return self.average_rating or 0.00 return self.articleratings.all().aggregate(Avg('rate')).get('rate__avg', 0.00)
方法2:预计算存储评分(适合高并发/频繁排序场景)
若文章评分更新频率低,可在Article模型中添加存储平均评分的字段,通过信号自动更新该字段:
1. 修改模型
class Article(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articles') caption = models.CharField(max_length=250) average_rating = models.FloatField(default=0.00) # 新增存储评分的字段 class ArticleComment(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) article = models.ForeignKey(Article, on_delete=models.CASCADE, null=True, blank=True, related_name='articleratings') author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='articleratings_author') rate = models.IntegerField(default=0) # 修正default为整数0,而非字符串'0'
2. 编写信号更新评分
from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.db.models import Avg @receiver([post_save, post_delete], sender=ArticleComment) def update_article_rating(sender, instance, **kwargs): if instance.article: avg_rating = instance.article.articleratings.aggregate(Avg('rate')).get('rate__avg', 0.00) instance.article.average_rating = avg_rating or 0.00 instance.article.save()
3. 修改ViewSet排序
class ArticleByCategoryViewSet(viewsets.ModelViewSet): permission_classes = (IsAuthenticated,) queryset = Article.objects.all().order_by('-average_rating') serializer_class = ArticleSerializer pagination_class = StandardResultsSetPagination
此方式排序性能更高,但需维护额外字段与信号逻辑。
方法3:内存层面排序(仅适合小数据量)
若数据量极小,可先查询所有文章,再在Python内存中排序:
class ArticleByCategoryViewSet(viewsets.ModelViewSet): permission_classes = (IsAuthenticated,) serializer_class = ArticleSerializer pagination_class = StandardResultsSetPagination def get_queryset(self): articles = list(Article.objects.all()) return sorted(articles, key=lambda x: x.average_rating, reverse=True)
注意:该方式会将所有文章加载到内存,数据量大时性能极差,且原生分页逻辑失效,需自行处理分页。
内容的提问来源于stack exchange,提问作者user14491987
相关产品推荐
相关产品推荐

