Django Rest Framework 在 SerializerMethodField 上排序
Django Rest Framework Ordering on a SerializerMethodField
我有一个论坛主题模型,我想在计算的 SerializerMethodField 上排序,例如 vote_count。这里有一个非常简化的模型、序列化器和视图集来显示问题:
# 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
这是有效的方法:
- OrderingFilter默认开启,我可以成功下单
/topics?ordering=title
- 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 并且解析器正在将其转换为驼峰式。我也尝试过 ordering=voteCount,但这不起作用(也不应该)
这不可能使用 the default OrderingFilter
,因为排序是在数据库端 实现的。这是出于效率原因,因为手动对结果进行排序可能 难以置信地 缓慢并且意味着打破标准 QuerySet
。通过将所有内容都保留为 QuerySet
,您可以受益于 Django REST 框架(通常需要 QuerySet
)提供的内置过滤和内置分页(没有分页可能会很慢)。
现在,在这些情况下您有两个选择:弄清楚如何在数据库端检索您的值,或者尽量减少您将不得不承受的性能损失。由于后一个选项是非常特定于实现的,所以我现在将跳过它。
在这种情况下,您可以使用the Count
function provided by Django to do the count on the database side. This is provided as part of the aggregation API and works like 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
,因为方法和属性不一样,所以不能设置值。
我会把它放在这里,因为描述的案例不是唯一的。
这个想法是重写你的 Viewset 的列表方法,以通过你的任何 SerializerMethodField(s) 进行排序,而不会将你的逻辑从 Serializer 移动到 ModelManager(特别是当你使用几个复杂的方法 and/or 个相关模型)
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
感谢@Kevin Brown的精彩解释和解答!
在我的例子中,我需要对一个名为 total_donation
的 serializerMethodField 进行排序,它是来自 UserPayments table.
的 sum
捐款
UserPayments
有:
User
作为外键
sum
这是一个 IntegerField
related_name='payments'
我需要获得每个用户的总捐款,但只有状态为 'donated' 的捐款,而不是 'pending'。还需要过滤掉 payment_type coupon
,它通过另外两个外键关联。
我很惊讶如何加入和过滤这些捐赠,然后能够通过 ordering_fields
对其进行排序。
感谢您的 post 我明白了!
我意识到它需要成为原始 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',]
我有一个论坛主题模型,我想在计算的 SerializerMethodField 上排序,例如 vote_count。这里有一个非常简化的模型、序列化器和视图集来显示问题:
# 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
这是有效的方法:
- OrderingFilter默认开启,我可以成功下单
/topics?ordering=title
- 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 并且解析器正在将其转换为驼峰式。我也尝试过 ordering=voteCount,但这不起作用(也不应该)
这不可能使用 the default OrderingFilter
,因为排序是在数据库端 实现的。这是出于效率原因,因为手动对结果进行排序可能 难以置信地 缓慢并且意味着打破标准 QuerySet
。通过将所有内容都保留为 QuerySet
,您可以受益于 Django REST 框架(通常需要 QuerySet
)提供的内置过滤和内置分页(没有分页可能会很慢)。
现在,在这些情况下您有两个选择:弄清楚如何在数据库端检索您的值,或者尽量减少您将不得不承受的性能损失。由于后一个选项是非常特定于实现的,所以我现在将跳过它。
在这种情况下,您可以使用the Count
function provided by Django to do the count on the database side. This is provided as part of the aggregation API and works like 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
,因为方法和属性不一样,所以不能设置值。
我会把它放在这里,因为描述的案例不是唯一的。 这个想法是重写你的 Viewset 的列表方法,以通过你的任何 SerializerMethodField(s) 进行排序,而不会将你的逻辑从 Serializer 移动到 ModelManager(特别是当你使用几个复杂的方法 and/or 个相关模型)
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
感谢@Kevin Brown的精彩解释和解答!
在我的例子中,我需要对一个名为 total_donation
的 serializerMethodField 进行排序,它是来自 UserPayments table.
sum
捐款
UserPayments
有:
User
作为外键sum
这是一个IntegerField
related_name='payments'
我需要获得每个用户的总捐款,但只有状态为 'donated' 的捐款,而不是 'pending'。还需要过滤掉 payment_type coupon
,它通过另外两个外键关联。
我很惊讶如何加入和过滤这些捐赠,然后能够通过 ordering_fields
对其进行排序。
感谢您的 post 我明白了!
我意识到它需要成为原始 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',]