Skip to content

Commit aa12b49

Browse files
committed
Allow POST, PATCH, DELETE for actions in ReadOnlyModelViewSet (#797)
Currently if you try to use `POST` for action in `ReadOnlyModelViewSet` you will get problems: - with DRF UI you will loose data input forms - `drf_yasg` package will not generate OpenAPI specifications for such actions This behavior was added to `django-rest-framework-json-api` in version 2.8.0 by mistake: Commit: 7abd764 Subject: remove disallowed PUT method. (#643)
1 parent 073c45b commit aa12b49

File tree

4 files changed

+71
-1
lines changed

4 files changed

+71
-1
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Raphael Cohen <[email protected]>
1919
Roberto Barreda <[email protected]>
2020
Rohith PR <[email protected]>
2121
santiavenda <[email protected]>
22+
Sergey Kolomenkin <https://kolomenkin.com>
2223
Tim Selman <[email protected]>
2324
Yaniv Peer <[email protected]>
2425
Mohammed Ali Zubair <[email protected]>

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ any parts of the framework not mentioned in the documentation should generally b
1818

1919
* Avoid `AttributeError` for PUT and PATCH methods when using `APIView`
2020
* Clear many-to-many relationships instead of deleting related objects during PATCH on `RelationshipView`
21+
* Allow POST, PATCH, DELETE for actions in `ReadOnlyModelViewSet`. It was problematic since 2.8.0.
2122

2223
### Changed
2324

example/tests/test_views.py

+68
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33

44
from django.test import RequestFactory
55
from django.utils import timezone
6+
from rest_framework import status
7+
from rest_framework.decorators import action
68
from rest_framework.exceptions import NotFound
79
from rest_framework.request import Request
10+
from rest_framework.response import Response
811
from rest_framework.reverse import reverse
912
from rest_framework.test import APIRequestFactory, APITestCase, force_authenticate
1013

14+
from rest_framework_json_api import serializers, views
1115
from rest_framework_json_api.utils import format_resource_type
1216

1317
from example.factories import AuthorFactory, CommentFactory, EntryFactory
@@ -634,3 +638,67 @@ def test_get_object_gives_correct_entry(self):
634638
}
635639
got = resp.json()
636640
self.assertEqual(got, expected)
641+
642+
643+
class BasicAuthorSerializer(serializers.ModelSerializer):
644+
class Meta:
645+
model = Author
646+
fields = ('name',)
647+
648+
649+
class ReadOnlyViewSetWithCustomActions(views.ReadOnlyModelViewSet):
650+
queryset = Author.objects.all()
651+
serializer_class = BasicAuthorSerializer
652+
653+
@action(detail=False, methods=['post', 'patch', 'delete'])
654+
def group_action(self, request):
655+
return Response(status=status.HTTP_204_NO_CONTENT)
656+
657+
@action(detail=True, methods=['post', 'patch', 'delete'])
658+
def item_action(self, request, pk):
659+
return Response(status=status.HTTP_204_NO_CONTENT)
660+
661+
662+
class TestReadonlyModelViewSet(TestBase):
663+
"""
664+
Test if ReadOnlyModelViewSet allows to have custom actions with POST, PATCH, DELETE methods
665+
"""
666+
factory = RequestFactory()
667+
viewset_class = ReadOnlyViewSetWithCustomActions
668+
media_type = 'application/vnd.api+json'
669+
670+
def test_group_action_allows_post(self):
671+
view = self.viewset_class.as_view({'post': 'group_action'})
672+
request = self.factory.post('/', '{}', content_type=self.media_type)
673+
response = view(request)
674+
self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
675+
676+
def test_group_action_allows_patch(self):
677+
view = self.viewset_class.as_view({'patch': 'group_action'})
678+
request = self.factory.patch('/', '{}', content_type=self.media_type)
679+
response = view(request)
680+
self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
681+
682+
def test_group_action_allows_delete(self):
683+
view = self.viewset_class.as_view({'delete': 'group_action'})
684+
request = self.factory.delete('/', '{}', content_type=self.media_type)
685+
response = view(request)
686+
self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
687+
688+
def test_item_action_allows_post(self):
689+
view = self.viewset_class.as_view({'post': 'item_action'})
690+
request = self.factory.post('/', '{}', content_type=self.media_type)
691+
response = view(request, pk='1')
692+
self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
693+
694+
def test_item_action_allows_patch(self):
695+
view = self.viewset_class.as_view({'patch': 'item_action'})
696+
request = self.factory.patch('/', '{}', content_type=self.media_type)
697+
response = view(request, pk='1')
698+
self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
699+
700+
def test_item_action_allows_delete(self):
701+
view = self.viewset_class.as_view({'delete': 'item_action'})
702+
request = self.factory.delete('/', '{}', content_type=self.media_type)
703+
response = view(request, pk='1')
704+
self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)

rest_framework_json_api/views.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ class ModelViewSet(AutoPrefetchMixin,
210210
class ReadOnlyModelViewSet(AutoPrefetchMixin,
211211
RelatedMixin,
212212
viewsets.ReadOnlyModelViewSet):
213-
http_method_names = ['get', 'head', 'options']
213+
http_method_names = ['get', 'post', 'patch', 'delete', 'head', 'options']
214214

215215

216216
class RelationshipView(generics.GenericAPIView):

0 commit comments

Comments
 (0)