From e198a2b37673a07a7cc374175c205362da34360e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Thu, 13 Dec 2012 16:57:17 +0100 Subject: [PATCH 1/9] added RetrieveUpdateAPIView --- docs/api-guide/generic-views.md | 8 ++++++++ docs/topics/release-notes.md | 4 ++++ rest_framework/generics.py | 12 ++++++++++++ 3 files changed, 24 insertions(+) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 428323b89..ef09dfe50 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -97,6 +97,14 @@ Provides `get` and `post` method handlers. Extends: [MultipleObjectAPIView], [ListModelMixin], [CreateModelMixin] +## RetrieveUpdateAPIView + +Used for **read or update** endpoints to represent a **single model instance**. + +Provides `get` and `put` method handlers. + +Extends: [SingleObjectAPIView], [RetrieveModelMixin], [UpdateModelMixin] + ## RetrieveDestroyAPIView Used for **read or delete** endpoints to represent a **single model instance**. diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 4f83cfd8f..a74ee5202 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -4,6 +4,10 @@ > > — Eric S. Raymond, [The Cathedral and the Bazaar][cite]. +## Master + +* Added `RetrieveUpdateAPIView` + ## 2.1.9 **Date**: 11th Dec 2012 diff --git a/rest_framework/generics.py b/rest_framework/generics.py index dd8dfcf8d..3e4f29ad2 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -185,6 +185,18 @@ class ListCreateAPIView(mixins.ListModelMixin, return self.create(request, *args, **kwargs) +class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + SingleObjectAPIView): + """ + Concrete view for retrieving, updating 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, *args, **kwargs) + class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, mixins.DestroyModelMixin, SingleObjectAPIView): From 76c840f1bb3b934dc2127faa04704ab4b11a018b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Thu, 13 Dec 2012 20:41:40 +0100 Subject: [PATCH 2/9] added missing line --- rest_framework/generics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 3e4f29ad2..507b12cb1 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -197,6 +197,7 @@ class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) + class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, mixins.DestroyModelMixin, SingleObjectAPIView): From 4f96951be9800cfc1fc67795a26d11ac66abc60d Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Sun, 16 Dec 2012 13:11:59 -0500 Subject: [PATCH 3/9] Add 'patch' http method support to the Django View object --- rest_framework/compat.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index d4901437d..5996b16e7 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -90,6 +90,12 @@ else: update_wrapper(view, cls.dispatch, assigned=()) return view +# Taken from @markotibold's attempt at supporting PATCH. +# https://github.com/markotibold/django-rest-framework/tree/patch +http_method_names = set(View.http_method_names) +http_method_names.add('patch') +View.http_method_names = list(http_method_names) # PATCH method is not implemented by Django + # PUT, DELETE do not require CSRF until 1.4. They should. Make it better. if django.VERSION >= (1, 4): from django.middleware.csrf import CsrfViewMiddleware From 18338a37d356faebb0f59bd57b2ba876d66e6b73 Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Sun, 16 Dec 2012 14:49:18 -0500 Subject: [PATCH 4/9] Adding PATCH support to Django REST Framework --- rest_framework/generics.py | 40 +++++++++++++++++++++++++++--- rest_framework/mixins.py | 4 +-- rest_framework/tests/decorators.py | 14 +++++++++++ rest_framework/tests/generics.py | 13 ++++++++++ rest_framework/tests/views.py | 4 ++- 5 files changed, 68 insertions(+), 7 deletions(-) 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): From 2b5deefe567d46315e9233fd405328e762e4ce07 Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Thu, 20 Dec 2012 00:27:29 -0500 Subject: [PATCH 5/9] Subclass Django's RequestFactory to provide PATCH support --- rest_framework/tests/utils.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 rest_framework/tests/utils.py diff --git a/rest_framework/tests/utils.py b/rest_framework/tests/utils.py new file mode 100644 index 000000000..d7f14156c --- /dev/null +++ b/rest_framework/tests/utils.py @@ -0,0 +1,27 @@ +from django.test.client import RequestFactory, FakePayload +from django.test.client import MULTIPART_CONTENT +from urlparse import urlparse + + +class DRFRequestFactory(RequestFactory): + + def __init__(self, **defaults): + super(DRFRequestFactory, self).__init__(**defaults) + + def patch(self, path, data={}, content_type=MULTIPART_CONTENT, + **extra): + "Construct a PATCH request." + + patch_data = self._encode_data(data, content_type) + + parsed = urlparse(path) + r = { + 'CONTENT_LENGTH': len(patch_data), + 'CONTENT_TYPE': content_type, + 'PATH_INFO': self._get_path(parsed), + 'QUERY_STRING': parsed[4], + 'REQUEST_METHOD': 'PATCH', + 'wsgi.input': FakePayload(patch_data), + } + r.update(extra) + return self.request(**r) From e61eab43f46dd100cf3efe9262474046678951a3 Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Thu, 20 Dec 2012 00:28:01 -0500 Subject: [PATCH 6/9] Adjust PATCH test cases to use the new DRFRequestFactory --- rest_framework/tests/decorators.py | 26 +++++++++++++------------ rest_framework/tests/generics.py | 31 +++++++++++++++--------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/rest_framework/tests/decorators.py b/rest_framework/tests/decorators.py index a36444cca..c2387f584 100644 --- a/rest_framework/tests/decorators.py +++ b/rest_framework/tests/decorators.py @@ -1,7 +1,7 @@ from django.test import TestCase from rest_framework import status from rest_framework.response import Response -from django.test.client import RequestFactory +# from django.test.client import RequestFactory from rest_framework.renderers import JSONRenderer from rest_framework.parsers import JSONParser from rest_framework.authentication import BasicAuthentication @@ -17,11 +17,13 @@ from rest_framework.decorators import ( permission_classes, ) +from rest_framework.tests.utils import DRFRequestFactory + class DecoratorTestCase(TestCase): def setUp(self): - self.factory = RequestFactory() + self.factory = DRFRequestFactory() def _finalize_response(self, request, response, *args, **kwargs): response.request = request @@ -63,19 +65,19 @@ class DecoratorTestCase(TestCase): response = view(request) self.assertEqual(response.status_code, 405) - # def test_calling_patch_method(self): + def test_calling_patch_method(self): - # @api_view(['GET', 'PATCH']) - # def view(request): - # return Response({}) + @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.patch('/') + response = view(request) + self.assertEqual(response.status_code, 200) - # request = self.factory.post('/') - # response = view(request) - # self.assertEqual(response.status_code, 405) + request = self.factory.post('/') + response = view(request) + self.assertEqual(response.status_code, 405) def test_renderer_classes(self): diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index 1b55a3a53..a5432d11d 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -1,11 +1,11 @@ from django.test import TestCase -from django.test.client import RequestFactory from django.utils import simplejson as json from rest_framework import generics, serializers, status +from rest_framework.tests.utils import DRFRequestFactory from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel -factory = RequestFactory() +factory = DRFRequestFactory() class RootView(generics.ListCreateAPIView): @@ -15,7 +15,7 @@ class RootView(generics.ListCreateAPIView): model = BasicModel -class InstanceView(generics.RetrieveUpdateDestroyAPIView): +class InstanceView(generics.RetrievePartialUpdateDestroyAPIView): """ Example description for OPTIONS. """ @@ -180,18 +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_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): """ From b9e48e84138baeab3849cd80408569ed3d0308fc Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Sun, 30 Dec 2012 13:56:59 -0400 Subject: [PATCH 7/9] Removing Partial Update classes PATCH methods merged into RetrieveUpdateDestroy class --- rest_framework/generics.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 11701f98d..14e4430e3 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -171,13 +171,6 @@ class UpdateAPIView(mixins.UpdateModelMixin, def put(self, 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) @@ -224,24 +217,5 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, 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) From df1880185c87733a82a41392898e67fe02c769aa Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Sun, 30 Dec 2012 13:57:43 -0400 Subject: [PATCH 8/9] Renaming DRFRequestFactory to RequestFactory Updated tests to reflect the new name. --- rest_framework/tests/decorators.py | 5 ++--- rest_framework/tests/generics.py | 6 +++--- rest_framework/tests/utils.py | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/rest_framework/tests/decorators.py b/rest_framework/tests/decorators.py index c2387f584..5e6bce4ef 100644 --- a/rest_framework/tests/decorators.py +++ b/rest_framework/tests/decorators.py @@ -1,7 +1,6 @@ from django.test import TestCase from rest_framework import status from rest_framework.response import Response -# from django.test.client import RequestFactory from rest_framework.renderers import JSONRenderer from rest_framework.parsers import JSONParser from rest_framework.authentication import BasicAuthentication @@ -17,13 +16,13 @@ from rest_framework.decorators import ( permission_classes, ) -from rest_framework.tests.utils import DRFRequestFactory +from rest_framework.tests.utils import RequestFactory class DecoratorTestCase(TestCase): def setUp(self): - self.factory = DRFRequestFactory() + self.factory = RequestFactory() def _finalize_response(self, request, response, *args, **kwargs): response.request = request diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index a5432d11d..33ac4b328 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -1,11 +1,11 @@ from django.test import TestCase from django.utils import simplejson as json from rest_framework import generics, serializers, status -from rest_framework.tests.utils import DRFRequestFactory +from rest_framework.tests.utils import RequestFactory from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel -factory = DRFRequestFactory() +factory = RequestFactory() class RootView(generics.ListCreateAPIView): @@ -15,7 +15,7 @@ class RootView(generics.ListCreateAPIView): model = BasicModel -class InstanceView(generics.RetrievePartialUpdateDestroyAPIView): +class InstanceView(generics.RetrieveUpdateDestroyAPIView): """ Example description for OPTIONS. """ diff --git a/rest_framework/tests/utils.py b/rest_framework/tests/utils.py index d7f14156c..3906adb9a 100644 --- a/rest_framework/tests/utils.py +++ b/rest_framework/tests/utils.py @@ -3,10 +3,10 @@ from django.test.client import MULTIPART_CONTENT from urlparse import urlparse -class DRFRequestFactory(RequestFactory): +class RequestFactory(RequestFactory): def __init__(self, **defaults): - super(DRFRequestFactory, self).__init__(**defaults) + super(RequestFactory, self).__init__(**defaults) def patch(self, path, data={}, content_type=MULTIPART_CONTENT, **extra): From b807f3d52a68dbf657c6437f71ecbfcba0695972 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 2 Jan 2013 13:39:24 +0000 Subject: [PATCH 9/9] Keep API backwards compatible. --- rest_framework/generics.py | 16 ++++++++++------ rest_framework/mixins.py | 21 +++++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 14e4430e3..3a38fab4f 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -47,14 +47,16 @@ class GenericAPIView(views.APIView): return serializer_class - def get_serializer(self, instance=None, data=None, files=None, partial=False): + 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, partial=partial, context=context) + return serializer_class(instance, data=data, files=files, + partial=partial, context=context) class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView): @@ -169,10 +171,11 @@ class UpdateAPIView(mixins.UpdateModelMixin, Concrete view for updating a model instance. """ def put(self, request, *args, **kwargs): - return self.update(request, partial=False, *args, **kwargs) + return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): - return self.update(request, partial=True, *args, **kwargs) + kwargs['partial'] = True + return self.update(request, *args, **kwargs) class ListCreateAPIView(mixins.ListModelMixin, @@ -212,10 +215,11 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): - return self.update(request, partial=False, *args, **kwargs) + return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) def patch(self, request, *args, **kwargs): - return self.update(request, partial=True, *args, **kwargs) + kwargs['partial'] = True + return self.update(request, *args, **kwargs) diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index d828078d7..43581ae9b 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -16,11 +16,14 @@ class CreateModelMixin(object): """ def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.DATA, files=request.FILES) + if serializer.is_valid(): self.pre_save(serializer.object) self.object = serializer.save() headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + return Response(serializer.data, status=status.HTTP_201_CREATED, + headers=headers) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def get_success_headers(self, data): @@ -81,21 +84,22 @@ class UpdateModelMixin(object): Update a model instance. Should be mixed in with `SingleObjectBaseView`. """ - def update(self, request, partial=False, *args, **kwargs): + def update(self, request, *args, **kwargs): + partial = kwargs.pop('partial', False) try: self.object = self.get_object() - created = False + success_status_code = status.HTTP_200_OK except Http404: self.object = None - created = True + success_status_code = status.HTTP_201_CREATED - serializer = self.get_serializer(self.object, data=request.DATA, files=request.FILES, partial=partial) + serializer = self.get_serializer(self.object, data=request.DATA, + files=request.FILES, partial=partial) if serializer.is_valid(): self.pre_save(serializer.object) self.object = serializer.save() - status_code = created and status.HTTP_201_CREATED or status.HTTP_200_OK - return Response(serializer.data, status=status_code) + return Response(serializer.data, status=success_status_code) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -115,7 +119,8 @@ class UpdateModelMixin(object): # Ensure we clean the attributes so that we don't eg return integer # pk using a string representation, as provided by the url conf kwarg. - obj.full_clean() + if hasattr(obj, 'full_clean'): + obj.full_clean() class DestroyModelMixin(object):