Skip to content

Commit

Permalink
Make swagger_auto_schema work with action mappings
Browse files Browse the repository at this point in the history
Fixes #177.
  • Loading branch information
axnsan12 committed Aug 7, 2018
1 parent 4c06913 commit 65aac1d
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 10 deletions.
23 changes: 13 additions & 10 deletions src/drf_yasg/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,40 +117,43 @@ def decorator(view_method):
# no overrides to set, no use in doing more work
return

# if the method is an @action, it will have a bind_to_methods attribute, or a mapper attribute for drf>3.8
# if the method is an @action, it will have a bind_to_methods attribute, or a mapping attribute for drf>3.8
bind_to_methods = getattr(view_method, 'bind_to_methods', [])
mapping = getattr(view_method, 'mapping', {})
mapping_methods = [mth for mth, name in mapping.items() if name == view_method.__name__]
action_http_methods = bind_to_methods + mapping_methods

# if the method is actually a function based view (@api_view), it will have a 'cls' attribute
view_cls = getattr(view_method, 'cls', None)
http_method_names = [m for m in getattr(view_cls, 'http_method_names', []) if hasattr(view_cls, m)]
api_view_http_methods = [m for m in getattr(view_cls, 'http_method_names', []) if hasattr(view_cls, m)]

available_methods = http_method_names + bind_to_methods
available_http_methods = api_view_http_methods + action_http_methods
existing_data = getattr(view_method, '_swagger_auto_schema', {})

_methods = methods
if methods or method:
assert available_methods or http_method_names, "`method` or `methods` can only be specified " \
"on @action or @api_view views"
assert available_http_methods, "`method` or `methods` can only be specified on @action or @api_view views"
assert bool(methods) != bool(method), "specify either method or methods"
assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \
" use `method` for a single argument"
if method:
_methods = [method.lower()]
else:
_methods = [mth.lower() for mth in methods]
assert all(mth in available_methods for mth in _methods), "http method not bound to view"
assert all(mth in available_http_methods for mth in _methods), "http method not bound to view"
assert not any(mth in existing_data for mth in _methods), "http method defined multiple times"

if available_methods:
if available_http_methods:
# action or api_view
assert bool(http_method_names) != bool(bind_to_methods), "this should never happen"
assert bool(api_view_http_methods) != bool(action_http_methods), "this should never happen"

if len(available_methods) > 1:
if len(available_http_methods) > 1:
assert _methods, \
"on multi-method api_view, action, detail_route or list_route, you must specify " \
"swagger_auto_schema on a per-method basis using one of the `method` or `methods` arguments"
else:
# for a single-method view we assume that single method as the decorator target
_methods = _methods or available_methods
_methods = _methods or available_http_methods

assert not any(hasattr(getattr(view_cls, mth, None), '_swagger_auto_schema') for mth in _methods), \
"swagger_auto_schema applied twice to method"
Expand Down
43 changes: 43 additions & 0 deletions tests/test_schema_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,46 @@ def test_view(request, pk=None):

# get_endpoints only includes one endpoint
assert len(generator.get_endpoints(None)['/test/'][1]) == 1


try:
from rest_framework.decorators import action, MethodMapper
except ImportError:
action = MethodMapper = None


@pytest.mark.skipif(not MethodMapper or not action, reason="action.mapping test (djangorestframework>=3.9 required)")
def test_action_mapping():
class ActionViewSet(viewsets.ViewSet):
@swagger_auto_schema(method='get', operation_id='mapping_get')
@swagger_auto_schema(method='delete', operation_id='mapping_delete')
@action(detail=False, methods=['get', 'delete'], url_path='test')
def action_main(self, request):
"""mapping docstring get/delete"""
pass

@swagger_auto_schema(operation_id='mapping_post')
@action_main.mapping.post
def action_post(self, request):
"""mapping docstring post"""
pass

router = routers.DefaultRouter()
router.register(r'action', ActionViewSet, base_name='action')

generator = OpenAPISchemaGenerator(
info=openapi.Info(title="Test generator", default_version="v1"),
version="v2",
url='',
patterns=router.urls
)

for _ in range(3):
swagger = generator.get_schema(None, True)
action_ops = swagger['paths']['/test/']
methods = ['get', 'post', 'delete']
assert all(mth in action_ops for mth in methods)
assert all(action_ops[mth]['operationId'] == 'mapping_' + mth for mth in methods)
assert action_ops['post']['description'] == 'mapping docstring post'
assert action_ops['get']['description'] == 'mapping docstring get/delete'
assert action_ops['delete']['description'] == 'mapping docstring get/delete'

0 comments on commit 65aac1d

Please sign in to comment.