diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index c281e5de2..771401044 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -840,7 +840,7 @@ class Device( "Location {location} does not belong to site {site}." ).format(location=self.location, site=self.site) }) - if self.rack and self.location and self.rack.location != self.location: + if self.rack and self.location and not self.location.get_descendants(True).contains(self.rack.location): raise ValidationError({ 'rack': _( "Rack {rack} does not belong to location {location}." diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index c4f7b0691..58e01ebc7 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -613,6 +613,25 @@ class DeviceTestCase(TestCase): with self.assertRaises(ValidationError): Device(name='device2', site=sites[0], location=locations[1], device_type=device_type, role=device_role).full_clean() + def test_device_mismatched_rack_location(self): + site = Site.objects.first() + + locations = ( + Location(name='Location 1', slug='location-1', site=site), + Location(name='Location 2', slug='location-2', site=site), + ) + for location in locations: + location.save() + + rack = Rack.objects.create(name='Rack 1', site=site, location=locations[0]) + + device_type = DeviceType.objects.first() + device_role = DeviceRole.objects.first() + + # Device should use location from rack + with self.assertRaises(ValidationError): + Device(name='device1', site=site, location=locations[1], rack=rack, device_type=device_type, role=device_role).full_clean() + def test_device_rack_clone_fields(self): site = Site.objects.first() location = Location(name='Location 1', slug='location-1', site=site)