Petr Hofman
Petr Hofman

Reputation: 172

How to flatten list of objects lists in django-rest-framework serializers?

I have 3 django models concatenated by ForeignKey:

# models.py
class Album(models.Model):
    some_fields

class Track(models.Model):
    some_fields
    album = models.ForeignKey(
        Album,
        related_name='tracks',
        on_delete=models.CASCADE,
    )

class Comment(models.Model):
    some_fields
    track = models.ForeignKey(
        Track,
        related_name='comments',
        on_delete=models.CASCADE,
    )

I would like to serialize the Album model to view all comments of all its tracks. I have created serializer file like this:

# serializers.py
class TrackSerializer(serializers.ModelSerializer):

    class Meta:
        model = Album
        fields = (some_fields, 'comments')

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True, read_only=True)

    class Meta:
        model = Album
        fields = (some_fields, 'tracks')

This way I get all the data but in nested lists. I would like to view all the comments of album tracks directly under album object.

# Output albums
[{
    "some_fields": some_values,
    "tracks": [{ "some_fields": some_values, "comments": [ comment1, comment2 ]},
            { "some_fields": some_values, "comments": []},
            { "some_fields": some_values, "comments": [ comment3 ]},]
},
{
    "some_fields": some_values,
    "tracks": [{ "some_fields": some_values, "comments": [ comment4, comment5 ]},
            { "some_fields": some_values, "comments": []},
            { "some_fields": some_values, "comments": [ comment6 ]},]
}]

# Desired output albums
[{
    "some_fields": some_values,
    "tracks": [{ "some_fields": some_values},
            { "some_fields": some_values},
            { "some_fields": some_values},],
    "comments": [ comment1, comment2, comment3]
},
{
    "some_fields": some_values,
    "tracks": [{ "some_fields": some_values},
            { "some_fields": some_values},
            { "some_fields": some_values},],
    "comments": [ comment4, comment5, comment6]
}]

I tried to flatten the list directly in serializers file but I get "TypeError: 'ListSerializer' object is not iterable".

# serializers.py
class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True, read_only=True)
    comments = [comment for track in tracks for comment in track.comments]

    class Meta:
        model = Album
        fields = (some_fields, 'tracks', 'comments')

Is there any neat way how to output a flattened list directly with serializers? Or should I do it later in views.py? Now it looks simply like this:

# views.py
class AlbumMixin(object):
    model = Album
    raise_exception = True
    serializer_class = AlbumSerializer

    def get_queryset(self):
        return Album.objects.all()

class AlbumList(AlbumMixin, generics.ListCreateAPIView):
    pass

Upvotes: 2

Views: 3907

Answers (2)

Rieljun Liguid
Rieljun Liguid

Reputation: 1531

You can add a serializers.SerializerMethodField on AlbumSerializer and return the necessary comments.

Something like this:

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = "__all__"


class AlbumSerializer(serializers.ModelSerializer):
    comments = serializers.SerializerMethodField()
    tracks = TrackSerializer(many=True, read_only=True)

    class Meta:
        model = Album
        fields = (some_fields, "comments", "tracks")

    def get_comments(self, obj):
        comments = Comment.objects.filter(track__in=obj.tracks.all())
        return CommentSerializer(comments, many=True).data
        # or you can get rid of CommentSerializer
        # return comments.values("some_field")

See SerializerMethodField docs here

Update

You can improve the query by using the one from @c6754

comments = Comment.objects.filter(track__album_id=obj.id)

Upvotes: 5

c6754
c6754

Reputation: 887

You could try using a serializerMethodField

# serializers.py
class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True, read_only=True)
    comments = serializers.SerializerMethodField()

    class Meta:
        model = Album
        fields = (some_fields, 'tracks', 'comments')

    def get_comments(self, obj):
        comments = Comments.objects.filter(track__album_id=obj.pk)
        return CommentSerializer(comments, many=True).data

Upvotes: 2

Related Questions