diff --git a/AUTHORS b/AUTHORS index 452317e2..4c63249d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ Raphael Cohen Roberto Barreda Rohith PR santiavenda +Sergey Kolomenkin Tim Selman Yaniv Peer Mohammed Ali Zubair diff --git a/CHANGELOG.md b/CHANGELOG.md index 95d7905b..2efa0f40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ any parts of the framework not mentioned in the documentation should generally b * Avoid `AttributeError` for PUT and PATCH methods when using `APIView` * Clear many-to-many relationships instead of deleting related objects during PATCH on `RelationshipView` +* Allow POST, PATCH, DELETE for actions in `ReadOnlyModelViewSet`. It was problematic since 2.8.0. ### Changed diff --git a/example/tests/test_views.py b/example/tests/test_views.py index e18511fa..1f47245b 100644 --- a/example/tests/test_views.py +++ b/example/tests/test_views.py @@ -3,11 +3,15 @@ from django.test import RequestFactory from django.utils import timezone +from rest_framework import status +from rest_framework.decorators import action from rest_framework.exceptions import NotFound from rest_framework.request import Request +from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.test import APIRequestFactory, APITestCase, force_authenticate +from rest_framework_json_api import serializers, views from rest_framework_json_api.utils import format_resource_type from example.factories import AuthorFactory, CommentFactory, EntryFactory @@ -634,3 +638,79 @@ def test_get_object_gives_correct_entry(self): } got = resp.json() self.assertEqual(got, expected) + + +class BasicAuthorSerializer(serializers.ModelSerializer): + class Meta: + model = Author + fields = ('name',) + + +class ReadOnlyViewSetWithCustomActions(views.ReadOnlyModelViewSet): + queryset = Author.objects.all() + serializer_class = BasicAuthorSerializer + + @action(detail=False, methods=['get', 'post', 'patch', 'delete']) + def group_action(self, request): + return Response(status=status.HTTP_204_NO_CONTENT) + + @action(detail=True, methods=['get', 'post', 'patch', 'delete']) + def item_action(self, request, pk): + return Response(status=status.HTTP_204_NO_CONTENT) + + +class TestReadonlyModelViewSet(TestBase): + """ + Test if ReadOnlyModelViewSet allows to have custom actions with POST, PATCH, DELETE methods + """ + factory = RequestFactory() + viewset_class = ReadOnlyViewSetWithCustomActions + media_type = 'application/vnd.api+json' + + def test_group_action_allows_get(self): + view = self.viewset_class.as_view({'get': 'group_action'}) + request = self.factory.get('/') + response = view(request) + self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) + + def test_group_action_allows_post(self): + view = self.viewset_class.as_view({'post': 'group_action'}) + request = self.factory.post('/', '{}', content_type=self.media_type) + response = view(request) + self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) + + def test_group_action_allows_patch(self): + view = self.viewset_class.as_view({'patch': 'group_action'}) + request = self.factory.patch('/', '{}', content_type=self.media_type) + response = view(request) + self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) + + def test_group_action_allows_delete(self): + view = self.viewset_class.as_view({'delete': 'group_action'}) + request = self.factory.delete('/', '{}', content_type=self.media_type) + response = view(request) + self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) + + def test_item_action_allows_get(self): + view = self.viewset_class.as_view({'get': 'item_action'}) + request = self.factory.get('/') + response = view(request, pk='1') + self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) + + def test_item_action_allows_post(self): + view = self.viewset_class.as_view({'post': 'item_action'}) + request = self.factory.post('/', '{}', content_type=self.media_type) + response = view(request, pk='1') + self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) + + def test_item_action_allows_patch(self): + view = self.viewset_class.as_view({'patch': 'item_action'}) + request = self.factory.patch('/', '{}', content_type=self.media_type) + response = view(request, pk='1') + self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) + + def test_item_action_allows_delete(self): + view = self.viewset_class.as_view({'delete': 'item_action'}) + request = self.factory.delete('/', '{}', content_type=self.media_type) + response = view(request, pk='1') + self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index ba919f32..2e0b22ef 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -210,7 +210,7 @@ class ModelViewSet(AutoPrefetchMixin, class ReadOnlyModelViewSet(AutoPrefetchMixin, RelatedMixin, viewsets.ReadOnlyModelViewSet): - http_method_names = ['get', 'head', 'options'] + http_method_names = ['get', 'post', 'patch', 'delete', 'head', 'options'] class RelationshipView(generics.GenericAPIView):