diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 6fdb77c86..150058c1a 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -447,6 +447,12 @@ An integer of 0 or more, that may be used to specify the number of application p Default: `None` +#### SUPPORT_PATCH + +If set to `False` then HTTP PATCH requests will not be allowed. + +Default: `True` + [cite]: http://www.python.org/dev/peps/pep-0020/ [rfc4627]: http://www.ietf.org/rfc/rfc4627.txt [heroku-minified-json]: https://github.com/interagent/http-api-design#keep-json-minified-in-all-responses diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 1104aa29c..c338efa4f 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -6,7 +6,7 @@ which allows mixin classes to be composed in interesting ways. """ from __future__ import unicode_literals -from rest_framework import status +from rest_framework import exceptions, status from rest_framework.response import Response from rest_framework.settings import api_settings @@ -74,6 +74,9 @@ class UpdateModelMixin(object): serializer.save() def partial_update(self, request, *args, **kwargs): + if not api_settings.SUPPORT_PATCH: + raise exceptions.MethodNotAllowed(request.method) + kwargs['partial'] = True return self.update(request, *args, **kwargs) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index e20e51287..df7785546 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -54,6 +54,7 @@ DEFAULTS = { # Generic view behavior 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'DEFAULT_FILTER_BACKENDS': (), + 'SUPPORT_PATCH': True, # Throttling 'DEFAULT_THROTTLE_RATES': { diff --git a/rest_framework/views.py b/rest_framework/views.py index a709c2f6b..102a8479e 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -129,7 +129,12 @@ class APIView(View): """ Wrap Django's private `_allowed_methods` interface in a public property. """ - return self._allowed_methods() + allowed_methods = self._allowed_methods() + + if not api_settings.SUPPORT_PATCH and 'PATCH' in allowed_methods: + allowed_methods.remove('PATCH') + + return allowed_methods @property def default_response_headers(self): diff --git a/tests/test_generics.py b/tests/test_generics.py index 0647119bf..80a4ed66d 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -7,6 +7,7 @@ from django.test import TestCase from django.utils import six from rest_framework import generics, renderers, serializers, status +from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory from tests.models import ( BasicModel, ForeignKeySource, ForeignKeyTarget, RESTFrameworkModel @@ -507,3 +508,26 @@ class TestFilterBackendAppliedToViews(TestCase): response = view(request).render() self.assertContains(response, 'field_b') self.assertNotContains(response, 'field_a') + + +class TestSupportPatchSetting(TestCase): + def test_patch_instance_view_support_patch(self): + """ + PATCH requests should fail when SUPPORT_PATCH is set to False. + """ + obj = BasicModel.objects.create(text='abc') + + class InstanceView(generics.UpdateAPIView): + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer + + api_settings.SUPPORT_PATCH = False + + data = {'text': 'foobar'} + request = factory.patch('/1', data, format='json') + view = InstanceView.as_view() + response = view(request, pk=obj.pk).render() + + api_settings.SUPPORT_PATCH = True + + self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)