Repository: netbox-community/netbox

Issue:
ValueError in web UI after REST API accepts invalid custom-field choice-set data
### Deployment Type

Self-hosted

### NetBox Version

v3.7.0

### Python Version

3.10

### Steps to Reproduce

1. Start with a fresh NetBox 3.7.0 installation and database
2. Post a custom-field choice-set whose choices are lists with just one element each (should be two elements each):

```
curl -H "Authorization: Token ${NETBOX_TOKEN}" \
  'http://192.168.64.3/api/extras/custom-field-choice-sets/' \
  -H 'accept: application/json'   -H 'Content-Type: application/json' \
  -d '[ { "name": "test1", "extra_choices": [ ["choice1"], ["choice2"] ] } ]'
```

3. Log in to the web UI, navigate to _Customization_ -> _Custom Field Choices_, and click on the __test1__ entry

### Expected Behavior

The REST API should reject the POST data as invalid

**AND/OR**

The web UI should render a dialog showing that the choice set's choices have undefined labels


### Observed Behavior

The REST API cheerfully creates the choice set with the choice labels undefined.

**AND**

The web UI displays an unstyled `ValueError`:

```
<class 'ValueError'>

Need 2 values to unpack in for loop; got 1. 

Python version: 3.10.12
NetBox version: 3.7.0
Plugins: None installed
```

This problem was originally reported by @jordanrvillarreal.


Discussion:

Resolving patch:
diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py
--- a/netbox/extras/api/serializers.py
+++ b/netbox/extras/api/serializers.py
@@ -3,6 +3,7 @@
 from drf_spectacular.types import OpenApiTypes
 from drf_spectacular.utils import extend_schema_field
 from rest_framework import serializers
+from rest_framework.fields import ListField
 
 from core.api.nested_serializers import NestedDataSourceSerializer, NestedDataFileSerializer, NestedJobSerializer
 from core.api.serializers import JobSerializer
@@ -171,6 +172,12 @@ class CustomFieldChoiceSetSerializer(ValidatedModelSerializer):
         choices=CustomFieldChoiceSetBaseChoices,
         required=False
     )
+    extra_choices = serializers.ListField(
+        child=serializers.ListField(
+            min_length=2,
+            max_length=2
+        )
+    )
 
     class Meta:
         model = CustomFieldChoiceSet


Test patch:
diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py
--- a/netbox/extras/tests/test_api.py
+++ b/netbox/extras/tests/test_api.py
@@ -14,7 +14,6 @@
 from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
 from utilities.testing import APITestCase, APIViewTestCases
 
-
 User = get_user_model()
 
 
@@ -251,6 +250,23 @@ def setUpTestData(cls):
         )
         CustomFieldChoiceSet.objects.bulk_create(choice_sets)
 
+    def test_invalid_choice_items(self):
+        """
+        Attempting to define each choice as a single-item list should return a 400 error.
+        """
+        self.add_permissions('extras.add_customfieldchoiceset')
+        data = {
+            "name": "test",
+            "extra_choices": [
+                ["choice1"],
+                ["choice2"],
+                ["choice3"],
+            ]
+        }
+
+        response = self.client.post(self._get_list_url(), data, format='json', **self.header)
+        self.assertEqual(response.status_code, 400)
+
 
 class CustomLinkTest(APIViewTestCases.APIViewTestCase):
     model = CustomLink