mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-25 02:53:58 +03:00
OpenAPI: Make operationId camelCase, matching spec examples. (#7208)
This commit is contained in:
parent
609f708a27
commit
6a23fa0649
|
@ -290,7 +290,8 @@ class MyView(APIView):
|
||||||
|
|
||||||
### OperationId
|
### 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.
|
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.
|
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")
|
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.
|
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:
|
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:
|
||||||
|
|
|
@ -131,11 +131,11 @@ class AutoSchema(ViewInspector):
|
||||||
response_media_types = []
|
response_media_types = []
|
||||||
|
|
||||||
method_mapping = {
|
method_mapping = {
|
||||||
'get': 'Retrieve',
|
'get': 'retrieve',
|
||||||
'post': 'Create',
|
'post': 'create',
|
||||||
'put': 'Update',
|
'put': 'update',
|
||||||
'patch': 'PartialUpdate',
|
'patch': 'partialUpdate',
|
||||||
'delete': 'Destroy',
|
'delete': 'destroy',
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_operation(self, path, method):
|
def get_operation(self, path, method):
|
||||||
|
@ -195,6 +195,12 @@ class AutoSchema(ViewInspector):
|
||||||
content = self._map_serializer(serializer)
|
content = self._map_serializer(serializer)
|
||||||
return {component_name: content}
|
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):
|
def get_operation_id_base(self, path, method, action):
|
||||||
"""
|
"""
|
||||||
Compute the base part for operation ID from the model, serializer or view name.
|
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):
|
if is_list_view(path, method, self.view):
|
||||||
action = 'list'
|
action = 'list'
|
||||||
elif method_name not in self.method_mapping:
|
elif method_name not in self.method_mapping:
|
||||||
action = method_name
|
action = self._to_camel_case(method_name)
|
||||||
else:
|
else:
|
||||||
action = self.method_mapping[method.lower()]
|
action = self.method_mapping[method.lower()]
|
||||||
|
|
||||||
|
|
|
@ -158,7 +158,7 @@ class TestOperationIntrospection(TestCase):
|
||||||
|
|
||||||
operation = inspector.get_operation(path, method)
|
operation = inspector.get_operation(path, method)
|
||||||
assert operation == {
|
assert operation == {
|
||||||
'operationId': 'RetrieveDocStringExampleDetail',
|
'operationId': 'retrieveDocStringExampleDetail',
|
||||||
'description': 'A description of my GET operation.',
|
'description': 'A description of my GET operation.',
|
||||||
'parameters': [{
|
'parameters': [{
|
||||||
'description': '',
|
'description': '',
|
||||||
|
@ -735,6 +735,23 @@ class TestOperationIntrospection(TestCase):
|
||||||
print(str(w[-1].message))
|
print(str(w[-1].message))
|
||||||
assert 'You have a duplicated operationId' in 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):
|
def test_serializer_datefield(self):
|
||||||
path = '/'
|
path = '/'
|
||||||
method = 'GET'
|
method = 'GET'
|
||||||
|
|
|
@ -11,7 +11,7 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.schemas.openapi import AutoSchema
|
from rest_framework.schemas.openapi import AutoSchema
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet, ViewSet
|
||||||
|
|
||||||
|
|
||||||
class ExampleListView(APIView):
|
class ExampleListView(APIView):
|
||||||
|
@ -215,3 +215,25 @@ class ExampleAutoSchemaDuplicate2(generics.GenericAPIView):
|
||||||
|
|
||||||
serializer = self.get_serializer(data=now.date(), datetime=now)
|
serializer = self.get_serializer(data=now.date(), datetime=now)
|
||||||
return Response(serializer.data)
|
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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user