diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index 3de07d1c7..3fffcd529 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -3,6 +3,7 @@ coreapi==2.3.1 coreschema==0.0.4 django-filter>=2.4.0,<3.0 django-guardian>=2.4.0,<2.5 +inflection==0.5.1 markdown==3.3 psycopg2-binary>=2.9.5,<2.10 pygments==2.12 diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 2f9fb9f28..8e0f400ca 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -11,6 +11,7 @@ from django.core.validators import ( ) from django.db import models from django.utils.encoding import force_str +from inflection import pluralize from rest_framework import ( RemovedInDRF315Warning, exceptions, renderers, serializers @@ -247,8 +248,8 @@ class AutoSchema(ViewInspector): if name.endswith(action.title()): # ListView, UpdateAPIView, ThingDelete ... name = name[:-len(action)] - if action == 'list' and not name.endswith('s'): # listThings instead of listThing - name += 's' + if action == 'list': + name = pluralize(name) return name diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 57007e9e8..e39c9df47 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -715,6 +715,21 @@ class TestOperationIntrospection(TestCase): operationId = inspector.get_operation_id(path, method) assert operationId == 'listUlysses' + def test_operation_id_plural(self): + path = '/' + method = 'GET' + + view = create_view( + views.ExampleGenericAPIView, + method, + create_request(path), + ) + inspector = AutoSchema(operation_id_base='City') + inspector.view = view + + operationId = inspector.get_operation_id(path, method) + assert operationId == 'listCities' + def test_operation_id_override_get(self): class CustomSchema(AutoSchema): def get_operation_id(self, path, method):