Closes #10500: Introduce support for nested modules (#16983)

* 10500 add ModularComponentModel

* 10500 add ModularComponentModel

* 10500 add to forms

* 10500 add to serializer, tables

* 10500 template

* 10500 add docs

* 10500 check recursion

* 10500 fix graphql

* 10500 fix conflicting migration from merge

* 10500 token resolution

* 10500 don't return reverse

* 10500 don't return reverse / optimize

* Add ModuleTypeModuleBaysView

* Fix replication of module bays on new modules

* Clean up tables & templates

* Adjust uniqueness constraints

* Correct URL

* Clean up docs

* Fix up serializers

* 10500 add filterset tests

* 10500 add nested validation to Module

* Misc cleanup

* 10500 ModuleBay recursion Test

* 10500 ModuleBay recursion Test

* 10500 ModuleBay recursion Test

* 10500 ModuleBay recursion Test

* Enable MPTT for module bays

* Fix tests

* Fix validation of module token in component names

* Misc cleanup

* Merge migrations

* Fix table ordering

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
Arthur Hanson
2024-08-06 00:13:59 +07:00
committed by GitHub
parent 57fe2071a4
commit 796b9e84af
21 changed files with 475 additions and 86 deletions

View File

@@ -620,6 +620,100 @@ class DeviceTestCase(TestCase):
Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[1]).full_clean()
class ModuleBayTestCase(TestCase):
@classmethod
def setUpTestData(cls):
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
device_type = DeviceType.objects.create(
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
)
device_role = DeviceRole.objects.create(name='Test Role 1', slug='test-role-1')
# Create a CustomField with a default value & assign it to all component models
location = Location.objects.create(name='Location 1', slug='location-1', site=site)
rack = Rack.objects.create(name='Rack 1', site=site)
device = Device.objects.create(name='Device 1', device_type=device_type, role=device_role, site=site, location=location, rack=rack)
module_bays = (
ModuleBay(device=device, name='Module Bay 1', label='A', description='First'),
ModuleBay(device=device, name='Module Bay 2', label='B', description='Second'),
ModuleBay(device=device, name='Module Bay 3', label='C', description='Third'),
)
for module_bay in module_bays:
module_bay.save()
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
modules = (
Module(device=device, module_bay=module_bays[0], module_type=module_type),
Module(device=device, module_bay=module_bays[1], module_type=module_type),
Module(device=device, module_bay=module_bays[2], module_type=module_type),
)
# M3 -> MB3 -> M2 -> MB2 -> M1 -> MB1
Module.objects.bulk_create(modules)
module_bays[1].module = modules[0]
module_bays[1].clean()
module_bays[1].save()
module_bays[2].module = modules[1]
module_bays[2].clean()
module_bays[2].save()
def test_module_bay_recursion(self):
module_bay_1 = ModuleBay.objects.get(name='Module Bay 1')
module_bay_2 = ModuleBay.objects.get(name='Module Bay 2')
module_bay_3 = ModuleBay.objects.get(name='Module Bay 3')
module_1 = Module.objects.get(module_bay=module_bay_1)
module_2 = Module.objects.get(module_bay=module_bay_2)
module_3 = Module.objects.get(module_bay=module_bay_3)
# Confirm error if ModuleBay recurses
with self.assertRaises(ValidationError):
module_bay_1.module = module_3
module_bay_1.clean()
module_bay_1.save()
# Confirm error if Module recurses
with self.assertRaises(ValidationError):
module_1.module_bay = module_bay_3
module_1.clean()
module_1.save()
def test_single_module_token(self):
module_bays = ModuleBay.objects.all()
modules = Module.objects.all()
device_type = DeviceType.objects.first()
device_role = DeviceRole.objects.first()
site = Site.objects.first()
location = Location.objects.first()
rack = Rack.objects.first()
# Create DeviceType components
ConsolePortTemplate.objects.create(
device_type=device_type,
name='{module}',
label='{module}',
)
ModuleBayTemplate.objects.create(
device_type=device_type,
name='Module Bay 1'
)
device = Device.objects.create(
name='Device 2',
device_type=device_type,
role=device_role,
site=site,
location=location,
rack=rack
)
cp = device.consoleports.first()
def test_nested_module_token(self):
pass
class CableTestCase(TestCase):
@classmethod