Repository: encode/django-rest-framework

Issue:
OpenAPI Schema inconsistent operationId casing
## Checklist

- [x] I have verified that that issue exists against the `master` branch of Django REST framework.
- [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [x] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [x] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
- [x] I have reduced the issue to the simplest possible case.
- [x] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Steps to reproduce

There is inconsistent casing of titles in the the OpenAPI `operationId` naming.

## Expected behavior

Expected behavior is that the method names are consistent in case between them. I.e.

```
RetrieveRegistrant
ListRegistrant
```

or 

```
retrieveRegistrant
listRegistrant
```

Given that the rest of the method names are lowercase, I would opt for `listRegistrant` (initial lower camel case) over CamelCase.

## Actual behavior

 It lowercases all methods names accept for List which is hardcoded.

```
retrieveRegistrant
ListRegistrants
```

Problem area:

[https://github.com/encode/django-rest-framework/blob/master/rest_framework/schemas/openapi.py#L112](https://github.com/encode/django-rest-framework/blob/master/rest_framework/schemas/openapi.py#L112
OpenAPI Schema inconsistent operationId casing
## Checklist

- [x] I have verified that that issue exists against the `master` branch of Django REST framework.
- [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [x] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [x] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
- [x] I have reduced the issue to the simplest possible case.
- [x] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Steps to reproduce

There is inconsistent casing of titles in the the OpenAPI `operationId` naming.

## Expected behavior

Expected behavior is that the method names are consistent in case between them. I.e.

```
RetrieveRegistrant
ListRegistrant
```

or 

```
retrieveRegistrant
listRegistrant
```

Given that the rest of the method names are lowercase, I would opt for `listRegistrant` (initial lower camel case) over CamelCase.

## Actual behavior

 It lowercases all methods names accept for List which is hardcoded.

```
retrieveRegistrant
ListRegistrants
```

Problem area:

[https://github.com/encode/django-rest-framework/blob/master/rest_framework/schemas/openapi.py#L112](https://github.com/encode/django-rest-framework/blob/master/rest_framework/schemas/openapi.py#L112


Discussion:
Hi @peterfarrell. Want to make a PR correcting that `L`?
Hi @peterfarrell. Want to make a PR correcting that `L`?

Resolving patch:
diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py
--- a/rest_framework/schemas/openapi.py
+++ b/rest_framework/schemas/openapi.py
@@ -111,7 +111,7 @@ def _get_operation_id(self, path, method):
         """
         method_name = getattr(self.view, 'action', method.lower())
         if is_list_view(path, method, self.view):
-            action = 'List'
+            action = 'list'
         elif method_name not in self.method_mapping:
             action = method_name
         else:
@@ -135,10 +135,13 @@ def _get_operation_id(self, path, method):
                 name = name[:-7]
             elif name.endswith('View'):
                 name = name[:-4]
-            if name.endswith(action):  # ListView, UpdateAPIView, ThingDelete ...
+
+            # Due to camel-casing of classes and `action` being lowercase, apply title in order to find if action truly
+            # comes at the end of the name
+            if name.endswith(action.title()):  # ListView, UpdateAPIView, ThingDelete ...
                 name = name[:-len(action)]
 
-        if action == 'List' and not name.endswith('s'):  # ListThings instead of ListThing
+        if action == 'list' and not name.endswith('s'):  # listThings instead of listThing
             name += 's'
 
         return action + name


Test patch:
diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py
--- a/tests/schemas/test_openapi.py
+++ b/tests/schemas/test_openapi.py
@@ -80,7 +80,7 @@ def test_path_without_parameters(self):
 
         operation = inspector.get_operation(path, method)
         assert operation == {
-            'operationId': 'ListExamples',
+            'operationId': 'listExamples',
             'parameters': [],
             'responses': {
                 '200': {
@@ -402,7 +402,7 @@ def test_operation_id_generation(self):
         inspector.view = view
 
         operationId = inspector._get_operation_id(path, method)
-        assert operationId == 'ListExamples'
+        assert operationId == 'listExamples'
 
     def test_repeat_operation_ids(self):
         router = routers.SimpleRouter()