diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index 5c5872650d..19f2f96aa8 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -19,6 +19,23 @@ Two popular options are [Swagger UI][swagger-ui] and [ReDoc][redoc]. Both require little more than the location of your static schema file or dynamic `SchemaView` endpoint. +openapi allows you to group endpoints by tags, that's you can configure these tags +on your view by adding `openapi_tags` as property, by default will set ['default']. + +```python +from rest_framework.views import APIView +from rest_framework.permissions import AllowAny +from rest_framework.response import Response + +class HelloWorldApiView(APIView): + permission_classes = [AllowAny] + openapi_tags = ['example', 'hello-world'] + + def get(self, request): + return Response(data={'message': 'hello world!'}) + +``` + ### A minimal example with Swagger UI Assuming you've followed the example from the schemas documentation for routing diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 9c6610eafa..03a6f748b9 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -87,6 +87,7 @@ def get_operation(self, path, method): operation['operationId'] = self._get_operation_id(path, method) operation['description'] = self.get_description(path, method) + operation['tags'] = self._get_tags(path, method) parameters = [] parameters += self._get_path_parameters(path, method) @@ -211,6 +212,24 @@ def _get_pagination_parameters(self, path, method): return paginator.get_schema_operation_parameters(view) + def _get_tags(self, path, method): + """ + Get tags parameters from view + + Default value to return it will be tuple with default tag + In other hand it will try to get it from openapi_tags of view + """ + + tags = ['default'] + + if hasattr(self.view, 'openapi_tags'): + tags = getattr(self.view, 'openapi_tags', tags) + + assert type(tags) == list, 'openapi_tags property is not a list' + assert len(tags) > 0, 'openapi_tags property should has almost one tag' + + return tags + def _map_choicefield(self, field): choices = list(OrderedDict.fromkeys(field.choices)) # preserve order and remove duplicates if all(isinstance(choice, bool) for choice in choices): diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index cfa2e89ef8..da01bd9259 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -125,6 +125,7 @@ def test_path_without_parameters(self): assert operation == { 'operationId': 'listDocStringExamples', 'description': 'A description of my GET operation.', + 'tags': ['default'], 'parameters': [], 'responses': { '200': { @@ -157,6 +158,47 @@ def test_path_with_id_parameter(self): assert operation == { 'operationId': 'RetrieveDocStringExampleDetail', 'description': 'A description of my GET operation.', + 'tags': ['default'], + 'parameters': [{ + 'description': '', + 'in': 'path', + 'name': 'id', + 'required': True, + 'schema': { + 'type': 'string', + }, + }], + 'responses': { + '200': { + 'description': '', + 'content': { + 'application/json': { + 'schema': { + }, + }, + }, + }, + }, + } + + def test_modify_openapi_tags(self): + path = '/example/{id}/' + method = 'GET' + + view = create_view( + views.DocStringExampleDetailWithTagsView, + method, + create_request(path) + ) + inspector = AutoSchema() + inspector.view = view + + operation = inspector.get_operation(path, method) + + assert operation == { + 'operationId': 'RetrieveDocStringExampleDetailWithTags', + 'description': 'A description of my GET operation.', + 'tags': ['DocString'], 'parameters': [{ 'description': '', 'in': 'path', diff --git a/tests/schemas/views.py b/tests/schemas/views.py index e8307ccbd0..ec08eecc64 100644 --- a/tests/schemas/views.py +++ b/tests/schemas/views.py @@ -53,6 +53,17 @@ def get(self, *args, **kwargs): pass +class DocStringExampleDetailWithTagsView(APIView): + permission_classes = [permissions.IsAuthenticatedOrReadOnly] + openapi_tags = ['DocString'] + + def get(self, *args, **kwargs): + """ + A description of my GET operation. + """ + pass + + # Generics. class ExampleSerializer(serializers.Serializer): date = serializers.DateField()