Ensure that viewset actions may be identified as list views

is_list_view only checks to see whether the view action is 'list'.

This means you cannot define an action with `detail=False` that will
generate a schema with a list response.

This commit allows specifying 'many=True' on an action to achieve this
This commit is contained in:
Bill Collins 2022-04-06 08:05:03 +01:00
parent 070c32f4a6
commit ae5ba11e39
3 changed files with 21 additions and 1 deletions

View File

@ -15,7 +15,7 @@ def is_list_view(path, method, view):
""" """
if hasattr(view, 'action'): if hasattr(view, 'action'):
# Viewsets have an explicitly defined action, which we can inspect. # Viewsets have an explicitly defined action, which we can inspect.
return view.action == 'list' return view.action == 'list' or view.many
if method.lower() != 'get': if method.lower() != 'get':
return False return False

View File

@ -75,6 +75,9 @@ class ViewSetMixin:
# The detail initkwarg is reserved for introspecting the viewset type. # The detail initkwarg is reserved for introspecting the viewset type.
cls.detail = None cls.detail = None
# The many initkwarg is reserved for introspecting the viewset return type.
cls.many = None
# Setting a basename allows a view to reverse its action urls. This # Setting a basename allows a view to reverse its action urls. This
# value is provided by the router through the initkwargs. # value is provided by the router through the initkwargs.
cls.basename = None cls.basename = None

View File

@ -10,6 +10,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework import filters, generics, pagination, routers, serializers from rest_framework import filters, generics, pagination, routers, serializers
from rest_framework.authtoken.views import obtain_auth_token from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.compat import uritemplate from rest_framework.compat import uritemplate
from rest_framework.decorators import action
from rest_framework.parsers import JSONParser, MultiPartParser from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.renderers import ( from rest_framework.renderers import (
BaseRenderer, BrowsableAPIRenderer, JSONOpenAPIRenderer, JSONRenderer, BaseRenderer, BrowsableAPIRenderer, JSONOpenAPIRenderer, JSONRenderer,
@ -866,6 +867,22 @@ class TestOperationIntrospection(TestCase):
assert schema['paths']['/account/{id}/']['patch']['operationId'] == 'partialUpdateExampleViewSet' assert schema['paths']['/account/{id}/']['patch']['operationId'] == 'partialUpdateExampleViewSet'
assert schema['paths']['/account/{id}/']['delete']['operationId'] == 'destroyExampleViewSet' assert schema['paths']['/account/{id}/']['delete']['operationId'] == 'destroyExampleViewSet'
def test_viewset_with_list_action(self):
router = routers.SimpleRouter()
class ViewSetWithAction(views.ExampleViewSet):
@action(detail=False, many=True)
def list_action(self, request, *args, **kwargs):
pass
router.register('account', ViewSetWithAction, basename="account")
urlpatterns = router.urls
generator = SchemaGenerator(patterns=urlpatterns)
request = create_request('/')
schema = generator.get_schema(request=request)
assert schema['paths']['/account/list_action/']['get']['responses']['200']['content']['application/json']['schema']['type'] == 'array'
def test_serializer_datefield(self): def test_serializer_datefield(self):
path = '/' path = '/'
method = 'GET' method = 'GET'