From 3a10ea61e08ec80c5eddfb99460ce22d18eaf149 Mon Sep 17 00:00:00 2001 From: Ryan Gillespie <24619595+nopg@users.noreply.github.com> Date: Wed, 17 Apr 2024 03:22:22 +0000 Subject: [PATCH 1/5] Fixes #15717: Allow VM with Site to Cluster without Site --- netbox/dcim/tests/test_models.py | 58 +++++++++++++++++++ .../virtualization/models/virtualmachines.py | 2 +- netbox/virtualization/tests/test_models.py | 3 + 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index cab1760ed10..37d6aeb8c13 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -8,6 +8,7 @@ from extras.models import CustomField from tenancy.models import Tenant from utilities.data import drange +from virtualization.models import Cluster, ClusterType class LocationTestCase(TestCase): @@ -533,6 +534,63 @@ def test_device_duplicate_names(self): device2.full_clean() device2.save() + def test_device_mismatched_site_cluster(self): + cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') + Cluster.objects.create(name='Cluster 1', type=cluster_type) + + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + ) + Site.objects.bulk_create(sites) + + clusters = ( + Cluster(name='Cluster 1', type=cluster_type, site=sites[0]), + Cluster(name='Cluster 2', type=cluster_type, site=sites[1]), + Cluster(name='Cluster 3', type=cluster_type, site=None), + ) + Cluster.objects.bulk_create(clusters) + + device_type = DeviceType.objects.first() + device_role = DeviceRole.objects.first() + + # Device with site only should pass + Device(name='device1', site=sites[0], device_type=device_type, role=device_role).full_clean() + + # Device with site, cluster non-site should pass + Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[2]).full_clean() + + # Device with non-site cluster only should pass + Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[2]).full_clean() + + # Device with mismatched site & cluster should fail + with self.assertRaises(ValidationError): + Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[1]).full_clean() + + def test_old_device_role_field(self): + """ + Ensure that the old device role field sets the value in the new role field. + """ + + # Test getter method + device = Device( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + role=DeviceRole.objects.first(), + name='Test Device 1', + device_role=DeviceRole.objects.first() + ) + device.full_clean() + device.save() + + self.assertEqual(device.role, device.device_role) + + # Test setter method + device.device_role = DeviceRole.objects.last() + device.full_clean() + device.save() + self.assertEqual(device.role, device.device_role) + class CableTestCase(TestCase): diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py index 92f1a947234..2ca1599bfe4 100644 --- a/netbox/virtualization/models/virtualmachines.py +++ b/netbox/virtualization/models/virtualmachines.py @@ -180,7 +180,7 @@ def clean(self): }) # Validate site for cluster & device - if self.cluster and self.site and self.cluster.site != self.site: + if self.cluster and self.cluster.site is not None and self.cluster.site != self.site: raise ValidationError({ 'cluster': _( 'The selected cluster ({cluster}) is not assigned to this site ({site}).' diff --git a/netbox/virtualization/tests/test_models.py b/netbox/virtualization/tests/test_models.py index c94ff930eae..a4e8d794720 100644 --- a/netbox/virtualization/tests/test_models.py +++ b/netbox/virtualization/tests/test_models.py @@ -63,6 +63,9 @@ def test_vm_mismatched_site_cluster(self): # VM with site only should pass VirtualMachine(name='vm1', site=sites[0]).full_clean() + # VM with site, cluster non-site should pass + VirtualMachine(name='vm1', site=sites[0], cluster=clusters[2]).full_clean() + # VM with non-site cluster only should pass VirtualMachine(name='vm1', cluster=clusters[2]).full_clean() From ea2728648057ac1265899307b952df711a2be439 Mon Sep 17 00:00:00 2001 From: Ryan Gillespie <24619595+nopg@users.noreply.github.com> Date: Thu, 18 Apr 2024 03:29:08 +0000 Subject: [PATCH 2/5] Fixes #15717: Allow VM with Site to Cluster without Site --- netbox/dcim/tests/test_models.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 37d6aeb8c13..4bb71643f81 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -560,9 +560,6 @@ def test_device_mismatched_site_cluster(self): # Device with site, cluster non-site should pass Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[2]).full_clean() - # Device with non-site cluster only should pass - Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[2]).full_clean() - # Device with mismatched site & cluster should fail with self.assertRaises(ValidationError): Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[1]).full_clean() From b816a5d0c6ca0fab2d11c926496e67304f1b39c3 Mon Sep 17 00:00:00 2001 From: Ryan Gillespie <24619595+nopg@users.noreply.github.com> Date: Thu, 23 May 2024 17:00:06 +0000 Subject: [PATCH 3/5] Fixes #15717: Allow VM with Site to Cluster without Site --- netbox/dcim/tests/test_models.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 4bb71643f81..9056a66c07f 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -564,30 +564,6 @@ def test_device_mismatched_site_cluster(self): with self.assertRaises(ValidationError): Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[1]).full_clean() - def test_old_device_role_field(self): - """ - Ensure that the old device role field sets the value in the new role field. - """ - - # Test getter method - device = Device( - site=Site.objects.first(), - device_type=DeviceType.objects.first(), - role=DeviceRole.objects.first(), - name='Test Device 1', - device_role=DeviceRole.objects.first() - ) - device.full_clean() - device.save() - - self.assertEqual(device.role, device.device_role) - - # Test setter method - device.device_role = DeviceRole.objects.last() - device.full_clean() - device.save() - self.assertEqual(device.role, device.device_role) - class CableTestCase(TestCase): From 3311a97be0d408d424e88c7f845f55ddcf3b80a9 Mon Sep 17 00:00:00 2001 From: Ryan Gillespie <24619595+nopg@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:02:49 +0000 Subject: [PATCH 4/5] Fixes #15717: Allow VM with Site to Cluster without Site --- netbox/virtualization/forms/model_forms.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py index bfdfc9ada04..f91dae79a47 100644 --- a/netbox/virtualization/forms/model_forms.py +++ b/netbox/virtualization/forms/model_forms.py @@ -177,9 +177,6 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm): queryset=Cluster.objects.all(), required=False, selector=True, - query_params={ - 'site_id': '$site', - } ) device = DynamicModelChoiceField( label=_('Device'), From 63093661640a8c14ac5a184cd9c5049452cdcbbc Mon Sep 17 00:00:00 2001 From: Ryan Gillespie <24619595+nopg@users.noreply.github.com> Date: Wed, 12 Jun 2024 04:48:04 +0000 Subject: [PATCH 5/5] Fixes #15717: Allow VM with Site to Cluster without Site --- netbox/dcim/forms/model_forms.py | 5 ++++- netbox/virtualization/forms/model_forms.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index d5cc0e85651..c12ddccde67 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -465,7 +465,10 @@ class DeviceForm(TenancyForm, NetBoxModelForm): label=_('Cluster'), queryset=Cluster.objects.all(), required=False, - selector=True + selector=True, + query_params={ + 'site_id': ['$site', 'null'] + }, ) comments = CommentField() local_context_data = JSONField( diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py index f91dae79a47..5ea8a4614aa 100644 --- a/netbox/virtualization/forms/model_forms.py +++ b/netbox/virtualization/forms/model_forms.py @@ -177,6 +177,9 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm): queryset=Cluster.objects.all(), required=False, selector=True, + query_params={ + 'site_id': ['$site', 'null'] + }, ) device = DynamicModelChoiceField( label=_('Device'),