Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use different Django Rest Framework serializers for the same request in Generic Views?

I'm using Django Rest Framework in an API project and am trying to figure out if there's a way to use two different serializers with the generic views (e.g. CreateAPIView). I want to use one serializer for deserializing a POST request, and a different one for serializing the resulting response.

Here's what I'm trying to do; I'll illustrate using the Album/Track examples from the docs:

The model that I'm working with has a ForeignKey relationship. In the API, I'd like to just be able to include the FK in the request when assigning the relationship, so in the serializer I'm using a PrimaryKeyRelatedField, similar to how the AlbumSerializer handles the relationship to Tracks:

class CreateAlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.PrimaryKeyRelatedField(many=True)

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

However, on the response, I'd like to include a full representation of the Album using a ModelSerializer, not just the PK, slug, etc., something like this:

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

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

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Album
        fields = ('order', 'title', 'duration')

The generic DRF views allow you to either specify the serializer_class or override the get_serializer_class method, but I can't figure out how to use that to accomplish what I'm after.

Is there something obvious that I'm missing? This seems like a reasonable thing to want to do, but I can't seem to grok how to get it done.

like image 800
Mark Kennedy Avatar asked Aug 12 '15 16:08

Mark Kennedy


1 Answers

Approach #1

Overwrite the generic mixins in the DRF ViewSet. For example:

class MyViewSet(CreateModelMixin, MultipleSerializersViewMixin, ViewSet):
    serializer_class = CreateAlbumSerializer

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

        saved = self.perform_create(serializer)
        serializer = self.get_serializer(instance=saved, serializer_class=AlbumSerializer)

        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        return serializer.save()

MultipleSerializersViewMixin is taken from django-rest-framework-braces.

Approach #2

Customize to_representation of the CreateAlbumSerializer. For example:

class MyViewSet(CreateModelMixin, ViewSet):
    serializer_class = CreateAlbumSerializer

class CreateAlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.PrimaryKeyRelatedField(many=True)

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

    def to_representation(self, instance):
        data = super(CreateAlbumSerializer, self).to_representation(instance)
        data['tracks'] = TrackSerializer(instance=instance.tracks).data
        return data

Comparison

I personally like approach #1 instead of #2 even though it is more verbose since it does not leak any of the custom create/response logic to serializers. I think serializer should just know how to serialize and all custom requirements to pick different serializers for the job should be done in the views.

like image 99
miki725 Avatar answered Nov 12 '22 02:11

miki725