diff --git a/netbox/virtualization/apps.py b/netbox/virtualization/apps.py index ebcc591bf..65ce0f112 100644 --- a/netbox/virtualization/apps.py +++ b/netbox/virtualization/apps.py @@ -17,7 +17,7 @@ class VirtualizationConfig(AppConfig): # Register denormalized fields denormalized.register(VirtualMachine, 'cluster', { - 'site': 'site', + 'site': '_site', }) # Register counters diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py index c18d784b4..3a1517ca6 100644 --- a/netbox/virtualization/forms/model_forms.py +++ b/netbox/virtualization/forms/model_forms.py @@ -123,6 +123,12 @@ class ClusterForm(TenancyForm, NetBoxModelForm): if self.instance and scope_type_id != self.instance.scope_type_id: self.initial['scope'] = None + def clean(self): + super().clean() + + # Assign the selected scope (if any) + self.instance.scope = self.cleaned_data.get('scope') + class ClusterAddDevicesForm(forms.Form): region = DynamicModelChoiceField( diff --git a/netbox/virtualization/models/clusters.py b/netbox/virtualization/models/clusters.py index da4a68df5..16ac650b3 100644 --- a/netbox/virtualization/models/clusters.py +++ b/netbox/virtualization/models/clusters.py @@ -164,13 +164,21 @@ class Cluster(ContactsMixin, PrimaryModel): def clean(self): super().clean() + site = None + if self.scope_type: + scope_type = self.scope_type.model_class() + if scope_type == apps.get_model('dcim', 'site'): + site = self.scope + elif scope_type == apps.get_model('dcim', 'location'): + site = self.scope.site + # If the Cluster is assigned to a Site, verify that all host Devices belong to that Site. - if not self._state.adding and self.site: - if nonsite_devices := Device.objects.filter(cluster=self).exclude(site=self.site).count(): + if not self._state.adding and site: + if nonsite_devices := Device.objects.filter(cluster=self).exclude(site=site).count(): raise ValidationError({ 'site': _( "{count} devices are assigned as hosts for this cluster but are not in site {site}" - ).format(count=nonsite_devices, site=self.site) + ).format(count=nonsite_devices, site=site) }) def save(self, *args, **kwargs): diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py index e7bdc139f..a48feddb9 100644 --- a/netbox/virtualization/models/virtualmachines.py +++ b/netbox/virtualization/models/virtualmachines.py @@ -181,7 +181,7 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co }) # Validate site for cluster & VM - if self.cluster and self.site and self.cluster.site and self.cluster.site != self.site: + if self.cluster and self.site and self.cluster._site 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_api.py b/netbox/virtualization/tests/test_api.py index a30cf0910..d11b46b66 100644 --- a/netbox/virtualization/tests/test_api.py +++ b/netbox/virtualization/tests/test_api.py @@ -157,11 +157,12 @@ class VirtualMachineTest(APIViewTestCases.APIViewTestCase): Site.objects.bulk_create(sites) clusters = ( - Cluster(name='Cluster 1', type=clustertype, site=sites[0], group=clustergroup), - Cluster(name='Cluster 2', type=clustertype, site=sites[1], group=clustergroup), + Cluster(name='Cluster 1', type=clustertype, scope=sites[0], group=clustergroup), + Cluster(name='Cluster 2', type=clustertype, scope=sites[1], group=clustergroup), Cluster(name='Cluster 3', type=clustertype), ) - Cluster.objects.bulk_create(clusters) + for cluster in clusters: + cluster.save() device1 = create_test_device('device1', site=sites[0], cluster=clusters[0]) device2 = create_test_device('device2', site=sites[1], cluster=clusters[1]) diff --git a/netbox/virtualization/tests/test_models.py b/netbox/virtualization/tests/test_models.py index a4e8d7947..7be423bf1 100644 --- a/netbox/virtualization/tests/test_models.py +++ b/netbox/virtualization/tests/test_models.py @@ -54,11 +54,12 @@ class VirtualMachineTestCase(TestCase): 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(name='Cluster 1', type=cluster_type, scope=sites[0]), + Cluster(name='Cluster 2', type=cluster_type, scope=sites[1]), + Cluster(name='Cluster 3', type=cluster_type, scope=None), ) - Cluster.objects.bulk_create(clusters) + for cluster in clusters: + cluster.save() # VM with site only should pass VirtualMachine(name='vm1', site=sites[0]).full_clean() diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index f8f870353..b9cb7b437 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -1,3 +1,4 @@ +from django.contrib.contenttypes.models import ContentType from django.test import override_settings from django.urls import reverse from netaddr import EUI @@ -202,10 +203,11 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase): clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') clusters = ( - Cluster(name='Cluster 1', type=clustertype, site=sites[0]), - Cluster(name='Cluster 2', type=clustertype, site=sites[1]), + Cluster(name='Cluster 1', type=clustertype, scope=sites[0]), + Cluster(name='Cluster 2', type=clustertype, scope=sites[1]), ) - Cluster.objects.bulk_create(clusters) + for cluster in clusters: + cluster.save() devices = ( create_test_device('device1', site=sites[0], cluster=clusters[0]), @@ -293,7 +295,7 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): site = Site.objects.create(name='Site 1', slug='site-1') role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') - cluster = Cluster.objects.create(name='Cluster 1', type=clustertype, site=site) + cluster = Cluster.objects.create(name='Cluster 1', type=clustertype, scope=site) virtualmachines = ( VirtualMachine(name='Virtual Machine 1', site=site, cluster=cluster, role=role), VirtualMachine(name='Virtual Machine 2', site=site, cluster=cluster, role=role),