Repository: netbox-community/netbox

Issue:
API variable type mismatch at creating/modifying an entry
### Environment
* Python version:  3.5.2
* NetBox version:  2.4.3

### Steps to Reproduce

1. Open `/api/docs/` URL of NetBox
2. For example, open `POST /ipam/ip-addresses/`
3. It shows
    ```
    tenant: {
        id: integer ...,
        url: string ...,
        name: string ...,
        slug: string ...
    }
    ```

### Expected Behavior
In the example above, it should be

```
tenant: integer ...
```

### Observed Behavior
It shows a parameter which is only compatible at reading but incompatible at writing like

```
tenant: {
    id: integer ...,
    url: string ...,
    name: string ...,
    slug: string ...
}
```

### Details
Thanks for providing a great software! I'm enjoy using it.

After upgrading to 2.4.3, I found all "WritableXXX" data models had gone away from API JSON representation taken from `/api/docs/?format=openapi` and these had been replaced by models which were used at reading (GET request). It causes API document mismatch with NetBox internal behavior and a generated code by Swagger doesn't work especially in static type system language like Go etc.

In the above, I explain about a `tenant` parameter but there are some other parameters which have the same issue.

As far as I checked, it seems to have been introduced at commit 72417832, 821fb1e0 etc. I think a solution should be

- Change API JSON description at POST, PUT etc. if a parameter inherits `WritableNestedSerializer`
- Just allow a reading parameter and ignore unneeded arguments at writing

I'm not much familiar with Python so the ideas are just my thought.


Discussion:
The API documentation is generated automatically using [drf-yasg](https://github.com/axnsan12/drf-yasg). If someone can come up with a non-intrusive way of enabling Swagger to recognize the different read/write behaviors, I'll be happy to incorporate it.
I've written a fix and tested it in our system. It passed unit tests and worked with our API client written in Go. I'll send a pull-request once I clean up commits. Let's discuss about the implementation there.

Resolving patch:
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -275,9 +275,12 @@
 
 # drf_yasg settings for Swagger
 SWAGGER_SETTINGS = {
+    'DEFAULT_AUTO_SCHEMA_CLASS': 'utilities.custom_inspectors.NetBoxSwaggerAutoSchema',
     'DEFAULT_FIELD_INSPECTORS': [
         'utilities.custom_inspectors.NullableBooleanFieldInspector',
         'utilities.custom_inspectors.CustomChoiceFieldInspector',
+        'utilities.custom_inspectors.TagListFieldInspector',
+        'utilities.custom_inspectors.SerializedPKRelatedFieldInspector',
         'drf_yasg.inspectors.CamelCaseJSONFilter',
         'drf_yasg.inspectors.ReferencingSerializerInspector',
         'drf_yasg.inspectors.RelatedFieldInspector',
diff --git a/netbox/utilities/custom_inspectors.py b/netbox/utilities/custom_inspectors.py
--- a/netbox/utilities/custom_inspectors.py
+++ b/netbox/utilities/custom_inspectors.py
@@ -1,9 +1,52 @@
 from drf_yasg import openapi
-from drf_yasg.inspectors import FieldInspector, NotHandled, PaginatorInspector, FilterInspector
+from drf_yasg.inspectors import FieldInspector, NotHandled, PaginatorInspector, FilterInspector, SwaggerAutoSchema
 from rest_framework.fields import ChoiceField
+from rest_framework.relations import ManyRelatedField
+from taggit_serializer.serializers import TagListSerializerField
 
 from extras.api.customfields import CustomFieldsSerializer
-from utilities.api import ChoiceField
+from utilities.api import ChoiceField, SerializedPKRelatedField, WritableNestedSerializer
+
+
+class NetBoxSwaggerAutoSchema(SwaggerAutoSchema):
+    def get_request_serializer(self):
+        serializer = super().get_request_serializer()
+
+        if serializer is not None and self.method in self.implicit_body_methods:
+            properties = {}
+            for child_name, child in serializer.fields.items():
+                if isinstance(child, (ChoiceField, WritableNestedSerializer)):
+                    properties[child_name] = None
+                elif isinstance(child, ManyRelatedField) and isinstance(child.child_relation, SerializedPKRelatedField):
+                    properties[child_name] = None
+
+            if properties:
+                writable_class = type('Writable' + type(serializer).__name__, (type(serializer),), properties)
+                serializer = writable_class()
+
+        return serializer
+
+
+class SerializedPKRelatedFieldInspector(FieldInspector):
+    def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
+        SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
+        if isinstance(field, SerializedPKRelatedField):
+            return self.probe_field_inspectors(field.serializer(), ChildSwaggerType, use_references)
+
+        return NotHandled
+
+
+class TagListFieldInspector(FieldInspector):
+    def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
+        SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
+        if isinstance(field, TagListSerializerField):
+            child_schema = self.probe_field_inspectors(field.child, ChildSwaggerType, use_references)
+            return SwaggerType(
+                type=openapi.TYPE_ARRAY,
+                items=child_schema,
+            )
+
+        return NotHandled
 
 
 class CustomChoiceFieldInspector(FieldInspector):


Test patch: