Repository: netbox-community/netbox

Issue:
List ChoiceField choices in Swagger as enum
### Environment
* Python version:  3.7.3
* NetBox version:  2.7.2

### Proposed Functionality

Based on this specification https://swagger.io/docs/specification/data-models/enums/ the ChoiceField choices for label and value can (and should) be listed as enums.

### Use Case

Proper generation of API model from openapi specification for 3rd party projects without requirement to query OPTIONS for each endpoint to receive extra data from django. For example we are generating Java API for Netbox based on openapi spec from Netbox. Also having the choices listed on api/docs Swagger UI is very helpful when browsing through the API docs.

### Database Changes

None

### External Dependencies

None


Discussion:

Resolving patch:
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
@@ -76,26 +76,28 @@ def field_to_swagger_object(self, field, swagger_object_type, use_references, **
         SwaggerType, _ = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
 
         if isinstance(field, ChoiceField):
-            value_schema = openapi.Schema(type=openapi.TYPE_STRING)
+            choices = field._choices
+            choice_value = list(choices.keys())
+            choice_label = list(choices.values())
+            value_schema = openapi.Schema(type=openapi.TYPE_STRING, enum=choice_value)
 
-            choices = list(field._choices.keys())
-            if set([None] + choices) == {None, True, False}:
+            if set([None] + choice_value) == {None, True, False}:
                 # DeviceType.subdevice_role, Device.face and InterfaceConnection.connection_status all need to be
                 # differentiated since they each have subtly different values in their choice keys.
                 # - subdevice_role and connection_status are booleans, although subdevice_role includes None
                 # - face is an integer set {0, 1} which is easily confused with {False, True}
                 schema_type = openapi.TYPE_STRING
-                if all(type(x) == bool for x in [c for c in choices if c is not None]):
+                if all(type(x) == bool for x in [c for c in choice_value if c is not None]):
                     schema_type = openapi.TYPE_BOOLEAN
-                value_schema = openapi.Schema(type=schema_type)
+                value_schema = openapi.Schema(type=schema_type, enum=choice_value)
                 value_schema['x-nullable'] = True
 
-            if isinstance(choices[0], int):
+            if isinstance(choice_value[0], int):
                 # Change value_schema for IPAddressFamilyChoices, RackWidthChoices
-                value_schema = openapi.Schema(type=openapi.TYPE_INTEGER)
+                value_schema = openapi.Schema(type=openapi.TYPE_INTEGER, enum=choice_value)
 
             schema = SwaggerType(type=openapi.TYPE_OBJECT, required=["label", "value"], properties={
-                "label": openapi.Schema(type=openapi.TYPE_STRING),
+                "label": openapi.Schema(type=openapi.TYPE_STRING, enum=choice_label),
                 "value": value_schema
             })
 


Test patch: