We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
API Exception with invalid choice value
### Environment
* Python version:  3.5.2
* NetBox version:  2.6.2

### Steps to Reproduce

Pass a list instead of an integer for interface type when creating an interface, e.g. `[32767]` instead of `32767`

See [forum thread](https://groups.google.com/forum/#!topic/netbox-discuss/H7xIrwVnOSM)

### Expected Behavior

Value to be rejected by API as invalid

### Observed Behavior

Exception with backtrace - makes it very hard to know where the data error was.

This is because a list is not hashable, and so the test `if data not in self._choices:` raises an exception. [Code link](https://github.com/netbox-community/netbox/blob/develop/netbox/utilities/api.py#L104)



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `netbox/utilities/api.py`
Content:
```
1 from collections import OrderedDict
2 
3 import pytz
4 from django.conf import settings
5 from django.contrib.contenttypes.models import ContentType
6 from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist
7 from django.db.models import ManyToManyField, ProtectedError
8 from django.http import Http404
9 from rest_framework.exceptions import APIException
10 from rest_framework.permissions import BasePermission
11 from rest_framework.relations import PrimaryKeyRelatedField, RelatedField
12 from rest_framework.response import Response
13 from rest_framework.serializers import Field, ModelSerializer, ValidationError
14 from rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet
15 
16 from .utils import dict_to_filter_params, dynamic_import
17 
18 
19 class ServiceUnavailable(APIException):
20     status_code = 503
21     default_detail = "Service temporarily unavailable, please try again later."
22 
23 
24 class SerializerNotFound(Exception):
25     pass
26 
27 
28 def get_serializer_for_model(model, prefix=''):
29     """
30     Dynamically resolve and return the appropriate serializer for a model.
31     """
32     app_name, model_name = model._meta.label.split('.')
33     serializer_name = '{}.api.serializers.{}{}Serializer'.format(
34         app_name, prefix, model_name
35     )
36     try:
37         return dynamic_import(serializer_name)
38     except AttributeError:
39         raise SerializerNotFound(
40             "Could not determine serializer for {}.{} with prefix '{}'".format(app_name, model_name, prefix)
41         )
42 
43 
44 #
45 # Authentication
46 #
47 
48 class IsAuthenticatedOrLoginNotRequired(BasePermission):
49     """
50     Returns True if the user is authenticated or LOGIN_REQUIRED is False.
51     """
52     def has_permission(self, request, view):
53         if not settings.LOGIN_REQUIRED:
54             return True
55         return request.user.is_authenticated
56 
57 
58 #
59 # Fields
60 #
61 
62 class ChoiceField(Field):
63     """
64     Represent a ChoiceField as {'value': <DB value>, 'label': <string>}.
65     """
66     def __init__(self, choices, **kwargs):
67         self._choices = dict()
68         for k, v in choices:
69             # Unpack grouped choices
70             if type(v) in [list, tuple]:
71                 for k2, v2 in v:
72                     self._choices[k2] = v2
73             else:
74                 self._choices[k] = v
75         super().__init__(**kwargs)
76 
77     def to_representation(self, obj):
78         if obj is '':
79             return None
80         data = OrderedDict([
81             ('value', obj),
82             ('label', self._choices[obj])
83         ])
84         return data
85 
86     def to_internal_value(self, data):
87 
88         # Provide an explicit error message if the request is trying to write a dict
89         if type(data) is dict:
90             raise ValidationError('Value must be passed directly (e.g. "foo": 123); do not use a dictionary.')
91 
92         # Check for string representations of boolean/integer values
93         if hasattr(data, 'lower'):
94             if data.lower() == 'true':
95                 data = True
96             elif data.lower() == 'false':
97                 data = False
98             else:
99                 try:
100                     data = int(data)
101                 except ValueError:
102                     pass
103 
104         if data not in self._choices:
105             raise ValidationError("{} is not a valid choice.".format(data))
106 
107         return data
108 
109     @property
110     def choices(self):
111         return self._choices
112 
113 
114 class ContentTypeField(RelatedField):
115     """
116     Represent a ContentType as '<app_label>.<model>'
117     """
118     default_error_messages = {
119         "does_not_exist": "Invalid content type: {content_type}",
120         "invalid": "Invalid value. Specify a content type as '<app_label>.<model_name>'.",
121     }
122 
123     def to_internal_value(self, data):
124         try:
125             app_label, model = data.split('.')
126             return ContentType.objects.get_by_natural_key(app_label=app_label, model=model)
127         except ObjectDoesNotExist:
128             self.fail('does_not_exist', content_type=data)
129         except (TypeError, ValueError):
130             self.fail('invalid')
131 
132     def to_representation(self, obj):
133         return "{}.{}".format(obj.app_label, obj.model)
134 
135 
136 class TimeZoneField(Field):
137     """
138     Represent a pytz time zone.
139     """
140     def to_representation(self, obj):
141         return obj.zone if obj else None
142 
143     def to_internal_value(self, data):
144         if not data:
145             return ""
146         if data not in pytz.common_timezones:
147             raise ValidationError('Unknown time zone "{}" (see pytz.common_timezones for all options)'.format(data))
148         return pytz.timezone(data)
149 
150 
151 class SerializedPKRelatedField(PrimaryKeyRelatedField):
152     """
153     Extends PrimaryKeyRelatedField to return a serialized object on read. This is useful for representing related
154     objects in a ManyToManyField while still allowing a set of primary keys to be written.
155     """
156     def __init__(self, serializer, **kwargs):
157         self.serializer = serializer
158         self.pk_field = kwargs.pop('pk_field', None)
159         super().__init__(**kwargs)
160 
161     def to_representation(self, value):
162         return self.serializer(value, context={'request': self.context['request']}).data
163 
164 
165 #
166 # Serializers
167 #
168 
169 # TODO: We should probably take a fresh look at exactly what we're doing with this. There might be a more elegant
170 # way to enforce model validation on the serializer.
171 class ValidatedModelSerializer(ModelSerializer):
172     """
173     Extends the built-in ModelSerializer to enforce calling clean() on the associated model during validation.
174     """
175     def validate(self, data):
176 
177         # Remove custom fields data and tags (if any) prior to model validation
178         attrs = data.copy()
179         attrs.pop('custom_fields', None)
180         attrs.pop('tags', None)
181 
182         # Skip ManyToManyFields
183         for field in self.Meta.model._meta.get_fields():
184             if isinstance(field, ManyToManyField):
185                 attrs.pop(field.name, None)
186 
187         # Run clean() on an instance of the model
188         if self.instance is None:
189             instance = self.Meta.model(**attrs)
190         else:
191             instance = self.instance
192             for k, v in attrs.items():
193                 setattr(instance, k, v)
194         instance.clean()
195 
196         return data
197 
198 
199 class WritableNestedSerializer(ModelSerializer):
200     """
201     Returns a nested representation of an object on read, but accepts only a primary key on write.
202     """
203 
204     def to_internal_value(self, data):
205 
206         if data is None:
207             return None
208 
209         # Dictionary of related object attributes
210         if isinstance(data, dict):
211             params = dict_to_filter_params(data)
212             try:
213                 return self.Meta.model.objects.get(**params)
214             except ObjectDoesNotExist:
215                 raise ValidationError(
216                     "Related object not found using the provided attributes: {}".format(params)
217                 )
218             except MultipleObjectsReturned:
219                 raise ValidationError(
220                     "Multiple objects match the provided attributes: {}".format(params)
221                 )
222             except FieldError as e:
223                 raise ValidationError(e)
224 
225         # Integer PK of related object
226         if isinstance(data, int):
227             pk = data
228         else:
229             try:
230                 # PK might have been mistakenly passed as a string
231                 pk = int(data)
232             except (TypeError, ValueError):
233                 raise ValidationError(
234                     "Related objects must be referenced by numeric ID or by dictionary of attributes. Received an "
235                     "unrecognized value: {}".format(data)
236                 )
237 
238         # Look up object by PK
239         try:
240             return self.Meta.model.objects.get(pk=int(data))
241         except ObjectDoesNotExist:
242             raise ValidationError(
243                 "Related object not found using the provided numeric ID: {}".format(pk)
244             )
245 
246 
247 #
248 # Viewsets
249 #
250 
251 class ModelViewSet(_ModelViewSet):
252     """
253     Accept either a single object or a list of objects to create.
254     """
255     def get_serializer(self, *args, **kwargs):
256 
257         # If a list of objects has been provided, initialize the serializer with many=True
258         if isinstance(kwargs.get('data', {}), list):
259             kwargs['many'] = True
260 
261         return super().get_serializer(*args, **kwargs)
262 
263     def get_serializer_class(self):
264 
265         # If 'brief' has been passed as a query param, find and return the nested serializer for this model, if one
266         # exists
267         request = self.get_serializer_context()['request']
268         if request.query_params.get('brief', False):
269             try:
270                 return get_serializer_for_model(self.queryset.model, prefix='Nested')
271             except SerializerNotFound:
272                 pass
273 
274         # Fall back to the hard-coded serializer class
275         return self.serializer_class
276 
277     def dispatch(self, request, *args, **kwargs):
278         try:
279             return super().dispatch(request, *args, **kwargs)
280         except ProtectedError as e:
281             models = ['{} ({})'.format(o, o._meta) for o in e.protected_objects.all()]
282             msg = 'Unable to delete object. The following dependent objects were found: {}'.format(', '.join(models))
283             return self.finalize_response(
284                 request,
285                 Response({'detail': msg}, status=409),
286                 *args,
287                 **kwargs
288             )
289 
290     def list(self, *args, **kwargs):
291         """
292         Call to super to allow for caching
293         """
294         return super().list(*args, **kwargs)
295 
296     def retrieve(self, *args, **kwargs):
297         """
298         Call to super to allow for caching
299         """
300         return super().retrieve(*args, **kwargs)
301 
302 
303 class FieldChoicesViewSet(ViewSet):
304     """
305     Expose the built-in numeric values which represent static choices for a model's field.
306     """
307     permission_classes = [IsAuthenticatedOrLoginNotRequired]
308     fields = []
309 
310     def __init__(self, *args, **kwargs):
311         super().__init__(*args, **kwargs)
312 
313         # Compile a dict of all fields in this view
314         self._fields = OrderedDict()
315         for cls, field_list in self.fields:
316             for field_name in field_list:
317 
318                 model_name = cls._meta.verbose_name.lower().replace(' ', '-')
319                 key = ':'.join([model_name, field_name])
320 
321                 serializer = get_serializer_for_model(cls)()
322                 choices = []
323 
324                 for k, v in serializer.get_fields()[field_name].choices.items():
325                     if type(v) in [list, tuple]:
326                         for k2, v2 in v:
327                             choices.append({
328                                 'value': k2,
329                                 'label': v2,
330                             })
331                     else:
332                         choices.append({
333                             'value': k,
334                             'label': v,
335                         })
336                 self._fields[key] = choices
337 
338     def list(self, request):
339         return Response(self._fields)
340 
341     def retrieve(self, request, pk):
342         if pk not in self._fields:
343             raise Http404
344         return Response(self._fields[pk])
345 
346     def get_view_name(self):
347         return "Field Choices"
348
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py
--- a/netbox/utilities/api.py
+++ b/netbox/utilities/api.py
@@ -85,9 +85,9 @@
 
     def to_internal_value(self, data):
 
-        # Provide an explicit error message if the request is trying to write a dict
-        if type(data) is dict:
-            raise ValidationError('Value must be passed directly (e.g. "foo": 123); do not use a dictionary.')
+        # Provide an explicit error message if the request is trying to write a dict or list
+        if isinstance(data, (dict, list)):
+            raise ValidationError('Value must be passed directly (e.g. "foo": 123); do not use a dictionary or list.')
 
         # Check for string representations of boolean/integer values
         if hasattr(data, 'lower'):
@@ -101,10 +101,13 @@
                 except ValueError:
                     pass
 
-        if data not in self._choices:
-            raise ValidationError("{} is not a valid choice.".format(data))
+        try:
+            if data in self._choices:
+                return data
+        except TypeError:  # Input is an unhashable type
+            pass
 
-        return data
+        raise ValidationError("{} is not a valid choice.".format(data))
 
     @property
     def choices(self):

{"golden_diff": "diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py\n--- a/netbox/utilities/api.py\n+++ b/netbox/utilities/api.py\n@@ -85,9 +85,9 @@\n \n     def to_internal_value(self, data):\n \n-        # Provide an explicit error message if the request is trying to write a dict\n-        if type(data) is dict:\n-            raise ValidationError('Value must be passed directly (e.g. \"foo\": 123); do not use a dictionary.')\n+        # Provide an explicit error message if the request is trying to write a dict or list\n+        if isinstance(data, (dict, list)):\n+            raise ValidationError('Value must be passed directly (e.g. \"foo\": 123); do not use a dictionary or list.')\n \n         # Check for string representations of boolean/integer values\n         if hasattr(data, 'lower'):\n@@ -101,10 +101,13 @@\n                 except ValueError:\n                     pass\n \n-        if data not in self._choices:\n-            raise ValidationError(\"{} is not a valid choice.\".format(data))\n+        try:\n+            if data in self._choices:\n+                return data\n+        except TypeError:  # Input is an unhashable type\n+            pass\n \n-        return data\n+        raise ValidationError(\"{} is not a valid choice.\".format(data))\n \n     @property\n     def choices(self):\n", "issue": "API Exception with invalid choice value\n### Environment\r\n* Python version:  3.5.2\r\n* NetBox version:  2.6.2\r\n\r\n### Steps to Reproduce\r\n\r\nPass a list instead of an integer for interface type when creating an interface, e.g. `[32767]` instead of `32767`\r\n\r\nSee [forum thread](https://groups.google.com/forum/#!topic/netbox-discuss/H7xIrwVnOSM)\r\n\r\n### Expected Behavior\r\n\r\nValue to be rejected by API as invalid\r\n\r\n### Observed Behavior\r\n\r\nException with backtrace - makes it very hard to know where the data error was.\r\n\r\nThis is because a list is not hashable, and so the test `if data not in self._choices:` raises an exception. [Code link](https://github.com/netbox-community/netbox/blob/develop/netbox/utilities/api.py#L104)\r\n\r\n\n", "before_files": [{"content": "from collections import OrderedDict\n\nimport pytz\nfrom django.conf import settings\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist\nfrom django.db.models import ManyToManyField, ProtectedError\nfrom django.http import Http404\nfrom rest_framework.exceptions import APIException\nfrom rest_framework.permissions import BasePermission\nfrom rest_framework.relations import PrimaryKeyRelatedField, RelatedField\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import Field, ModelSerializer, ValidationError\nfrom rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet\n\nfrom .utils import dict_to_filter_params, dynamic_import\n\n\nclass ServiceUnavailable(APIException):\n    status_code = 503\n    default_detail = \"Service temporarily unavailable, please try again later.\"\n\n\nclass SerializerNotFound(Exception):\n    pass\n\n\ndef get_serializer_for_model(model, prefix=''):\n    \"\"\"\n    Dynamically resolve and return the appropriate serializer for a model.\n    \"\"\"\n    app_name, model_name = model._meta.label.split('.')\n    serializer_name = '{}.api.serializers.{}{}Serializer'.format(\n        app_name, prefix, model_name\n    )\n    try:\n        return dynamic_import(serializer_name)\n    except AttributeError:\n        raise SerializerNotFound(\n            \"Could not determine serializer for {}.{} with prefix '{}'\".format(app_name, model_name, prefix)\n        )\n\n\n#\n# Authentication\n#\n\nclass IsAuthenticatedOrLoginNotRequired(BasePermission):\n    \"\"\"\n    Returns True if the user is authenticated or LOGIN_REQUIRED is False.\n    \"\"\"\n    def has_permission(self, request, view):\n        if not settings.LOGIN_REQUIRED:\n            return True\n        return request.user.is_authenticated\n\n\n#\n# Fields\n#\n\nclass ChoiceField(Field):\n    \"\"\"\n    Represent a ChoiceField as {'value': <DB value>, 'label': <string>}.\n    \"\"\"\n    def __init__(self, choices, **kwargs):\n        self._choices = dict()\n        for k, v in choices:\n            # Unpack grouped choices\n            if type(v) in [list, tuple]:\n                for k2, v2 in v:\n                    self._choices[k2] = v2\n            else:\n                self._choices[k] = v\n        super().__init__(**kwargs)\n\n    def to_representation(self, obj):\n        if obj is '':\n            return None\n        data = OrderedDict([\n            ('value', obj),\n            ('label', self._choices[obj])\n        ])\n        return data\n\n    def to_internal_value(self, data):\n\n        # Provide an explicit error message if the request is trying to write a dict\n        if type(data) is dict:\n            raise ValidationError('Value must be passed directly (e.g. \"foo\": 123); do not use a dictionary.')\n\n        # Check for string representations of boolean/integer values\n        if hasattr(data, 'lower'):\n            if data.lower() == 'true':\n                data = True\n            elif data.lower() == 'false':\n                data = False\n            else:\n                try:\n                    data = int(data)\n                except ValueError:\n                    pass\n\n        if data not in self._choices:\n            raise ValidationError(\"{} is not a valid choice.\".format(data))\n\n        return data\n\n    @property\n    def choices(self):\n        return self._choices\n\n\nclass ContentTypeField(RelatedField):\n    \"\"\"\n    Represent a ContentType as '<app_label>.<model>'\n    \"\"\"\n    default_error_messages = {\n        \"does_not_exist\": \"Invalid content type: {content_type}\",\n        \"invalid\": \"Invalid value. Specify a content type as '<app_label>.<model_name>'.\",\n    }\n\n    def to_internal_value(self, data):\n        try:\n            app_label, model = data.split('.')\n            return ContentType.objects.get_by_natural_key(app_label=app_label, model=model)\n        except ObjectDoesNotExist:\n            self.fail('does_not_exist', content_type=data)\n        except (TypeError, ValueError):\n            self.fail('invalid')\n\n    def to_representation(self, obj):\n        return \"{}.{}\".format(obj.app_label, obj.model)\n\n\nclass TimeZoneField(Field):\n    \"\"\"\n    Represent a pytz time zone.\n    \"\"\"\n    def to_representation(self, obj):\n        return obj.zone if obj else None\n\n    def to_internal_value(self, data):\n        if not data:\n            return \"\"\n        if data not in pytz.common_timezones:\n            raise ValidationError('Unknown time zone \"{}\" (see pytz.common_timezones for all options)'.format(data))\n        return pytz.timezone(data)\n\n\nclass SerializedPKRelatedField(PrimaryKeyRelatedField):\n    \"\"\"\n    Extends PrimaryKeyRelatedField to return a serialized object on read. This is useful for representing related\n    objects in a ManyToManyField while still allowing a set of primary keys to be written.\n    \"\"\"\n    def __init__(self, serializer, **kwargs):\n        self.serializer = serializer\n        self.pk_field = kwargs.pop('pk_field', None)\n        super().__init__(**kwargs)\n\n    def to_representation(self, value):\n        return self.serializer(value, context={'request': self.context['request']}).data\n\n\n#\n# Serializers\n#\n\n# TODO: We should probably take a fresh look at exactly what we're doing with this. There might be a more elegant\n# way to enforce model validation on the serializer.\nclass ValidatedModelSerializer(ModelSerializer):\n    \"\"\"\n    Extends the built-in ModelSerializer to enforce calling clean() on the associated model during validation.\n    \"\"\"\n    def validate(self, data):\n\n        # Remove custom fields data and tags (if any) prior to model validation\n        attrs = data.copy()\n        attrs.pop('custom_fields', None)\n        attrs.pop('tags', None)\n\n        # Skip ManyToManyFields\n        for field in self.Meta.model._meta.get_fields():\n            if isinstance(field, ManyToManyField):\n                attrs.pop(field.name, None)\n\n        # Run clean() on an instance of the model\n        if self.instance is None:\n            instance = self.Meta.model(**attrs)\n        else:\n            instance = self.instance\n            for k, v in attrs.items():\n                setattr(instance, k, v)\n        instance.clean()\n\n        return data\n\n\nclass WritableNestedSerializer(ModelSerializer):\n    \"\"\"\n    Returns a nested representation of an object on read, but accepts only a primary key on write.\n    \"\"\"\n\n    def to_internal_value(self, data):\n\n        if data is None:\n            return None\n\n        # Dictionary of related object attributes\n        if isinstance(data, dict):\n            params = dict_to_filter_params(data)\n            try:\n                return self.Meta.model.objects.get(**params)\n            except ObjectDoesNotExist:\n                raise ValidationError(\n                    \"Related object not found using the provided attributes: {}\".format(params)\n                )\n            except MultipleObjectsReturned:\n                raise ValidationError(\n                    \"Multiple objects match the provided attributes: {}\".format(params)\n                )\n            except FieldError as e:\n                raise ValidationError(e)\n\n        # Integer PK of related object\n        if isinstance(data, int):\n            pk = data\n        else:\n            try:\n                # PK might have been mistakenly passed as a string\n                pk = int(data)\n            except (TypeError, ValueError):\n                raise ValidationError(\n                    \"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an \"\n                    \"unrecognized value: {}\".format(data)\n                )\n\n        # Look up object by PK\n        try:\n            return self.Meta.model.objects.get(pk=int(data))\n        except ObjectDoesNotExist:\n            raise ValidationError(\n                \"Related object not found using the provided numeric ID: {}\".format(pk)\n            )\n\n\n#\n# Viewsets\n#\n\nclass ModelViewSet(_ModelViewSet):\n    \"\"\"\n    Accept either a single object or a list of objects to create.\n    \"\"\"\n    def get_serializer(self, *args, **kwargs):\n\n        # If a list of objects has been provided, initialize the serializer with many=True\n        if isinstance(kwargs.get('data', {}), list):\n            kwargs['many'] = True\n\n        return super().get_serializer(*args, **kwargs)\n\n    def get_serializer_class(self):\n\n        # If 'brief' has been passed as a query param, find and return the nested serializer for this model, if one\n        # exists\n        request = self.get_serializer_context()['request']\n        if request.query_params.get('brief', False):\n            try:\n                return get_serializer_for_model(self.queryset.model, prefix='Nested')\n            except SerializerNotFound:\n                pass\n\n        # Fall back to the hard-coded serializer class\n        return self.serializer_class\n\n    def dispatch(self, request, *args, **kwargs):\n        try:\n            return super().dispatch(request, *args, **kwargs)\n        except ProtectedError as e:\n            models = ['{} ({})'.format(o, o._meta) for o in e.protected_objects.all()]\n            msg = 'Unable to delete object. The following dependent objects were found: {}'.format(', '.join(models))\n            return self.finalize_response(\n                request,\n                Response({'detail': msg}, status=409),\n                *args,\n                **kwargs\n            )\n\n    def list(self, *args, **kwargs):\n        \"\"\"\n        Call to super to allow for caching\n        \"\"\"\n        return super().list(*args, **kwargs)\n\n    def retrieve(self, *args, **kwargs):\n        \"\"\"\n        Call to super to allow for caching\n        \"\"\"\n        return super().retrieve(*args, **kwargs)\n\n\nclass FieldChoicesViewSet(ViewSet):\n    \"\"\"\n    Expose the built-in numeric values which represent static choices for a model's field.\n    \"\"\"\n    permission_classes = [IsAuthenticatedOrLoginNotRequired]\n    fields = []\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        # Compile a dict of all fields in this view\n        self._fields = OrderedDict()\n        for cls, field_list in self.fields:\n            for field_name in field_list:\n\n                model_name = cls._meta.verbose_name.lower().replace(' ', '-')\n                key = ':'.join([model_name, field_name])\n\n                serializer = get_serializer_for_model(cls)()\n                choices = []\n\n                for k, v in serializer.get_fields()[field_name].choices.items():\n                    if type(v) in [list, tuple]:\n                        for k2, v2 in v:\n                            choices.append({\n                                'value': k2,\n                                'label': v2,\n                            })\n                    else:\n                        choices.append({\n                            'value': k,\n                            'label': v,\n                        })\n                self._fields[key] = choices\n\n    def list(self, request):\n        return Response(self._fields)\n\n    def retrieve(self, request, pk):\n        if pk not in self._fields:\n            raise Http404\n        return Response(self._fields[pk])\n\n    def get_view_name(self):\n        return \"Field Choices\"\n", "path": "netbox/utilities/api.py"}], "after_files": [{"content": "from collections import OrderedDict\n\nimport pytz\nfrom django.conf import settings\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist\nfrom django.db.models import ManyToManyField, ProtectedError\nfrom django.http import Http404\nfrom rest_framework.exceptions import APIException\nfrom rest_framework.permissions import BasePermission\nfrom rest_framework.relations import PrimaryKeyRelatedField, RelatedField\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import Field, ModelSerializer, ValidationError\nfrom rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet\n\nfrom .utils import dict_to_filter_params, dynamic_import\n\n\nclass ServiceUnavailable(APIException):\n    status_code = 503\n    default_detail = \"Service temporarily unavailable, please try again later.\"\n\n\nclass SerializerNotFound(Exception):\n    pass\n\n\ndef get_serializer_for_model(model, prefix=''):\n    \"\"\"\n    Dynamically resolve and return the appropriate serializer for a model.\n    \"\"\"\n    app_name, model_name = model._meta.label.split('.')\n    serializer_name = '{}.api.serializers.{}{}Serializer'.format(\n        app_name, prefix, model_name\n    )\n    try:\n        return dynamic_import(serializer_name)\n    except AttributeError:\n        raise SerializerNotFound(\n            \"Could not determine serializer for {}.{} with prefix '{}'\".format(app_name, model_name, prefix)\n        )\n\n\n#\n# Authentication\n#\n\nclass IsAuthenticatedOrLoginNotRequired(BasePermission):\n    \"\"\"\n    Returns True if the user is authenticated or LOGIN_REQUIRED is False.\n    \"\"\"\n    def has_permission(self, request, view):\n        if not settings.LOGIN_REQUIRED:\n            return True\n        return request.user.is_authenticated\n\n\n#\n# Fields\n#\n\nclass ChoiceField(Field):\n    \"\"\"\n    Represent a ChoiceField as {'value': <DB value>, 'label': <string>}.\n    \"\"\"\n    def __init__(self, choices, **kwargs):\n        self._choices = dict()\n        for k, v in choices:\n            # Unpack grouped choices\n            if type(v) in [list, tuple]:\n                for k2, v2 in v:\n                    self._choices[k2] = v2\n            else:\n                self._choices[k] = v\n        super().__init__(**kwargs)\n\n    def to_representation(self, obj):\n        if obj is '':\n            return None\n        data = OrderedDict([\n            ('value', obj),\n            ('label', self._choices[obj])\n        ])\n        return data\n\n    def to_internal_value(self, data):\n\n        # Provide an explicit error message if the request is trying to write a dict or list\n        if isinstance(data, (dict, list)):\n            raise ValidationError('Value must be passed directly (e.g. \"foo\": 123); do not use a dictionary or list.')\n\n        # Check for string representations of boolean/integer values\n        if hasattr(data, 'lower'):\n            if data.lower() == 'true':\n                data = True\n            elif data.lower() == 'false':\n                data = False\n            else:\n                try:\n                    data = int(data)\n                except ValueError:\n                    pass\n\n        try:\n            if data in self._choices:\n                return data\n        except TypeError:  # Input is an unhashable type\n            pass\n\n        raise ValidationError(\"{} is not a valid choice.\".format(data))\n\n    @property\n    def choices(self):\n        return self._choices\n\n\nclass ContentTypeField(RelatedField):\n    \"\"\"\n    Represent a ContentType as '<app_label>.<model>'\n    \"\"\"\n    default_error_messages = {\n        \"does_not_exist\": \"Invalid content type: {content_type}\",\n        \"invalid\": \"Invalid value. Specify a content type as '<app_label>.<model_name>'.\",\n    }\n\n    def to_internal_value(self, data):\n        try:\n            app_label, model = data.split('.')\n            return ContentType.objects.get_by_natural_key(app_label=app_label, model=model)\n        except ObjectDoesNotExist:\n            self.fail('does_not_exist', content_type=data)\n        except (TypeError, ValueError):\n            self.fail('invalid')\n\n    def to_representation(self, obj):\n        return \"{}.{}\".format(obj.app_label, obj.model)\n\n\nclass TimeZoneField(Field):\n    \"\"\"\n    Represent a pytz time zone.\n    \"\"\"\n    def to_representation(self, obj):\n        return obj.zone if obj else None\n\n    def to_internal_value(self, data):\n        if not data:\n            return \"\"\n        if data not in pytz.common_timezones:\n            raise ValidationError('Unknown time zone \"{}\" (see pytz.common_timezones for all options)'.format(data))\n        return pytz.timezone(data)\n\n\nclass SerializedPKRelatedField(PrimaryKeyRelatedField):\n    \"\"\"\n    Extends PrimaryKeyRelatedField to return a serialized object on read. This is useful for representing related\n    objects in a ManyToManyField while still allowing a set of primary keys to be written.\n    \"\"\"\n    def __init__(self, serializer, **kwargs):\n        self.serializer = serializer\n        self.pk_field = kwargs.pop('pk_field', None)\n        super().__init__(**kwargs)\n\n    def to_representation(self, value):\n        return self.serializer(value, context={'request': self.context['request']}).data\n\n\n#\n# Serializers\n#\n\n# TODO: We should probably take a fresh look at exactly what we're doing with this. There might be a more elegant\n# way to enforce model validation on the serializer.\nclass ValidatedModelSerializer(ModelSerializer):\n    \"\"\"\n    Extends the built-in ModelSerializer to enforce calling clean() on the associated model during validation.\n    \"\"\"\n    def validate(self, data):\n\n        # Remove custom fields data and tags (if any) prior to model validation\n        attrs = data.copy()\n        attrs.pop('custom_fields', None)\n        attrs.pop('tags', None)\n\n        # Skip ManyToManyFields\n        for field in self.Meta.model._meta.get_fields():\n            if isinstance(field, ManyToManyField):\n                attrs.pop(field.name, None)\n\n        # Run clean() on an instance of the model\n        if self.instance is None:\n            instance = self.Meta.model(**attrs)\n        else:\n            instance = self.instance\n            for k, v in attrs.items():\n                setattr(instance, k, v)\n        instance.clean()\n\n        return data\n\n\nclass WritableNestedSerializer(ModelSerializer):\n    \"\"\"\n    Returns a nested representation of an object on read, but accepts only a primary key on write.\n    \"\"\"\n\n    def to_internal_value(self, data):\n\n        if data is None:\n            return None\n\n        # Dictionary of related object attributes\n        if isinstance(data, dict):\n            params = dict_to_filter_params(data)\n            try:\n                return self.Meta.model.objects.get(**params)\n            except ObjectDoesNotExist:\n                raise ValidationError(\n                    \"Related object not found using the provided attributes: {}\".format(params)\n                )\n            except MultipleObjectsReturned:\n                raise ValidationError(\n                    \"Multiple objects match the provided attributes: {}\".format(params)\n                )\n            except FieldError as e:\n                raise ValidationError(e)\n\n        # Integer PK of related object\n        if isinstance(data, int):\n            pk = data\n        else:\n            try:\n                # PK might have been mistakenly passed as a string\n                pk = int(data)\n            except (TypeError, ValueError):\n                raise ValidationError(\n                    \"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an \"\n                    \"unrecognized value: {}\".format(data)\n                )\n\n        # Look up object by PK\n        try:\n            return self.Meta.model.objects.get(pk=int(data))\n        except ObjectDoesNotExist:\n            raise ValidationError(\n                \"Related object not found using the provided numeric ID: {}\".format(pk)\n            )\n\n\n#\n# Viewsets\n#\n\nclass ModelViewSet(_ModelViewSet):\n    \"\"\"\n    Accept either a single object or a list of objects to create.\n    \"\"\"\n    def get_serializer(self, *args, **kwargs):\n\n        # If a list of objects has been provided, initialize the serializer with many=True\n        if isinstance(kwargs.get('data', {}), list):\n            kwargs['many'] = True\n\n        return super().get_serializer(*args, **kwargs)\n\n    def get_serializer_class(self):\n\n        # If 'brief' has been passed as a query param, find and return the nested serializer for this model, if one\n        # exists\n        request = self.get_serializer_context()['request']\n        if request.query_params.get('brief', False):\n            try:\n                return get_serializer_for_model(self.queryset.model, prefix='Nested')\n            except SerializerNotFound:\n                pass\n\n        # Fall back to the hard-coded serializer class\n        return self.serializer_class\n\n    def dispatch(self, request, *args, **kwargs):\n        try:\n            return super().dispatch(request, *args, **kwargs)\n        except ProtectedError as e:\n            models = ['{} ({})'.format(o, o._meta) for o in e.protected_objects.all()]\n            msg = 'Unable to delete object. The following dependent objects were found: {}'.format(', '.join(models))\n            return self.finalize_response(\n                request,\n                Response({'detail': msg}, status=409),\n                *args,\n                **kwargs\n            )\n\n    def list(self, *args, **kwargs):\n        \"\"\"\n        Call to super to allow for caching\n        \"\"\"\n        return super().list(*args, **kwargs)\n\n    def retrieve(self, *args, **kwargs):\n        \"\"\"\n        Call to super to allow for caching\n        \"\"\"\n        return super().retrieve(*args, **kwargs)\n\n\nclass FieldChoicesViewSet(ViewSet):\n    \"\"\"\n    Expose the built-in numeric values which represent static choices for a model's field.\n    \"\"\"\n    permission_classes = [IsAuthenticatedOrLoginNotRequired]\n    fields = []\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        # Compile a dict of all fields in this view\n        self._fields = OrderedDict()\n        for cls, field_list in self.fields:\n            for field_name in field_list:\n\n                model_name = cls._meta.verbose_name.lower().replace(' ', '-')\n                key = ':'.join([model_name, field_name])\n\n                serializer = get_serializer_for_model(cls)()\n                choices = []\n\n                for k, v in serializer.get_fields()[field_name].choices.items():\n                    if type(v) in [list, tuple]:\n                        for k2, v2 in v:\n                            choices.append({\n                                'value': k2,\n                                'label': v2,\n                            })\n                    else:\n                        choices.append({\n                            'value': k,\n                            'label': v,\n                        })\n                self._fields[key] = choices\n\n    def list(self, request):\n        return Response(self._fields)\n\n    def retrieve(self, request, pk):\n        if pk not in self._fields:\n            raise Http404\n        return Response(self._fields[pk])\n\n    def get_view_name(self):\n        return \"Field Choices\"\n", "path": "netbox/utilities/api.py"}]}