From 4d40699f2c80b42124ec22d7b1b1caf0b69f80df Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 May 2024 14:04:09 -0400 Subject: [PATCH] Fixes #15995: Permit nullable fields referenced by unique constraints to be omitted from REST API requests --- netbox/circuits/api/serializers_/circuits.py | 2 +- netbox/circuits/api/serializers_/providers.py | 1 + netbox/circuits/tests/test_api.py | 4 ++-- netbox/dcim/api/serializers_/devices.py | 1 + netbox/dcim/api/serializers_/sites.py | 2 +- netbox/dcim/tests/test_api.py | 4 ++-- netbox/tenancy/api/serializers_/tenants.py | 2 +- .../virtualization/api/serializers_/virtualmachines.py | 9 ++++----- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/netbox/circuits/api/serializers_/circuits.py b/netbox/circuits/api/serializers_/circuits.py index b59c73f09..a0d0e5e13 100644 --- a/netbox/circuits/api/serializers_/circuits.py +++ b/netbox/circuits/api/serializers_/circuits.py @@ -48,7 +48,7 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer): class CircuitSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail') provider = ProviderSerializer(nested=True) - provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True) + provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None) status = ChoiceField(choices=CircuitStatusChoices, required=False) type = CircuitTypeSerializer(nested=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True) diff --git a/netbox/circuits/api/serializers_/providers.py b/netbox/circuits/api/serializers_/providers.py index 302c2da5a..fa4489787 100644 --- a/netbox/circuits/api/serializers_/providers.py +++ b/netbox/circuits/api/serializers_/providers.py @@ -45,6 +45,7 @@ class ProviderSerializer(NetBoxModelSerializer): class ProviderAccountSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provideraccount-detail') provider = ProviderSerializer(nested=True) + name = serializers.CharField(allow_blank=True, max_length=100, required=False, default='') class Meta: model = ProviderAccount diff --git a/netbox/circuits/tests/test_api.py b/netbox/circuits/tests/test_api.py index c8ec08943..d3745f2b1 100644 --- a/netbox/circuits/tests/test_api.py +++ b/netbox/circuits/tests/test_api.py @@ -141,7 +141,7 @@ class CircuitTest(APIViewTestCases.APIViewTestCase): { 'cid': 'Circuit 6', 'provider': providers[1].pk, - 'provider_account': provider_accounts[1].pk, + # Omit provider account to test uniqueness constraint 'type': circuit_types[1].pk, }, ] @@ -237,7 +237,7 @@ class ProviderAccountTest(APIViewTestCases.APIViewTestCase): 'account': '5678', }, { - 'name': 'Provider Account 6', + # Omit name to test uniqueness constraint 'provider': providers[0].pk, 'account': '6789', }, diff --git a/netbox/dcim/api/serializers_/devices.py b/netbox/dcim/api/serializers_/devices.py index 7d1601882..edfac3072 100644 --- a/netbox/dcim/api/serializers_/devices.py +++ b/netbox/dcim/api/serializers_/devices.py @@ -122,6 +122,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer): class VirtualDeviceContextSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualdevicecontext-detail') device = DeviceSerializer(nested=True) + identifier = serializers.IntegerField(allow_null=True, max_value=32767, min_value=0, required=False, default=None) tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None) primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True) primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True) diff --git a/netbox/dcim/api/serializers_/sites.py b/netbox/dcim/api/serializers_/sites.py index 8063278a7..cf30ca44c 100644 --- a/netbox/dcim/api/serializers_/sites.py +++ b/netbox/dcim/api/serializers_/sites.py @@ -83,7 +83,7 @@ class SiteSerializer(NetBoxModelSerializer): class LocationSerializer(NestedGroupModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail') site = SiteSerializer(nested=True) - parent = NestedLocationSerializer(required=False, allow_null=True) + parent = NestedLocationSerializer(required=False, allow_null=True, default=None) status = ChoiceField(choices=LocationStatusChoices, required=False) tenant = TenantSerializer(nested=True, required=False, allow_null=True) rack_count = serializers.IntegerField(read_only=True) diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 0a3931696..cc6165370 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -230,7 +230,7 @@ class LocationTest(APIViewTestCases.APIViewTestCase): 'name': 'Test Location 6', 'slug': 'test-location-6', 'site': sites[1].pk, - 'parent': parent_locations[1].pk, + # Omit parent to test uniqueness constraint 'status': LocationStatusChoices.STATUS_PLANNED, }, ] @@ -2307,6 +2307,6 @@ class VirtualDeviceContextTest(APIViewTestCases.APIViewTestCase): 'device': devices[1].pk, 'status': 'active', 'name': 'VDC 3', - 'identifier': 3, + # Omit identifier to test uniqueness constraint }, ] diff --git a/netbox/tenancy/api/serializers_/tenants.py b/netbox/tenancy/api/serializers_/tenants.py index 3ee238c90..10b38076e 100644 --- a/netbox/tenancy/api/serializers_/tenants.py +++ b/netbox/tenancy/api/serializers_/tenants.py @@ -27,7 +27,7 @@ class TenantGroupSerializer(NestedGroupModelSerializer): class TenantSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail') - group = TenantGroupSerializer(nested=True, required=False, allow_null=True) + group = TenantGroupSerializer(nested=True, required=False, allow_null=True, default=None) # Related object counts circuit_count = RelatedObjectCountField('circuits') diff --git a/netbox/virtualization/api/serializers_/virtualmachines.py b/netbox/virtualization/api/serializers_/virtualmachines.py index fb2ed0479..53146e44a 100644 --- a/netbox/virtualization/api/serializers_/virtualmachines.py +++ b/netbox/virtualization/api/serializers_/virtualmachines.py @@ -31,11 +31,11 @@ __all__ = ( class VirtualMachineSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail') status = ChoiceField(choices=VirtualMachineStatusChoices, required=False) - site = SiteSerializer(nested=True, required=False, allow_null=True) - cluster = ClusterSerializer(nested=True, required=False, allow_null=True) - device = DeviceSerializer(nested=True, required=False, allow_null=True) + site = SiteSerializer(nested=True, required=False, allow_null=True, default=None) + cluster = ClusterSerializer(nested=True, required=False, allow_null=True, default=None) + device = DeviceSerializer(nested=True, required=False, allow_null=True, default=None) role = DeviceRoleSerializer(nested=True, required=False, allow_null=True) - tenant = TenantSerializer(nested=True, required=False, allow_null=True) + tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None) platform = PlatformSerializer(nested=True, required=False, allow_null=True) primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True) primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True) @@ -55,7 +55,6 @@ class VirtualMachineSerializer(NetBoxModelSerializer): 'interface_count', 'virtual_disk_count', ] brief_fields = ('id', 'url', 'display', 'name', 'description') - validators = [] class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):