OpenAPI: Make operationId camelCase, matching spec examples. (#7208)

This commit is contained in:
Martin Desrumaux 2020-03-03 17:51:51 +01:00 committed by GitHub
parent 609f708a27
commit 6a23fa0649
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 10 deletions

View File

@ -290,7 +290,8 @@ 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..
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..
The `operationId` is camelCase by convention.
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.
@ -303,7 +304,7 @@ class ExampleView(APIView):
schema = AutoSchema(operation_id_base="Custom")
```
The previous example will generate the following operationId: "ListCustoms", "RetrieveCustom", "UpdateCustom", "PartialUpdateCustom", "DestroyCustom".
The previous example will generate the following operationId: "listCustoms", "retrieveCustom", "updateCustom", "partialUpdateCustom", "destroyCustom".
You need to provide the singular form of he operation name. For the list operation, a "s" will be appended at the end of the operation.
If you need more configuration over the `operationId` field, you can override the `get_operation_id_base` and `get_operation_id` methods from the `AutoSchema` class:

View File

@ -131,11 +131,11 @@ class AutoSchema(ViewInspector):
response_media_types = []
method_mapping = {
'get': 'Retrieve',
'post': 'Create',
'put': 'Update',
'patch': 'PartialUpdate',
'delete': 'Destroy',
'get': 'retrieve',
'post': 'create',
'put': 'update',
'patch': 'partialUpdate',
'delete': 'destroy',
}
def get_operation(self, path, method):
@ -195,6 +195,12 @@ class AutoSchema(ViewInspector):
content = self._map_serializer(serializer)
return {component_name: content}
def _to_camel_case(self, snake_str):
components = snake_str.split('_')
# We capitalize the first letter of each component except the first one
# with the 'title' method and join them together.
return components[0] + ''.join(x.title() for x in components[1:])
def get_operation_id_base(self, path, method, action):
"""
Compute the base part for operation ID from the model, serializer or view name.
@ -240,7 +246,7 @@ class AutoSchema(ViewInspector):
if is_list_view(path, method, self.view):
action = 'list'
elif method_name not in self.method_mapping:
action = method_name
action = self._to_camel_case(method_name)
else:
action = self.method_mapping[method.lower()]

View File

@ -158,7 +158,7 @@ class TestOperationIntrospection(TestCase):
operation = inspector.get_operation(path, method)
assert operation == {
'operationId': 'RetrieveDocStringExampleDetail',
'operationId': 'retrieveDocStringExampleDetail',
'description': 'A description of my GET operation.',
'parameters': [{
'description': '',
@ -735,6 +735,23 @@ class TestOperationIntrospection(TestCase):
print(str(w[-1].message))
assert 'You have a duplicated operationId' in str(w[-1].message)
def test_operation_id_viewset(self):
router = routers.SimpleRouter()
router.register('account', views.ExampleViewSet, basename="account")
urlpatterns = router.urls
generator = SchemaGenerator(patterns=urlpatterns)
request = create_request('/')
schema = generator.get_schema(request=request)
print(schema)
assert schema['paths']['/account/']['get']['operationId'] == 'listExampleViewSets'
assert schema['paths']['/account/']['post']['operationId'] == 'createExampleViewSet'
assert schema['paths']['/account/{id}/']['get']['operationId'] == 'retrieveExampleViewSet'
assert schema['paths']['/account/{id}/']['put']['operationId'] == 'updateExampleViewSet'
assert schema['paths']['/account/{id}/']['patch']['operationId'] == 'partialUpdateExampleViewSet'
assert schema['paths']['/account/{id}/']['delete']['operationId'] == 'destroyExampleViewSet'
def test_serializer_datefield(self):
path = '/'
method = 'GET'

View File

@ -11,7 +11,7 @@ from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.schemas.openapi import AutoSchema
from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet
from rest_framework.viewsets import GenericViewSet, ViewSet
class ExampleListView(APIView):
@ -215,3 +215,25 @@ class ExampleAutoSchemaDuplicate2(generics.GenericAPIView):
serializer = self.get_serializer(data=now.date(), datetime=now)
return Response(serializer.data)
class ExampleViewSet(ViewSet):
serializer_class = ExampleSerializerModel
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass