Django Rest框架在SerializerMethodField上排序

时间:2015-05-04 23:50:19

标签: django django-rest-framework

我有一个论坛主题模型,我想在计算的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

这是有效的:

  1. OrderingFilter默认开启,我可以成功订购/topics?ordering=title
  2. vote_count功能完美无缺
  3. 我正在尝试通过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,但这不起作用(它不应该)

3 个答案:

答案 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