KingFish
KingFish

Reputation: 9183

Changing Return Type on Creation in Serializer

I have the following two serializers:

class ProgramSerializer(serializers.ModelSerializer):
    class Meta:
        from radio.models import Program
        model = Program
        fields = ('id', 'title')

class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
    program_data = ProgramSerializer(read_only=True)
    class Meta:
        model = UserRecentlyPlayed
        fields = ('id', 'user', 'program', 'program_data',)

They are based on the following models:

class Program(models.Model):
    title = models.CharField(max_length=64)

class UserRecentlyPlayed(models.Model):
    user = models.ForeignKey(User)
    program = models.ForeignKey(Program)

What I'm trying to do is the following: On create, I want to be able create a new instance of UserRecentlyPlayed in the following manner:

{
    "user": "...user id ....",
    "program": "....program id...."
}

However, when I return a list, I would like to return the following:

[{
     "id": "... id .... ",
     "user": ".... user id .....",
     "program": {"id": "...program id...", "title": "...title..." }
 }]

These are called in the following view:

class RecentlyPlayed(generics.ListCreateAPIView):
    serializer_class = UserRecentlyPlayedSerializer

This, unfortunately is not working. What is the correct magic for this?

Upvotes: 2

Views: 544

Answers (3)

Enthusiast Martin
Enthusiast Martin

Reputation: 3091

You can rename your program_data in your serializer to program or you can specify source for your nested serializer.

That should return the output of list as you'd like.

class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
    program = ProgramSerializer(read_only=True)
    class Meta:
        model = UserRecentlyPlayed
        fields = ('id', 'user', 'program',)

or

class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
    program_data = ProgramSerializer(read_only=True, source='program')
    class Meta:
        model = UserRecentlyPlayed
        fields = ('id', 'user', 'program_data',)

And to support same json input for create, the easiest way is create another serializer for input:

class UserRecentlyPlayedSerializerInput(serializers.ModelSerializer):
    program = serializers.PrimaryKeyRelatedField(queryset=Program.objects.all())

    class Meta:
        model = UserRecentlyPlayed
        fields = ('id', 'user', 'program',)

And use it in your view when request is POST/PUT/PATCH:

class RecentlyPlayed(generics.ListCreateAPIView):
    serializer_class = UserRecentlyPlayedSerializer    

    def get_serializer_class(self):
        if self.request.method.lower() == 'get':
            return self.serializer_class

        return UserRecentlyPlayedSerializerInput

While this works great for a "get", I would like to see that same result after a create. I still see {"program": "...id...."

For this, you have to change slightly the implementation of create method in your view

   def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        instance = serializer.save()
        headers = self.get_success_headers(serializer.data)

        oser = UserRecentlyPlayedSerializer(instance)

        return Response(oser.data, status=status.HTTP_201_CREATED, 
headers=headers)

Upvotes: 1

KingFish
KingFish

Reputation: 9183

Ok, I went in a slightly different direction and it works. Instead of using the ListCreateAPIView, I created my own class using ListModeMixin, CreateModelMixin and GenericAPIView. The magic was in overriding the def list class. I also implemented a "return_serializer_class" attribute. That's what did it.

class RecentlyPlayed(mixins.ListModelMixin,
                     mixins.CreateModelMixin,
                     generics.GenericAPIView):
    serializer_class = UserRecentlyPlayedSerializer
    return_serializer_class = ProgramSerializer

    parser_classes = (JSONParser, MultiPartParser)

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.create(request, *args, **kwargs)
        return self.list(request, *args, **kwargs)

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.return_serializer_class(queryset, many=True)
        return Response({'recently_played': serializer.data})

Upvotes: 0

tayyab_fareed
tayyab_fareed

Reputation: 679

Firstly create a property named program_data in your model

class Program(models.Model):
    title = models.CharField(max_length=64)

class UserRecentlyPlayed(models.Model):
    user = models.ForeignKey(User)
    program = models.ForeignKey(Program)

    @property
    def program_data(self):
        return self.program

Then in your serializer you do not need to change anything following, it will remain same as below

class ProgramSerializer(serializers.ModelSerializer):
    class Meta:
        from radio.models import Program
        model = Program
        fields = ('id', 'title')

class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
    program_data = ProgramSerializer(read_only=True)
    class Meta:
        model = UserRecentlyPlayed
        fields = ('id', 'user', 'program', 'program_data',)

Upvotes: 0

Related Questions