Просмотр исходного кода

Fixes #10241: Support referencing custom field related objects by attribute in addition to PK

jeremystretch 3 лет назад
Родитель
Сommit
d5538c1ca3

+ 1 - 0
docs/release-notes/version-3.3.md

@@ -12,6 +12,7 @@
 * [#9223](https://github.com/netbox-community/netbox/issues/9223) - Fix serialization of array field values in change log
 * [#9878](https://github.com/netbox-community/netbox/issues/9878) - Fix spurious error message when rendering REST API docs
 * [#10236](https://github.com/netbox-community/netbox/issues/10236) - Fix TypeError exception when viewing PDU configured for three-phase power
+* [#10241](https://github.com/netbox-community/netbox/issues/10241) - Support referencing custom field related objects by attribute in addition to PK
 * [#10579](https://github.com/netbox-community/netbox/issues/10579) - Mark cable traces terminating to a provider network as complete
 * [#10721](https://github.com/netbox-community/netbox/issues/10721) - Disable ordering by custom object field columns
 * [#10938](https://github.com/netbox-community/netbox/issues/10938) - `render_field` template tag should respect `label` kwarg

+ 18 - 0
netbox/extras/api/customfields.py

@@ -5,6 +5,7 @@ from rest_framework.serializers import ValidationError
 from extras.choices import CustomFieldTypeChoices
 from extras.models import CustomField
 from netbox.constants import NESTED_SERIALIZER_PREFIX
+from utilities.api import get_serializer_for_model
 
 
 #
@@ -69,6 +70,23 @@ class CustomFieldsDataField(Field):
                 "values."
             )
 
+        # Serialize object and multi-object values
+        for cf in self._get_custom_fields():
+            if cf.name in data and cf.type in (
+                    CustomFieldTypeChoices.TYPE_OBJECT,
+                    CustomFieldTypeChoices.TYPE_MULTIOBJECT
+            ):
+                serializer_class = get_serializer_for_model(
+                    model=cf.object_type.model_class(),
+                    prefix=NESTED_SERIALIZER_PREFIX
+                )
+                many = cf.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT
+                serializer = serializer_class(data=data[cf.name], many=many, context=self.parent.context)
+                if serializer.is_valid():
+                    data[cf.name] = [obj['id'] for obj in serializer.data] if many else serializer.data['id']
+                else:
+                    raise ValidationError(f"Unknown related object(s): {data[cf.name]}")
+
         # If updating an existing instance, start with existing custom_field_data
         if self.parent.instance:
             data = {**self.parent.instance.custom_field_data, **data}

+ 51 - 0
netbox/extras/tests/test_customfields.py

@@ -803,6 +803,57 @@ class CustomFieldAPITest(APITestCase):
         self.assertEqual(site2.custom_field_data['object_field'], original_cfvs['object_field'])
         self.assertEqual(site2.custom_field_data['multiobject_field'], original_cfvs['multiobject_field'])
 
+    def test_specify_related_object_by_attr(self):
+        site1 = Site.objects.get(name='Site 1')
+        vlans = VLAN.objects.all()[:3]
+        url = reverse('dcim-api:site-detail', kwargs={'pk': site1.pk})
+        self.add_permissions('dcim.change_site')
+
+        # Set related objects by PK
+        data = {
+            'custom_fields': {
+                'object_field': vlans[0].pk,
+                'multiobject_field': [vlans[1].pk, vlans[2].pk],
+            },
+        }
+        response = self.client.patch(url, data, format='json', **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(
+            response.data['custom_fields']['object_field']['id'],
+            vlans[0].pk
+        )
+        self.assertListEqual(
+            [obj['id'] for obj in response.data['custom_fields']['multiobject_field']],
+            [vlans[1].pk, vlans[2].pk]
+        )
+
+        # Set related objects by name
+        data = {
+            'custom_fields': {
+                'object_field': {
+                    'name': vlans[0].name,
+                },
+                'multiobject_field': [
+                    {
+                        'name': vlans[1].name
+                    },
+                    {
+                        'name': vlans[2].name
+                    },
+                ],
+            },
+        }
+        response = self.client.patch(url, data, format='json', **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(
+            response.data['custom_fields']['object_field']['id'],
+            vlans[0].pk
+        )
+        self.assertListEqual(
+            [obj['id'] for obj in response.data['custom_fields']['multiobject_field']],
+            [vlans[1].pk, vlans[2].pk]
+        )
+
     def test_minimum_maximum_values_validation(self):
         site2 = Site.objects.get(name='Site 2')
         url = reverse('dcim-api:site-detail', kwargs={'pk': site2.pk})