Skip to content

Allow POST, PATCH, DELETE for custom actions in ReadOnlyModelViewSet #797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Raphael Cohen <[email protected]>
Roberto Barreda <[email protected]>
Rohith PR <[email protected]>
santiavenda <[email protected]>
Sergey Kolomenkin <https://kolomenkin.com>
Tim Selman <[email protected]>
Yaniv Peer <[email protected]>
Mohammed Ali Zubair <[email protected]>
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
80 changes: 80 additions & 0 deletions example/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
2 changes: 1 addition & 1 deletion rest_framework_json_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down