56

Is there a way to order a nested serializer _set, for example order by pk or time-stamp.

So basically order song_set shown in the json data below from the most recent to the latest object created, in this case by order_by('-timestamp') or order_by('-pk').

Json data

{
    "pk": 151,
    "album_name": "Name",
    "song_set": [
         {
           pk: 3,
           timestamp: '5 seconds'
         },
         {
           pk: 2,
           timestamp: '10 seconds'
         },
         {
           pk: 1,
           timestamp: '15 seconds'
         }
    ]
}

Model

class Album(models.Model):
    album_name     = models.CharField(max_length=100, blank=True)


class Song(models.Model):
    album          = models.ForeignKey('album.Album', default=1)
    timestamp      = models.DateTimeField(auto_now_add=True, auto_now=False)

Serializer

class SongListSerializer(HyperlinkedModelSerializer):
    class Meta:
        model = Song
        fields = [
            'pk',
            'timestamp'
        ]

class AlbumSerializer(HyperlinkedModelSerializer):
    song_set = SongListSerializer(many=True, read_only=True)
    class Meta:
        model = Album
        fields = [
            'pk',
            'timestamp',
            'song_set'
        ]
1

5 Answers 5

77

You can use SerializerMethodField and write custom method for this.

class AlbumSerializer(HyperlinkedModelSerializer):
    song_set = serializers.SerializerMethodField()
    class Meta:
        model = Album
        fields = [
            'pk',
            'timestamp',
            'song_set'
        ]

    def get_song_set(self, instance):
        songs = instance.song_set.all().order_by('-timestamp')
        return SongListSerializer(songs, many=True).data
Sign up to request clarification or add additional context in comments.

5 Comments

How can I pass a variable to filter the values with a field?
You can pass data in serializer context.
I did that way, but was thinking like If I can pass the set of filtered instances along with the data like "AlbumSerializer(data,song_instances)" this is wrong but is ther ea similar way to directly pass instances to nested list serializer without using serializermethod()?
If you do not want to filter your data on the basis of your instance, than you can pass filtered instances to you serializer. If tou want to pass data directly to your nested serialized, than that data must be part of your queryset.
This will cause your schema generator to be unable to determine the type of song_set field. If using libraries like drf_spectacular you will need to extend_schema_field or at least provide a type hinting for the get_song_set property.
65

Add ordering meta parameter to your Song model:

class Song(models.Model):
    album = models.ForeignKey('album.Album', default=1)
    timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)

    class Meta:
        ordering = ['timestamp', 'pk']

4 Comments

This solved my issue when i didn't want to adopt the SerializerMethodField solution
Beware this will affect every use of the Song model. It's not limited to the serializer.
per what @djvg said, this is a bad idea performance wise
It's a very bad practice, because every time you make a query (even fetching only a few records) the DB will run a full table scan! Either create an index on the timestamp or use the other method
37

In your ViewSet, you can specify a queryset with a custom Prefetch object that you can filter and order as you like. Prefetching causes just one additional database query (instead of one per parent object when using SerializerMethodField), giving vastly improved performance.

from rest_framework import viewsets
from django.db.models import Prefetch

class AlbumViewSet(viewsets.ModelViewSet):
    queryset = Album.objects.prefetch_related(Prefetch('song_set',
        queryset=Song.objects.order_by('-timestamp')))

Comments

8

Old thread, but because it's still popping up on Google I want to share my answer as well. Try overwriting the Serializer.to_representation method. Now you can basically do whatever you want, including customising the sorting of your response. In your case:

class AlbumSerializer(HyperlinkedModelSerializer):
    song_set = SongListSerializer(many=True, read_only=True)
    class Meta:
        model = Album
        fields = [
            'pk',
            'timestamp',
            'song_set'
        ]

    def to_representation(self, instance):
        response = super().to_representation(instance)
        response["song_set"] = sorted(response["song_set"], key=lambda x: x["timestamp"])
        return response

2 Comments

I'm sure there could be a better way to do this. I think changing the order through the ModelManager could be a better approach. Not sure about it, though.
This looks like it could have run time performance impacts since sorting seems to be left to the server and the not the database.
1

Another way to solve this is to modify the to_representation method on the ListSerializer to change the ordering when the relationship is queried via the ORM.

This to_representation is similar to how it works for the base ListSerializer in DRF.

class SongListSerializer(HyperlinkedModelSerializer):
    class Meta:
        model = Song
        fields = [
            'pk',
            'timestamp'
        ]

    def to_representation(self, data):
        iterable = data.all().order_by('-timestamp', '-id') \
            if isinstance(data, models.Manager) else data

        return [self.child.to_representation(item) for item in iterable]



class AlbumSerializer(HyperlinkedModelSerializer):
    song_set = SongListSerializer(many=True, read_only=True)
    class Meta:
        model = Album
        fields = [
            'pk',
            'timestamp',
            'song_set'
        ]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.