diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index bb748981e..c4770bb32 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -64,7 +64,8 @@ Each of the concrete generic views provided is built by combining `GenericAPIVie The following attributes control the basic view behavior. * `queryset` - The queryset that should be used for returning objects from this view. Typically, you must either set this attribute, or override the `get_queryset()` method. -* `serializer_class` - The serializer class that should be used for validating and deserializing input, and for serializing output. Typically, you must either set this attribute, or override the `get_serializer_class()` method. +* `serializer_class` - The serializer class that should be used for validating and deserializing input. Typically, you must either set this attribute, or override the `get_serializer_class()` method. +* `serializer_class_for_output` - The serializer class that should be used for deserializing input, and for serializing output. You don't have to set this, if it is unset, `serializer_class` is used instead by default. You can override `get_serializer_class_for_output()` to change this behavior. * `lookup_field` - The model field that should be used to for performing object lookup of individual model instances. Defaults to `'pk'`. Note that when using hyperlinked APIs you'll need to ensure that *both* the API views *and* the serializer classes set the lookup fields if you need to use a custom value. * `lookup_url_kwarg` - The URL keyword argument that should be used for object lookup. The URL conf should include a keyword argument corresponding to this value. If unset this defaults to using the same value as `lookup_field`. @@ -141,7 +142,7 @@ For example: Returns the class that should be used for the serializer. Defaults to returning the `serializer_class` attribute, or dynamically generating a serializer class if the `model` shortcut is being used. -May be override to provide dynamic behavior such as using different serializers for read and write operations, or providing different serializers to different types of users. +May be override to provide dynamic behavior such as providing different serializers to different types of users. To usie different serializers for read and write operations, see `get_serializer_class_for_output`. For example: @@ -150,6 +151,36 @@ For example: return FullAccountSerializer return BasicAccountSerializer +#### `get_serializer_class_for_output(self)` + +Returns the class that should be used for the serializer when deserializing objects. By default this returns the `serializer_class_for_output` attribute. If this attribute is not set, this is identical to `get_serializer_class`. + +This method allows you to use a different serializer for read and write operations on objects. For example: + + class Album(Model): + title = CharField(max_length=127) + + class Track(Model): + title = CharField(max_length=127) + album = ForeignKey(Album, related_name='tracks') + + class TrackSerializer(ModelSerializer): + class Meta: + fields = ('title', 'album') + + class TrackSerializerWithDetails(ModelSerializer): + album = AlbumSerializer() + + class Meta: + fields = ('title', 'album') + +In this situation you may wish to display the full album information in the Track view, but you want the album field to be editable, and you don't want to supply a full Album object when updating or creating a Track. This is easily done by creating your ModelViewSet as such: + + class TrackViewSet(ModelViewSet): + queryset = Track.objects.all() + serializer_class = TrackSerializer + serializer_class_for_output = TrackSerializerWithDetails + #### `get_paginate_by(self)` Returns the page size to use with pagination. By default this uses the `paginate_by` attribute, and may be overridden by the client if the `paginate_by_param` attribute is set. diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 7fc9db364..21f5d0d41 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -46,6 +46,11 @@ class GenericAPIView(views.APIView): queryset = None serializer_class = None + # You may set this attribute to use a different serializer for + # deserializing objects. You can override + # `get_serializer_class_for_output()` instead of setting it. + serializer_class_for_output = None + # This shortcut may be used instead of setting either or both # of the `queryset`/`serializer_class` attributes, although using # the explicit style is generally preferred. @@ -94,7 +99,7 @@ class GenericAPIView(views.APIView): partial=False, allow_add_remove=False): """ Return the serializer instance that should be used for validating and - deserializing input, and for serializing output. + deserializing input. """ serializer_class = self.get_serializer_class() context = self.get_serializer_context() @@ -103,13 +108,23 @@ class GenericAPIView(views.APIView): allow_add_remove=allow_add_remove, context=context) + def get_serializer_for_output(self, instance=None, many=False): + """ + Return the serializer instance that should be used for + serializing output. Defaults to the serializer instance used + for deserializing input. + """ + serializer_class = self.get_serializer_class_for_output() + context = self.get_serializer_context() + return serializer_class(instance, context=context, many=many) + def get_pagination_serializer(self, page): """ Return a serializer instance to use with paginated data. """ class SerializerClass(self.pagination_serializer_class): class Meta: - object_serializer_class = self.get_serializer_class() + object_serializer_class = self.get_serializer_class_for_output() pagination_serializer_class = SerializerClass context = self.get_serializer_context() @@ -252,6 +267,19 @@ class GenericAPIView(views.APIView): model = self.model return DefaultSerializer + def get_serializer_class_for_output(self): + """ + Return the class to use for the serializer when deserializing + objects. + + Defaults to using `self.serializer_class_for_output`, if None, + defaults to `self.get_serializer()`. + """ + serializer_class = self.serializer_class_for_output + if serializer_class is None: + return self.get_serializer_class() + return serializer_class + def get_queryset(self): """ Get the list of items for this view. diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index e1a24dc7e..01f452424 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -53,8 +53,9 @@ class CreateModelMixin(object): self.pre_save(serializer.object) self.object = serializer.save(force_insert=True) self.post_save(self.object, created=True) + out_serializer = self.get_serializer_for_output(self.object) headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, + return Response(out_serializer.data, status=status.HTTP_201_CREATED, headers=headers) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -93,7 +94,8 @@ class ListModelMixin(object): if page is not None: serializer = self.get_pagination_serializer(page) else: - serializer = self.get_serializer(self.object_list, many=True) + serializer = self.get_serializer_for_output( + self.object_list, many=True) return Response(serializer.data) @@ -104,7 +106,7 @@ class RetrieveModelMixin(object): """ def retrieve(self, request, *args, **kwargs): self.object = self.get_object() - serializer = self.get_serializer(self.object) + serializer = self.get_serializer_for_output(self.object) return Response(serializer.data) @@ -136,7 +138,8 @@ class UpdateModelMixin(object): self.object = serializer.save(force_update=True) self.post_save(self.object, created=False) - return Response(serializer.data, status=status.HTTP_200_OK) + out_serializer = self.get_serializer_for_output(self.object) + return Response(out_serializer.data, status=status.HTTP_200_OK) def partial_update(self, request, *args, **kwargs): kwargs['partial'] = True