From 378745e79cea7bf644db9e01bf4348cbf6c52f4c Mon Sep 17 00:00:00 2001 From: Lucidiot Date: Fri, 29 Mar 2019 16:05:22 +0100 Subject: [PATCH] Add operationId on OpenAPI operations --- rest_framework/schemas/inspectors.py | 48 ++++++++++++++++++++++++++++ tests/schemas/test_openapi.py | 1 + 2 files changed, 49 insertions(+) diff --git a/rest_framework/schemas/inspectors.py b/rest_framework/schemas/inspectors.py index f96be7c8a..a3381a09c 100644 --- a/rest_framework/schemas/inspectors.py +++ b/rest_framework/schemas/inspectors.py @@ -511,10 +511,19 @@ class DefaultSchema(ViewInspector): class OpenAPIAutoSchema(ViewInspector): content_types = ['application/json'] + method_mapping = { + 'get': 'Retrieve', + 'post': 'Create', + 'put': 'Update', + 'patch': 'PartialUpdate', + 'delete': 'Destroy', + } def get_operation(self, path, method): operation = {} + operation['operationId'] = self._get_operation_id(path, method) + parameters = [] parameters += self._get_path_parameters(path, method) parameters += self._get_pagination_parameters(path, method) @@ -528,6 +537,45 @@ class OpenAPIAutoSchema(ViewInspector): return operation + def _get_operation_id(self, path, method): + """ + Compute an operation ID from the model, serializer or view name. + """ + # TODO: Allow an attribute/method on the view to change that ID? + # Avoid cyclic imports + from rest_framework.generics import GenericAPIView + + if is_list_view(path, method, self.view): + action = 'List' + 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: + name = model.__name__ + + # Try with the serializer class name + elif isinstance(self.view, GenericAPIView): + name = self.view.get_serializer_class().__name__ + if name.endswith('Serializer'): + name = name[:-10] + + # Fallback to the view name + else: + name = self.view.__class__.__name__ + if name.endswith('APIView'): + name = name[:-7] + elif name.endswith('View'): + name = name[:-4] + if name.endswith(action): # ListView, UpdateAPIView, ThingDelete ... + name = name[:-len(action)] + + if action == 'List' and not name.endswith('s'): # ListThings instead of ListThing + name += 's' + + return action + name + def _get_path_parameters(self, path, method): """ Return a list of parameters from templated path variables. diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 3e9dd0322..5eede864b 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -57,6 +57,7 @@ class TestOperationIntrospection(TestCase): operation = inspector.get_operation(path, method) assert operation == { + 'operationId': 'ListExamples', 'parameters': [], 'responses': {'200': {'content': {'application/json': {}}}}, }