From b0f11cdd294266cdc2bc3a1151cf48c398aab11f Mon Sep 17 00:00:00 2001 From: Dhaval Mehta <20968146+dhaval-mehta@users.noreply.github.com> Date: Thu, 13 Feb 2020 00:09:19 +0530 Subject: [PATCH] fix changes given by kevin-brown --- docs/api-guide/schemas.md | 43 +++++++++++++++++++++---------- rest_framework/schemas/openapi.py | 5 ++-- tests/schemas/test_openapi.py | 18 ++++++------- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index bf19c5425..be2f700da 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -222,23 +222,30 @@ Tags can be used for logical grouping operations. Each tag name in the list MUST --- **Django REST Framework generates tags automatically with following logic:** -1. Extract tag name from `ViewSet`. If `ViewSet` name ends with `ViewSet`, or `View`, it will be truncated from tag name. +1. Extract tag from `ViewSet`. + 1. If `ViewSet` name ends with `ViewSet`, or `View`, remove them. + 2. Convert class name into words & join each word with a space. + + Examples: + + ViewSet Class | Tags + ----------------|------------ + User | ['user'] + UserView | ['user'] + UserViewSet | ['user'] + PascalCaseXYZ | ['pascal case xyz'] + IPAddressView | ['ip address'] - ViewSet Class | Tags - ----------------|------------ - User | User - UserView | User - UserViewSet | user - -2. If View is not an instance of ViewSet, tag name will be first element from the path. Consider below examples. +2. If View is not an instance of ViewSet, tag name will be first element from the path. Also, any `-` or `_` in path name will be converted as a space. +Consider below examples. Example 1: Consider a user management system. The following table will illustrate the tag generation logic. Here first element from the paths is: `users`. Hence tag wil be `users` Http Method | Path | Tags -------------------------------------|-------------------|------------- - PUT, PATCH, GET(Retrieve), DELETE | /users/{id}/ | [users] - POST, GET(List) | /users/ | [users] + PUT, PATCH, GET(Retrieve), DELETE | /users/{id}/ | ['users'] + POST, GET(List) | /users/ | ['users'] Example 2: Consider a restaurant management system. The System has restaurants. Each restaurant has branches. Consider REST APIs to deal with a branch of a particular restaurant. @@ -246,11 +253,21 @@ Tags can be used for logical grouping operations. Each tag name in the list MUST Http Method | Path | Tags -------------------------------------|----------------------------------------------------|------------------- - PUT, PATCH, GET(Retrieve), DELETE: | /restaurants/{restaurant_id}/branches/{branch_id} | [restaurants] - POST, GET(List): | /restaurants/{restaurant_id}/branches/ | [restaurants] + PUT, PATCH, GET(Retrieve), DELETE: | /restaurants/{restaurant_id}/branches/{branch_id} | ['restaurants'] + POST, GET(List): | /restaurants/{restaurant_id}/branches/ | ['restaurants'] + + Example 3: Consider Order items for an e commerce company. + + Http Method | Path | Tags + -------------------------------------|-------------------------|------------- + PUT, PATCH, GET(Retrieve), DELETE | /order-items/{id}/ | ['order items'] + POST, GET(List) | /order-items/ | ['order items'] + --- -**You can override auto-generated tags by passing a list of tags to each `View` by passing `tags` as an argument to the constructor of `AutoSchema`. `tags` argument can be:** +**You can override auto-generated tags by passing `tags` argument to the constructor of `AutoSchema`.** + +**`tags` argument can be a** 1. list of string. ```python class MyView(APIView): diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 6abaf1b5d..a577fe26c 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -15,6 +15,7 @@ from rest_framework import exceptions, renderers, serializers from rest_framework.compat import uritemplate from rest_framework.fields import _UnvalidatedField, empty +from ..utils.formatting import camelcase_to_spaces from .generators import BaseSchemaGenerator from .inspectors import ViewInspector from .utils import get_pk_description, is_list_view @@ -597,7 +598,7 @@ class AutoSchema(ViewInspector): name = name[:-7] elif name.endswith('View'): name = name[:-4] - return [name] + return [camelcase_to_spaces(name).lower()] # First element of a specific path could be valid tag. This is a fallback solution. # PUT, PATCH, GET(Retrieve), DELETE: /users/{id}/ tags = [users] @@ -605,4 +606,4 @@ class AutoSchema(ViewInspector): if path.startswith('/'): path = path[1:] - return [path.split('/')[0]] + return [path.split('/')[0].translate(str.maketrans({'-': ' ', '_': ' '}))] diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index cd38e4a63..f9184d5cf 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -776,25 +776,25 @@ class TestOperationIntrospection(TestCase): ] def test_auto_generated_viewset_tags(self): - class ExampleViewSet(views.ExampleTagsViewSet): + class ExampleIPViewSet(views.ExampleTagsViewSet): pass - class ExampleView(views.ExampleTagsViewSet): + class ExampleXYZView(views.ExampleTagsViewSet): pass class Example(views.ExampleTagsViewSet): pass router = routers.SimpleRouter() - router.register('test1', ExampleViewSet, basename="test") - router.register('test2', ExampleView, basename="test") + router.register('test1', ExampleIPViewSet, basename="test") + router.register('test2', ExampleXYZView, basename="test") router.register('test3', Example, basename="test") generator = SchemaGenerator(patterns=router.urls) schema = generator.get_schema(request=create_request('/')) - assert schema['paths']['/test1/{id}/']['get']['tags'] == ['Example'] - assert schema['paths']['/test2/{id}/']['get']['tags'] == ['Example'] - assert schema['paths']['/test3/{id}/']['get']['tags'] == ['Example'] + assert schema['paths']['/test1/{id}/']['get']['tags'] == ['example ip'] + assert schema['paths']['/test2/{id}/']['get']['tags'] == ['example xyz'] + assert schema['paths']['/test3/{id}/']['get']['tags'] == ['example'] assert schema['tags'] == [] def test_auto_generated_apiview_tags(self): @@ -805,12 +805,12 @@ class TestOperationIntrospection(TestCase): pass url_patterns = [ - url(r'^restaurants/?$', RestaurantAPIView.as_view()), + url(r'^any-dash_underscore/?$', RestaurantAPIView.as_view()), url(r'^restaurants/branches/?$', BranchAPIView.as_view()) ] generator = SchemaGenerator(patterns=url_patterns) schema = generator.get_schema(request=create_request('/')) - assert schema['paths']['/restaurants/']['get']['tags'] == ['restaurants'] + assert schema['paths']['/any-dash_underscore/']['get']['tags'] == ['any dash underscore'] assert schema['paths']['/restaurants/branches/']['get']['tags'] == ['restaurants'] assert schema['tags'] == []