diff --git a/rest_framework/generics.py b/rest_framework/generics.py index dd8dfcf8d..11701f98d 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -47,14 +47,14 @@ class GenericAPIView(views.APIView): return serializer_class - def get_serializer(self, instance=None, data=None, files=None): + def get_serializer(self, instance=None, data=None, files=None, partial=False): """ Return the serializer instance that should be used for validating and deserializing input, and for serializing output. """ serializer_class = self.get_serializer_class() context = self.get_serializer_context() - return serializer_class(instance, data=data, files=files, context=context) + return serializer_class(instance, data=data, files=files, partial=partial, context=context) class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView): @@ -169,7 +169,17 @@ class UpdateAPIView(mixins.UpdateModelMixin, Concrete view for updating a model instance. """ def put(self, request, *args, **kwargs): - return self.update(request, *args, **kwargs) + return self.update(request, partial=False, *args, **kwargs) + + +class ParitalUpdateAPIView(mixins.UpdateModelMixin, + SingleObjectAPIView): + + """ + Concrete view for paritally updating a model instance. + """ + def patch(self, request, *args, **kwargs): + return self.update(request, partial=True, *args, **kwargs) class ListCreateAPIView(mixins.ListModelMixin, @@ -209,7 +219,29 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): - return self.update(request, *args, **kwargs) + return self.update(request, partial=False, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + return self.destroy(request, *args, **kwargs) + + +class RetrievePartialUpdateDestroyAPIView(mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + SingleObjectAPIView): + + """ + Concrete view for retrieving, updating via PATCH (partial) or PUT (full), + or deleting a model instance. + """ + def get(self, request, *args, **kwargs): + return self.retrieve(request, *args, **kwargs) + + def put(self, request, *args, **kwargs): + return self.update(request, partial=False, *args, **kwargs) + + def patch(self, request, *args, **kwargs): + return self.update(request, partial=True, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 1edcfa5c9..b71ab05c1 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -81,7 +81,7 @@ class UpdateModelMixin(object): Update a model instance. Should be mixed in with `SingleObjectBaseView`. """ - def update(self, request, *args, **kwargs): + def update(self, request, partial=False, *args, **kwargs): try: self.object = self.get_object() created = False @@ -89,7 +89,7 @@ class UpdateModelMixin(object): self.object = None created = True - serializer = self.get_serializer(self.object, data=request.DATA, files=request.FILES) + serializer = self.get_serializer(self.object, data=request.DATA, files=request.FILES, partial=partial) if serializer.is_valid(): self.pre_save(serializer.object) diff --git a/rest_framework/tests/decorators.py b/rest_framework/tests/decorators.py index 41864d71e..a36444cca 100644 --- a/rest_framework/tests/decorators.py +++ b/rest_framework/tests/decorators.py @@ -63,6 +63,20 @@ class DecoratorTestCase(TestCase): response = view(request) self.assertEqual(response.status_code, 405) + # def test_calling_patch_method(self): + + # @api_view(['GET', 'PATCH']) + # def view(request): + # return Response({}) + + # request = self.factory.patch('/') + # response = view(request) + # self.assertEqual(response.status_code, 200) + + # request = self.factory.post('/') + # response = view(request) + # self.assertEqual(response.status_code, 405) + def test_renderer_classes(self): @api_view(['GET']) diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index a8279ef2b..1b55a3a53 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -180,6 +180,19 @@ class TestInstanceView(TestCase): updated = self.objects.get(id=1) self.assertEquals(updated.text, 'foobar') + # def test_patch_instance_view(self): + # """ + # PATCH requests to RetrieveUpdateDestroyAPIView should update an object. + # """ + # content = {'text': 'foobar'} + # request = factory.patch('/1', json.dumps(content), + # content_type='application/json') + # response = self.view(request, pk=1).render() + # self.assertEquals(response.status_code, status.HTTP_200_OK) + # self.assertEquals(response.data, {'id': 1, 'text': 'foobar'}) + # updated = self.objects.get(id=1) + # self.assertEquals(updated.text, 'foobar') + def test_delete_instance_view(self): """ DELETE requests to RetrieveUpdateDestroyAPIView should delete an object. diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py index 43365e07a..7cd826565 100644 --- a/rest_framework/tests/views.py +++ b/rest_framework/tests/views.py @@ -18,7 +18,7 @@ class BasicView(APIView): return Response({'method': 'POST', 'data': request.DATA}) -@api_view(['GET', 'POST', 'PUT']) +@api_view(['GET', 'POST', 'PUT', 'PATCH']) def basic_view(request): if request.method == 'GET': return {'method': 'GET'} @@ -26,6 +26,8 @@ def basic_view(request): return {'method': 'POST', 'data': request.DATA} elif request.method == 'PUT': return {'method': 'PUT', 'data': request.DATA} + elif request.method == 'PATCH': + return {'method': 'PATCH', 'data': request.DATA} def sanitise_json_error(error_dict):