diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index e2d6b2060..4df135a9f 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -223,46 +223,32 @@ Tags can be used to group logical operations. Each tag name in the list MUST be --- #### Django REST Framework generates tags automatically with the following logic: -1. Extract tag from `ViewSet`. - 1. If `ViewSet` name ends with `ViewSet`, or `View`; remove it. - 2. Convert class name into lowercase words & join each word using a `-`(dash). - - Examples: - - ViewSet Class | Tags - ----------------|------------ - User | ['user'] - UserView | ['user'] - UserViewSet | ['user'] - PascalCaseXYZ | ['pascal-case-xyz'] - IPAddressView | ['ip-address'] - -2. If View is not an instance of ViewSet, tag name will be first element from the path. Also, any `_` in path name will be replaced by a `-`. +Tag name will be first element from the path. Also, any `_` in path name will be replaced by a `-`. 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` +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'] - - 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. - Here first element from the paths is: `restaurants`. Hence tag wil be `restaurants`. - - Http Method | Path | Tags - -------------------------------------|----------------------------------------------------|------------------- - 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'] +Http Method | Path | Tags +-------------------------------------|-------------------|------------- +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. +Here first element from the paths is: `restaurants`. Hence tag wil be `restaurants`. + +Http Method | Path | Tags +-------------------------------------|----------------------------------------------------|------------------- +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'] --- diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 5f6e37b36..5277f17a6 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -15,7 +15,6 @@ 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 @@ -578,18 +577,6 @@ class AutoSchema(ViewInspector): if self._tags: return self._tags - # Extract tag from viewset name - # UserProfileViewSet tags = [user-profile] - # UserProfileView tags = [user-profile] - # UserProfile tags = [user-profile] - if hasattr(self.view, 'action'): - name = self.view.__class__.__name__ - if name.endswith('ViewSet'): - name = name[:-7] - elif name.endswith('View'): - name = name[:-4] - return [camelcase_to_spaces(name).lower().replace(' ', '-')] - # First element of a specific path could be valid tag. This is a fallback solution. # PUT, PATCH, GET(Retrieve), DELETE: /user_profile/{id}/ tags = [user-profile] # POST, GET(List): /user_profile/ tags = [user-profile] diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index c5322cdf5..7f73c8c30 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -699,14 +699,15 @@ class TestOperationIntrospection(TestCase): assert 'format' not in properties['ip'] def test_overridden_tags(self): - class ExampleStringTagsViewSet(views.ExampleTagsViewSet): + class ExampleStringTagsViewSet(views.ExampleGenericAPIView): schema = AutoSchema(tags=['example1', 'example2']) - router = routers.SimpleRouter() - router.register('test', ExampleStringTagsViewSet, basename="test") - generator = SchemaGenerator(patterns=router.urls) + url_patterns = [ + url(r'^test/?$', ExampleStringTagsViewSet.as_view()), + ] + generator = SchemaGenerator(patterns=url_patterns) schema = generator.get_schema(request=create_request('/')) - assert schema['paths']['/test/{id}/']['get']['tags'] == ['example1', 'example2'] + assert schema['paths']['/test/']['get']['tags'] == ['example1', 'example2'] def test_overridden_get_tags_method(self): class MySchema(AutoSchema): @@ -730,32 +731,6 @@ class TestOperationIntrospection(TestCase): assert schema['paths']['/example/new/']['get']['tags'] == ['tag1', 'tag2'] assert schema['paths']['/example/old/']['get']['tags'] == ['tag2', 'tag3'] - def test_auto_generated_viewset_tags(self): - class ExampleIPViewSet(views.ExampleTagsViewSet): - pass - - class ExampleXYZView(views.ExampleTagsViewSet): - pass - - class Example(views.ExampleTagsViewSet): - pass - - class PascalCaseXYZTestIp(views.ExampleTagsViewSet): - pass - - router = routers.SimpleRouter() - router.register('test1', ExampleIPViewSet, basename="test1") - router.register('test2', ExampleXYZView, basename="test2") - router.register('test3', Example, basename="test3") - router.register('test4', PascalCaseXYZTestIp, basename="test4") - - generator = SchemaGenerator(patterns=router.urls) - schema = generator.get_schema(request=create_request('/')) - 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['paths']['/test4/{id}/']['get']['tags'] == ['pascal-case-xyz-test-ip'] - def test_auto_generated_apiview_tags(self): class RestaurantAPIView(views.ExampleGenericAPIView): pass diff --git a/tests/schemas/views.py b/tests/schemas/views.py index ba326ad14..e8307ccbd 100644 --- a/tests/schemas/views.py +++ b/tests/schemas/views.py @@ -137,14 +137,3 @@ class ExampleValidatedAPIView(generics.GenericAPIView): url='http://localhost', uuid=uuid.uuid4(), ip4='127.0.0.1', ip6='::1', ip='192.168.1.1') return Response(serializer.data) - - -class ExampleTagsViewSet(GenericViewSet): - serializer_class = ExampleSerializer - - def retrieve(self, request, *args, **kwargs): - serializer = self.get_serializer(integer=33, string='hello', regex='foo', decimal1=3.55, - decimal2=5.33, email='a@b.co', - url='http://localhost', uuid=uuid.uuid4(), ip4='127.0.0.1', ip6='::1', - ip='192.168.1.1') - return Response(serializer.data)