我有一个论坛主题模型,我想在计算的SerializerMethodField上订购,例如vote_count。以下是一个非常简化的Model,Serializer和ViewSet来显示问题:
# models.py
class Topic(models.Model):
"""
An individual discussion post in the forum
"""
title = models.CharField(max_length=60)
def vote_count(self):
"""
count the votes for the object
"""
return TopicVote.objects.filter(topic=self).count()
# serializers.py
class TopicSerializer(serializers.ModelSerializer):
vote_count = serializers.SerializerMethodField()
def get_vote_count(self, obj):
return obj.vote_count()
class Meta:
model = Topic
# views.py
class TopicViewSet(TopicMixin, viewsets.ModelViewSet):
queryset = Topic.objects.all()
serializer_class = TopicSerializer
这是有效的:
/topics?ordering=title
我正在尝试通过TopicSerializer上的MethodField订购,vote_count如/topics?ordering=-vote_count
,但似乎不支持。我可以通过那个领域订购任何方式吗?
我的简化JSON响应如下所示:
{
"id": 1,
"title": "first post",
"voteCount": 1
},
{
"id": 2,
"title": "second post",
"voteCount": 8
},
{
"id": 3,
"title": "third post",
"voteCount": 4
}
我正在使用Ember来使用我的API并且解析器将它转换为camelCase。我也尝试过订购= voteCount,但这不起作用(它不应该)
答案 0 :(得分:26)
使用the default OrderingFilter
无法做到这一点,因为订购是在数据库端实现的。这是出于效率原因,因为手动排序结果可能令人难以置信地慢,意味着打破标准QuerySet
。通过将所有内容保持为QuerySet
,您可以从Django REST框架(通常需要QuerySet
)和内置分页(没有一个可能很慢)中提供的内置过滤中受益。
现在,在这些情况下,您有两种选择:弄清楚如何在数据库端检索您的值,或尝试最小化您将不得不采取的性能影响。由于后一种选择是特定于实现的,我现在暂时跳过它。
在这种情况下,您可以使用Django提供的the Count
function在数据库端进行计数。这是the aggregation API的一部分,与the SQL COUNT
function类似。您可以通过修改视图上的Count
queryset
来电
queryset = Topic.objects.annotate(vote_count=Count('topicvote_set'))
用your related_name
for the field替换topicvote_set
(你有一套,对吧?)。这将允许您根据投票数量排序结果,甚至可以进行过滤(如果您愿意),因为它在查询本身中可用。
这需要对序列化程序稍作更改,因此它会从对象上可用的新vote_count
属性中提取。
class TopicSerializer(serializers.ModelSerializer):
vote_count = serializers.IntegerField(read_only=True)
class Meta:
model = Topic
这会覆盖现有的vote_count
方法,因此您可能希望重命名注释时使用的变量(如果无法替换旧方法)。
此外,您可以将方法名称作为Django REST框架字段的source
传递,它将自动调用它。所以从技术上讲,你当前的序列化器可能只是
class TopicSerializer(serializers.ModelSerializer):
vote_count = serializers.IntegerField(read_only=True)
class Meta:
model = Topic
它会像现在一样完全。请注意,在这种情况下需要read_only
,因为方法与属性不同,因此无法设置该值。
答案 1 :(得分:1)
感谢@Kevin Brown 的精彩解释和回答!
在我的例子中,我需要对一个名为 total_donation
的 serializerMethodField 进行排序,它是 UserPayments 表中捐赠的 sum
。
UserPayments
有:
User
作为外键sum
是一个 IntegerField
related_name='payments'
我需要获得每个用户的总捐款,但只有状态为“已捐款”而非“待处理”的捐款。还需要过滤掉通过另外两个外键关联的 payment_type coupon
。
我对如何加入和过滤这些捐赠然后能够通过 ordering_fields
对其进行排序感到震惊。
多亏了你的帖子,我明白了!
我意识到它需要成为原始 queryset
的一部分才能使用 ordering
进行排序。
我需要做的就是在我的视图中注释查询集,使用带有过滤器的 Sum()
,如下所示:
class DashboardUserListView(generics.ListAPIView):
donation_filter = Q(payments__status='donated') & ~Q(payments__payment_type__payment_type='coupon')
queryset = User.objects.annotate(total_donated=Sum('payments__sum', filter=donation_filter ))
serializer_class = DashboardUserListSerializer
pagination_class = DashboardUsersPagination
filter_backends = [filters.OrderingFilter]
ordering_fields = ['created', 'last_login', 'total_donated' ]
ordering = ['-created',]
答案 2 :(得分:0)
我会把它放在这里,因为所描述的案例不是唯一的案例。 我们的想法是重写您的Viewset的list方法,以便您的任何 SerializerMethodField 也可以在不将您的逻辑从Serializer移动到ModelManager的情况下进行排序(特别是当您使用多种复杂方法和/时)或相关模型)
def list(self, request, *args, **kwargs):
response = super(YourModelList, self).list(request, args, kwargs)
ordering = request.query_params.get('ordering')
response.data['results'] = sorted(response.data['results'], key=operator.itemgetter(ordering.replace('-',''),))
if "-" in ordering:
response.data['results'] = sorted(response.data['results'], key=lambda k: (k[ordering.replace('-','')], ), reverse=True)
else:
response.data['results'] = sorted(response.data['results'], key=lambda k: (k[ordering], ))
return response