From 964fbc2bd21e7ed40e3a01d19d31beeb2c8be769 Mon Sep 17 00:00:00 2001 From: David Avsajanishvili Date: Mon, 3 Jun 2013 13:42:19 +0400 Subject: [PATCH 1/3] Change status code to 422 (UNPROCESSABLE ENTITY) on validation error --- rest_framework/mixins.py | 4 ++-- rest_framework/status.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index f11def6d4..e41fe1daa 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -55,7 +55,7 @@ class CreateModelMixin(object): return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_422_UNPROCESSABLE_ENTITY) def get_success_headers(self, data): try: @@ -132,7 +132,7 @@ class UpdateModelMixin(object): self.post_save(self.object, created=created) return Response(serializer.data, status=success_status_code) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_422_UNPROCESSABLE_ENTITY) def partial_update(self, request, *args, **kwargs): kwargs['partial'] = True diff --git a/rest_framework/status.py b/rest_framework/status.py index b9f249f9f..9f47539e9 100644 --- a/rest_framework/status.py +++ b/rest_framework/status.py @@ -41,6 +41,7 @@ HTTP_414_REQUEST_URI_TOO_LONG = 414 HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 HTTP_417_EXPECTATION_FAILED = 417 +HTTP_422_UNPROCESSABLE_ENTITY = 422 HTTP_428_PRECONDITION_REQUIRED = 428 HTTP_429_TOO_MANY_REQUESTS = 429 HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 From 6b2f1c73713039b3aa9cc62da343549be8d86c33 Mon Sep 17 00:00:00 2001 From: David Avsajanishvili Date: Mon, 3 Jun 2013 14:19:40 +0400 Subject: [PATCH 2/3] Test HTTP 422 --- rest_framework/tests/test_generics.py | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/rest_framework/tests/test_generics.py b/rest_framework/tests/test_generics.py index 37734195a..75dc4a36f 100644 --- a/rest_framework/tests/test_generics.py +++ b/rest_framework/tests/test_generics.py @@ -158,6 +158,21 @@ class TestRootView(TestCase): created = self.objects.get(id=4) self.assertEqual(created.text, 'foobar') + def test_post_unprocessable_entity(self): + """ + POST requests with wrong JSON data should raise HTTP 422 + (UNPROCESSABLE ENTITY) + """ + content = {'id': 999, 'wrongtext': 'foobar'} + request = factory.post('/', json.dumps(content), + content_type='application/json') + with self.assertNumQueries(0): + response = self.view(request, pk=1).render() + self.assertEqual(response.status_code, + status.HTTP_422_UNPROCESSABLE_ENTITY) + self.assertIn('text', response.data) + self.assertEqual(response.data['text'], ['This field is required.']) + class TestInstanceView(TestCase): def setUp(self): @@ -303,6 +318,39 @@ class TestInstanceView(TestCase): updated = self.objects.get(id=1) self.assertEqual(updated.text, 'foobar') + def test_put_unprocessable_entity(self): + """ + PUT requests with wrong JSON data should raise HTTP 422 + (UNPROCESSABLE ENTITY) + """ + content = {'id': 999, 'wrongtext': 'foobar'} + request = factory.put('/1', json.dumps(content), + content_type='application/json') + with self.assertNumQueries(1): + response = self.view(request, pk=1).render() + self.assertEqual(response.status_code, + status.HTTP_422_UNPROCESSABLE_ENTITY) + self.assertIn('text', response.data) + self.assertEqual(response.data['text'], ['This field is required.']) + + def test_patch_unprocessable_entity(self): + """ + PATCH requests with wrong JSON data should raise HTTP 422 + (UNPROCESSABLE ENTITY) + """ + content = {'text': 'foobar' * 20} # too long + request = factory.patch('/1', json.dumps(content), + content_type='application/json') + + with self.assertNumQueries(1): + response = self.view(request, pk=1).render() + self.assertEqual(response.status_code, + status.HTTP_422_UNPROCESSABLE_ENTITY) + self.assertIn('text', response.data) + self.assertEqual( + response.data['text'], + [u'Ensure this value has at most 100 characters (it has 120).']) + def test_put_to_deleted_instance(self): """ PUT requests to RetrieveUpdateDestroyAPIView should create an object From 2701257db74ff1e6dc21ea56393d4f7eb101730f Mon Sep 17 00:00:00 2001 From: David Avsajanishvili Date: Mon, 3 Jun 2013 14:25:22 +0400 Subject: [PATCH 3/3] Update docs --- docs/api-guide/generic-views.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 20b9440ba..fcae707f4 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -182,7 +182,7 @@ Provides a `.create(request, *args, **kwargs)` method, that implements creating If an object is created this returns a `201 Created` response, with a serialized representation of the object as the body of the response. If the representation contains a key named `url`, then the `Location` header of the response will be populated with that value. -If the request data provided for creating the object was invalid, a `400 Bad Request` response will be returned, with the error details as the body of the response. +If the request data provided for creating the object was invalid, a `422 Unprocessable Entity` response will be returned, with the error details as the body of the response. ## RetrieveModelMixin @@ -200,7 +200,7 @@ If an object is updated this returns a `200 OK` response, with a serialized repr If an object is created, for example when making a `DELETE` request followed by a `PUT` request to the same URL, this returns a `201 Created` response, with a serialized representation of the object as the body of the response. -If the request data provided for updating the object was invalid, a `400 Bad Request` response will be returned, with the error details as the body of the response. +If the request data provided for updating the object was invalid, a `422 Unprocessable Entity` response will be returned, with the error details as the body of the response. ## DestroyModelMixin