Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework Ordering on a SerializerMethodField

Tags:

I have a Forum Topic model that I want to order on a computed SerializerMethodField, such as vote_count. Here are a very simplified Model, Serializer and ViewSet to show the issue:

# 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 

Here is what works:

  1. OrderingFilter is on by default and I can successfully order /topics?ordering=title
  2. The vote_count function works perfectly

I'm trying to order by the MethodField on the TopicSerializer, vote_count like /topics?ordering=-vote_count but it seems that is not supported. Is there any way I can order by that field?

My simplified JSON response looks like this:

{     "id": 1,     "title": "first post",     "voteCount": 1 }, {      "id": 2,     "title": "second post",     "voteCount": 8 }, {      "id": 3,     "title": "third post",     "voteCount": 4 } 

I'm using Ember to consume my API and the parser is turning it to camelCase. I've tried ordering=voteCount as well, but that doesn't work (and it shouldn't)

like image 765
awwester Avatar asked May 04 '15 23:05

awwester


People also ask

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.

Why use Django REST framework instead of Django?

Its main benefit is that it makes serialization much easier. Django REST framework is based on Django's class-based views, so it's an excellent option if you're familiar with Django. It adopts implementations like class-based views, forms, model validator, QuerySet, and more.

Is Django REST framework fast?

Django/Python/REST framework is too slow. As this article will argue, the biggest performance gains for Web APIs can be made not by code tweaking, but by proper caching of database lookups, well designed HTTP caching, and running behind a shared server-side cache if possible.


1 Answers

This is not possible using the default OrderingFilter, because the ordering is implemented on the database side. This is for efficiency reasons, as manually sorting the results can be incredibly slow and means breaking from a standard QuerySet. By keeping everything as a QuerySet, you benefit from the built-in filtering provided by Django REST framework (which generally expects a QuerySet) and the built-in pagination (which can be slow without one).

Now, you have two options in these cases: figure out how to retrieve your value on the database side, or try to minimize the performance hit you are going to have to take. Since the latter option is very implementation-specific, I'm going to skip it for now.

In this case, you can use 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. You can do the equivalent Count call by modifying your queryset on the view to be

queryset = Topic.objects.annotate(vote_count=Count('topicvote_set')) 

Replacing topicvote_set with your related_name for the field (you have one set, right?). This will allow you to order the results based on the number of votes, and even do filtering (if you want to) because it is available within the query itself.

This would require making a slight change to your serializer, so it pulls from the new vote_count property available on objects.

class TopicSerializer(serializers.ModelSerializer):     vote_count = serializers.IntegerField(read_only=True)      class Meta:         model = Topic 

This will override your existing vote_count method, so you may want to rename the variable used when annotating (if you can't replace the old method).


Also, you can pass a method name as the source of a Django REST framework field and it will automatically call it. So technically your current serializer could just be

class TopicSerializer(serializers.ModelSerializer):     vote_count = serializers.IntegerField(read_only=True)      class Meta:         model = Topic 

And it would work exactly like it currently does. Note that read_only is required in this case because a method is not the same as a property, so the value cannot be set.

like image 130
Kevin Brown-Silva Avatar answered Oct 13 '22 06:10

Kevin Brown-Silva