【问题标题】:Django Rest Framework Ordering on a SerializerMethodFieldSerializerMethodField 上的 Django Rest 框架排序
【发布时间】:2015-07-14 12:52:23
【问题描述】:

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

我正在尝试通过 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。我也尝试过 ordering=voteCount,但这不起作用(也不应该)

【问题讨论】:

    标签: django django-rest-framework


    【解决方案1】:

    感谢@Kevin Brown您的精彩解释和回答!

    在我的例子中,我需要对名为 total_donation 的 serializerMethodField 进行排序,它是来自 UserPayments 表的捐款的 sum

    UserPayments 有:

    • User 作为外键
    • sum 这是一个IntegerField
    • related_name='payments'

    我需要获取每个用户的捐赠总额,但只获取状态为“已捐赠”而非“待处理”的捐赠。还需要过滤掉payment_typecoupon,通过另外两个外键关联。

    我惊呆了如何加入和过滤这些捐赠,然后能够通过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】:

      我会把它放在这里,因为所描述的案例并不是唯一的。 这个想法是重写 Viewset 的 list 方法以按您的任何 SerializerMethodField(s) 进行排序,而无需将您的逻辑从 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
      

      【讨论】:

        【解决方案3】:

        这在使用the default OrderingFilter 时是不可能的,因为排序是在数据库端实现的。这是出于效率原因,因为手动对结果进行排序可能难以置信缓慢,并且意味着打破标准QuerySet。通过将所有内容保留为 QuerySet,您将从 Django REST 框架提供的内置过滤(通常需要 QuerySet)和内置分页(如果没有它可能会很慢)中受益。

        现在,在这些情况下,您有两种选择:弄清楚如何在数据库端检索您的值,或者尽量减少您将不得不承受的性能损失。由于后一个选项是非常特定于实现的,所以我现在将跳过它。

        在这种情况下,您可以使用 Django 提供的the Count function 在数据库端进行计数。这是作为the aggregation API 的一部分提供的,其工作方式类似于the SQL COUNT function。您可以通过将视图上的 queryset 修改为来执行等效的 Count 调用

        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,因为方法和属性不一样,所以不能设置值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-19
          • 2016-07-21
          • 1970-01-01
          • 2014-12-31
          • 2014-09-19
          • 2019-05-12
          相关资源
          最近更新 更多