From 2357148b124fbf99cc25f74c141ac69c4a54b352 Mon Sep 17 00:00:00 2001 From: Martin Desrumaux Date: Fri, 28 Feb 2020 18:04:45 +0100 Subject: [PATCH] allow custom name for operation_id --- docs/api-guide/schemas.md | 17 +++++++++++++++++ rest_framework/schemas/openapi.py | 15 +++++++++++++-- tests/schemas/test_openapi.py | 15 +++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 2e5ffc79b..7f802d310 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -288,8 +288,25 @@ class MyView(APIView): ... ``` +### OperationId + +The schema generator generates an [operationid](openapi-operationid) for each operation. This `operationId` is deduced from the model name, serializer name or view name. The operationId may looks like "ListItems", "RetrieveItem", "UpdateItem", etc.. + +If you have several views with the same model, the generator may generate duplicate operationId. +In order to work around this, you can override the second part of the operationId: operation name. + +```python +from rest_framework.schemas.openapi import AutoSchema + +class ExampleView(APIView): + """APIView subclass with custom schema introspection.""" + schema = AutoSchema(operation_name="Custom") +``` + +The previous example will generate the following operationid: "ListCustoms", "RetrieveCustom", "UpdateCustom", "PartialUpdateCustom", `DestroyCustom`. [openapi]: https://github.com/OAI/OpenAPI-Specification [openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions [openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject [openapi-tags]: https://swagger.io/specification/#tagObject +[openapi-operationid]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#fixed-fields-17 diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 5277f17a6..1680dcfe9 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -88,6 +88,13 @@ class AutoSchema(ViewInspector): 'delete': 'Destroy', } + def __init__(self, operation_name=None): + """ + :param operation_name: user-defined name in operationId. If empty, it will be deducted from the Model/Serializer/View name. + """ + super().__init__() + self.operation_name = operation_name + def get_operation(self, path, method): operation = {} @@ -120,9 +127,13 @@ class AutoSchema(ViewInspector): else: action = self.method_mapping[method.lower()] - # Try to deduce the ID from the view's model model = getattr(getattr(self.view, 'queryset', None), 'model', None) - if model is not None: + + if self.operation_name is not None: + name = self.operation_name + + # Try to deduce the ID from the view's model + elif model is not None: name = model.__name__ # Try with the serializer class name diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 7f73c8c30..c5f9d32a4 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -577,6 +577,21 @@ class TestOperationIntrospection(TestCase): operationId = inspector._get_operation_id(path, method) assert operationId == 'listExamples' + def test_operation_id_custom_name(self): + path = '/' + method = 'GET' + + view = create_view( + views.ExampleGenericAPIView, + method, + create_request(path), + ) + inspector = AutoSchema(operation_name="Ulysse") + inspector.view = view + + operationId = inspector._get_operation_id(path, method) + assert operationId == 'listUlysses' + def test_repeat_operation_ids(self): router = routers.SimpleRouter() router.register('account', views.ExampleGenericViewSet, basename="account")