Ofirov
Ofirov

Reputation: 773

How to order a Django Rest Framework ManyToMany related field?

I have a Django Rest Framework application with the following (simplified) models.py:

class Photo(models.Model):
    ...

class Album(models.Model):
    ...
    photos = models.ManyToManyField(Photo, through='PhotoInAlbum', related_name='albums')

class PhotoInAlbum(models.Model):

    photo = models.ForeignKey(Photo)
    album = models.ForeignKey(Album)

    order = models.IntegerField()

    class Meta:
        ordering = ['album', 'order']

And in my serializers.py, I have the following:

class AlbumSerializer(serializers.ModelSerializer):

    ...
    photos = serializers.PrimaryKeyRelatedField('photos', many=True)

My question is, how can I have AlbumSerializer return the photos ordered by the field order?

Upvotes: 12

Views: 9012

Answers (3)

Enix
Enix

Reputation: 4579

The best solution to customise the queryset is using serializers.SerializerMethodField, but what shezi's reply is not exactly right. You need to return serializer.data from SerializerMethodField. So the solution should be like this:

class PhotoInAlbumSerializer(serialisers.ModelSerializer):
    class Meta:
        model = PhotoInAlbum


class AlbumSerializer(serializers.ModelSerializer):
    # ...

    photos = serializers.SerializerMethodField('get_photos_list')

    def get_photos_list(self, instance):
        photos = PhotoInAlbum.objects\
            .filter(album_id=instance.id)\
            .order_by('order')\
            .values_list('photo_id', flat=True)

        return PhotoInAlbumSerializer(photos, many=True, context=self.context).data

Upvotes: 9

shezi
shezi

Reputation: 588

It looks as if the RelatedManager that handles the relationship for ManyToManyFields does not respect ordering on the through model.

Since you cannot easily add an ordering parameter to the serializer field, the easiest way to achieve ordering is by using a serializer method:

class AlbumSerializer(serializers.modelSerializer):
    # ...

    photos = serializers.SerializerMethodField('get_photos_list')

    def get_photos_list(self, instance):
        return PhotoInAlbum.objects\
            .filter(album_id=instance.id)\
            .order_by('order')\
            .values_list('photo_id', flat=True)

Upvotes: 5

Patryk Hes
Patryk Hes

Reputation: 78

Generally, the easiest way is to do this in your AlbumView or AlbumViewSet. You can do this by filtering - in this case you should define a get_queryset method in your AlbumViewSet.

Anyway, this is a good solution as long as you only GET the data. If you want to have POST and PUT methods working with ordering the photos, you can do it in two ways:

  • stay with ManyToMany relation - patch the create method in AlbumViewSet and __create_items and restore_object method in AlbumSerializer

or

Note that the second solution does not mess with AlbumViewSet (and even AlbumSerializer!) - ordering logic stays in the relation field code.

Upvotes: 2

Related Questions