Repository: encode/django-rest-framework

Issue:
Schema Generation: Extract Method for adjusting manual fields
Ref https://github.com/encode/django-rest-framework/pull/5621#issuecomment-346841479
Ref #5630

The logic to add/adjust `manual_fields` is currently inline in `get_link`: 

https://github.com/encode/django-rest-framework/blob/c63e35cb09170e1166896ad31a1bb9a121aaa2f8/rest_framework/schemas/inspectors.py#L184-L188

This should be extracted to a separate method so the logic can be re-used when users are overriding `get_link`


Discussion:

Resolving patch:
diff --git a/rest_framework/schemas/inspectors.py b/rest_framework/schemas/inspectors.py
--- a/rest_framework/schemas/inspectors.py
+++ b/rest_framework/schemas/inspectors.py
@@ -172,7 +172,8 @@ def __init__(self, manual_fields=None):
         * `manual_fields`: list of `coreapi.Field` instances that
             will be added to auto-generated fields, overwriting on `Field.name`
         """
-
+        if manual_fields is None:
+            manual_fields = []
         self._manual_fields = manual_fields
 
     def get_link(self, path, method, base_url):
@@ -181,11 +182,8 @@ def get_link(self, path, method, base_url):
         fields += self.get_pagination_fields(path, method)
         fields += self.get_filter_fields(path, method)
 
-        if self._manual_fields is not None:
-            by_name = {f.name: f for f in fields}
-            for f in self._manual_fields:
-                by_name[f.name] = f
-            fields = list(by_name.values())
+        manual_fields = self.get_manual_fields(path, method)
+        fields = self.update_fields(fields, manual_fields)
 
         if fields and any([field.location in ('form', 'body') for field in fields]):
             encoding = self.get_encoding(path, method)
@@ -379,6 +377,31 @@ def get_filter_fields(self, path, method):
             fields += filter_backend().get_schema_fields(self.view)
         return fields
 
+    def get_manual_fields(self, path, method):
+        return self._manual_fields
+
+    @staticmethod
+    def update_fields(fields, update_with):
+        """
+        Update list of coreapi.Field instances, overwriting on `Field.name`.
+
+        Utility function to handle replacing coreapi.Field fields
+        from a list by name. Used to handle `manual_fields`.
+
+        Parameters:
+
+        * `fields`: list of `coreapi.Field` instances to update
+        * `update_with: list of `coreapi.Field` instances to add or replace.
+        """
+        if not update_with:
+            return fields
+
+        by_name = OrderedDict((f.name, f) for f in fields)
+        for f in update_with:
+            by_name[f.name] = f
+        fields = list(by_name.values())
+        return fields
+
     def get_encoding(self, path, method):
         """
         Return the 'encoding' parameter to use for a given endpoint.


Test patch:
diff --git a/tests/test_schemas.py b/tests/test_schemas.py
--- a/tests/test_schemas.py
+++ b/tests/test_schemas.py
@@ -516,7 +516,7 @@ def test_4605_regression(self):
         assert prefix == '/'
 
 
-class TestDescriptor(TestCase):
+class TestAutoSchema(TestCase):
 
     def test_apiview_schema_descriptor(self):
         view = APIView()
@@ -528,7 +528,43 @@ def test_get_link_requires_instance(self):
         with pytest.raises(AssertionError):
             descriptor.get_link(None, None, None)  # ???: Do the dummy arguments require a tighter assert?
 
-    def test_manual_fields(self):
+    def test_update_fields(self):
+        """
+        That updating fields by-name helper is correct
+
+        Recall: `update_fields(fields, update_with)`
+        """
+        schema = AutoSchema()
+        fields = []
+
+        # Adds a field...
+        fields = schema.update_fields(fields, [
+            coreapi.Field(
+                "my_field",
+                required=True,
+                location="path",
+                schema=coreschema.String()
+            ),
+        ])
+
+        assert len(fields) == 1
+        assert fields[0].name == "my_field"
+
+        # Replaces a field...
+        fields = schema.update_fields(fields, [
+            coreapi.Field(
+                "my_field",
+                required=False,
+                location="path",
+                schema=coreschema.String()
+            ),
+        ])
+
+        assert len(fields) == 1
+        assert fields[0].required is False
+
+    def test_get_manual_fields(self):
+        """That get_manual_fields is applied during get_link"""
 
         class CustomView(APIView):
             schema = AutoSchema(manual_fields=[