Igor Perepilitsyn
Igor Perepilitsyn

Reputation: 60

Django rest framework bypass entity in nested relation

Consider these three models:

class Genre(models.Model):
    slug = models.CharField(max_length=30, unique=True)
    name = models.CharField(max_length=300)

class Group(models.Model):
    slug = models.CharField(max_length=30, unique=True)
    name = models.CharField(max_length=300)
    genre = models.ForeignKey('Genre', related_name='groups')

class Album(models.Model):
    name = models.CharField(max_length=300)
    track_count = models.IntegerField()
    artist = models.ForeignKey('Group', related_name='albums')

Now, I need this kind of DRF output:

[ 
  {'name': 'Rock', 'albums': [
         {'name': 'Meteora', 'track_count': 12}, 
         {'name': 'Master of Puppets', 'track_count': 10'}]
  },
  {..},
]

In other words, I need to get all of the albums in each genre, bypassing "group" model. I have achieved something similar this way:

views.py

class SegmentViewSet(viewsets.ModelViewSet):

    queryset = Genre.objects.all().annotate(Count('name'))
    serializer_class = GenreSerializer

serializers.py

class GroupSerializer(serializers.ModelSerializer):
    albums = AlbumSerializer(many=True, read_only=True)

    def to_representation(self, value):
        return value.products.annotate(Count('id')).values('name', 'sap_code')

class Meta:
    model = Group
    fields = ('albums',)


class GenreSerializer(serializers.ModelSerializer):
    albums = GroupSerializer(many=True, read_only=True)

    class Meta:
        model = Genre
        fields = ('id',
                  'name',
                  'albums')

That gives me something like this:

[
  {'name': 'Rock', 'albums': [
      [ 
        {'name': 'Meteora', 'track_count': 12}, 
        {...}, 
        {...} 
      ], 
      [ 
        {...}, 
        {...}
      ] 
    ] 
  }
]

Which is albums, grouped by albums, grouped by genre. That is a nested array and I need just the albums grouped by genre in one array.

I know that my solution with to_representation function is pretty dirty, but it is something alike to what I'm looking for. But hey, that's what custom related fields are for.

Is there a way to get desired output without direct linking albums to genres? There really IS a reason to keep things this way.

I already tried custom related fields, PrimaryKeyRelatedField, even tried to overload get_queryset in my view. Did I overlook something?

Upvotes: 1

Views: 109

Answers (1)

mislavcimpersak
mislavcimpersak

Reputation: 3020

You could try defining serializers as follows:

from rest_framework import serializers


class GenreSerializer(serializers.ModelSerializer):
    albums = serializers.SerializerMethodField()

    class Meta:
        model = Genre

    def get_albums(self, obj):
        albums = Album.objects.filter(group__genre=obj)
        albums_serializer = AlbumSerializer(data=albums, many=True)
        albums_serializer.is_valid()
        return albums_serializer.data


class AlbumSerializer(serializers.ModelSerializer):
    class Meta:
        model = Album

The result should be as follows:

[{
        'name': 'Rock',
        'albums': [{
                'name': 'Meteora',
                'track_count': 13
            },
            {
                'name': 'Master of Puppets',
                'track_count': 12
            },
            {
                'name': 'Nevermind',
                'track_count': 11
            }
        ]
    },
    {
        'name': 'Pop',
        'albums': [{
                'name': 'Ray of Light',
                'track_count': 11
            },
            {
                'name': 'Thriller',
                'track_count': 14
            }
        ]
    }
]

Mind you that this is highly unoptimized and untested but from here I'm sure you know how to use GenreSeralizer in a view and optimize the code.

Upvotes: 1

Related Questions