Fixes #18245: Make DeviceRole Hierarchical (#19008)

Made DeviceRoles hierarchical, had to also change the filtersets for Device, ConfigContext and VirtualMachine to use the TreeNodeMultipleChoiceFilter.

Note: The model was changed to use NestedGroupModel, a side-effect of this is it also adds comments field, but I thought that was better then doing a one-off just for DeviceRole and having to define the fields, validators, etc.. - keeps everything DRY / consistent.

* 18981 Make Device Roles Hierarchical

* 18981 forms, serializer

* 18981 fix tests

* 18981 fix tests

* 18981 fix tests

* 18981 fix tests

* 18981 fix tests

* 18981 fix migration merge

* 18981 fix tests

* 18981 fix filtersets

* 18981 fix tests

* 18981 comments

* 18981 review changes
This commit is contained in:
Arthur Hanson
2025-03-28 12:32:02 -07:00
committed by GitHub
parent 7a71c7b8f8
commit 1508e3a770
25 changed files with 327 additions and 55 deletions

View File

@@ -1149,7 +1149,9 @@ class InventoryItemTemplateTest(APIViewTestCases.APIViewTestCase):
class DeviceRoleTest(APIViewTestCases.APIViewTestCase):
model = DeviceRole
brief_fields = ['description', 'device_count', 'display', 'id', 'name', 'slug', 'url', 'virtualmachine_count']
brief_fields = [
'_depth', 'description', 'device_count', 'display', 'id', 'name', 'slug', 'url', 'virtualmachine_count'
]
create_data = [
{
'name': 'Device Role 4',
@@ -1174,12 +1176,9 @@ class DeviceRoleTest(APIViewTestCases.APIViewTestCase):
@classmethod
def setUpTestData(cls):
roles = (
DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000'),
DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00'),
DeviceRole(name='Device Role 3', slug='device-role-3', color='0000ff'),
)
DeviceRole.objects.bulk_create(roles)
DeviceRole.objects.create(name='Device Role 1', slug='device-role-1', color='ff0000')
DeviceRole.objects.create(name='Device Role 2', slug='device-role-2', color='00ff00')
DeviceRole.objects.create(name='Device Role 3', slug='device-role-3', color='0000ff')
class PlatformTest(APIViewTestCases.APIViewTestCase):
@@ -1252,7 +1251,8 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000'),
DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')

View File

@@ -2191,12 +2191,65 @@ class DeviceRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
@classmethod
def setUpTestData(cls):
roles = (
parent_roles = (
DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000', vm_role=True, description='foobar1'),
DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00', vm_role=True, description='foobar2'),
DeviceRole(name='Device Role 3', slug='device-role-3', color='0000ff', vm_role=False),
DeviceRole(name='Device Role 3', slug='device-role-3', color='0000ff', vm_role=False)
)
DeviceRole.objects.bulk_create(roles)
for role in parent_roles:
role.save()
roles = (
DeviceRole(
name='Device Role 1A',
slug='device-role-1a',
color='aa0000',
vm_role=True,
parent=parent_roles[0]
),
DeviceRole(
name='Device Role 2A',
slug='device-role-2a',
color='00aa00',
vm_role=True,
parent=parent_roles[1]
),
DeviceRole(
name='Device Role 3A',
slug='device-role-3a',
color='0000aa',
vm_role=False,
parent=parent_roles[2]
)
)
for role in roles:
role.save()
child_roles = (
DeviceRole(
name='Device Role 1A1',
slug='device-role-1a1',
color='bb0000',
vm_role=True,
parent=roles[0]
),
DeviceRole(
name='Device Role 2A1',
slug='device-role-2a1',
color='00bb00',
vm_role=True,
parent=roles[1]
),
DeviceRole(
name='Device Role 3A1',
slug='device-role-3a1',
color='0000bb',
vm_role=False,
parent=roles[2]
)
)
for role in child_roles:
role.save()
def test_q(self):
params = {'q': 'foobar1'}
@@ -2216,14 +2269,28 @@ class DeviceRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
def test_vm_role(self):
params = {'vm_role': 'true'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
params = {'vm_role': 'false'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_parent(self):
roles = DeviceRole.objects.filter(parent__isnull=True)[:2]
params = {'parent_id': [roles[0].pk, roles[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'parent': [roles[0].slug, roles[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_ancestor(self):
roles = DeviceRole.objects.filter(parent__isnull=True)[:2]
params = {'ancestor_id': [roles[0].pk, roles[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'ancestor': [roles[0].slug, roles[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = Platform.objects.all()
@@ -2309,7 +2376,8 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
platforms = (
Platform(name='Platform 1', slug='platform-1'),
@@ -2974,7 +3042,8 @@ class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
@@ -3186,7 +3255,8 @@ class ConsoleServerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeL
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
@@ -3404,7 +3474,8 @@ class PowerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
@@ -3648,7 +3719,8 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
@@ -3913,7 +3985,8 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
@@ -4492,7 +4565,8 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
@@ -4764,7 +4838,8 @@ class RearPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilt
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
@@ -5004,7 +5079,8 @@ class ModuleBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
@@ -5176,7 +5252,8 @@ class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
@@ -5311,7 +5388,8 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
regions = (
Region(name='Region 1', slug='region-1'),

View File

@@ -346,7 +346,8 @@ class DeviceTestCase(TestCase):
DeviceRole(name='Test Role 1', slug='test-role-1'),
DeviceRole(name='Test Role 2', slug='test-role-2'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
# Create a CustomField with a default value & assign it to all component models
cf1 = CustomField.objects.create(name='cf1', default='foo')

View File

@@ -1694,13 +1694,16 @@ class DeviceRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
@classmethod
def setUpTestData(cls):
roles = (
roles = [
DeviceRole(name='Device Role 1', slug='device-role-1'),
DeviceRole(name='Device Role 2', slug='device-role-2'),
DeviceRole(name='Device Role 3', slug='device-role-3'),
)
DeviceRole.objects.bulk_create(roles)
DeviceRole(name='Device Role 4', slug='device-role-4'),
]
for role in roles:
role.save()
roles.append(DeviceRole.objects.create(name='Device Role 5', slug='device-role-5', parent=roles[3]))
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
@@ -1724,6 +1727,7 @@ class DeviceRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
f"{roles[0].pk},Device Role 7,New description7",
f"{roles[1].pk},Device Role 8,New description8",
f"{roles[2].pk},Device Role 9,New description9",
f"{roles[4].pk},Device Role 10,New description10",
)
cls.bulk_edit_data = {
@@ -1809,7 +1813,8 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
DeviceRole(name='Device Role 1', slug='device-role-1'),
DeviceRole(name='Device Role 2', slug='device-role-2'),
)
DeviceRole.objects.bulk_create(roles)
for role in roles:
role.save()
platforms = (
Platform(name='Platform 1', slug='platform-1'),