From 269112a5654713779c05b70aff691164c528abc8 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Fri, 5 Dec 2025 17:43:20 -0600 Subject: [PATCH] Fixes #19918: Resolve {module} placeholders in nested module bay labels ModuleBayTemplate.instantiate() now calls resolve_name() and resolve_label() to properly resolve {module} placeholders, making it consistent with other modular components like InterfaceTemplate. When a module with nested module bays is installed (e.g., a module with SFP bays in position "A"), the nested bay labels now correctly show "A-21" instead of "{module}-21". This also removes the inconsistent fix from #17436 which only handled name resolution post-instantiation. The proper resolution now happens during instantiation using the existing resolve methods. --- .../dcim/models/device_component_templates.py | 4 +- netbox/dcim/models/modules.py | 2 - netbox/dcim/tests/test_models.py | 50 ++++++++++++++++++- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index f8f2e100f..6eea97743 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -681,8 +681,8 @@ class ModuleBayTemplate(ModularComponentTemplateModel): def instantiate(self, **kwargs): return self.component_model( - name=self.name, - label=self.label, + name=self.resolve_name(kwargs.get('module')), + label=self.resolve_label(kwargs.get('module')), position=self.position, **kwargs ) diff --git a/netbox/dcim/models/modules.py b/netbox/dcim/models/modules.py index 4376f40aa..1c3f9f730 100644 --- a/netbox/dcim/models/modules.py +++ b/netbox/dcim/models/modules.py @@ -7,7 +7,6 @@ from django.utils.translation import gettext_lazy as _ from jsonschema.exceptions import ValidationError as JSONValidationError from dcim.choices import * -from dcim.constants import MODULE_TOKEN from dcim.utils import update_interface_bridges from extras.models import ConfigContextModel, CustomField from netbox.models import PrimaryModel @@ -331,7 +330,6 @@ class Module(PrimaryModel, ConfigContextModel): else: # ModuleBays must be saved individually for MPTT for instance in create_instances: - instance.name = instance.name.replace(MODULE_TOKEN, str(self.module_bay.position)) instance.save() update_fields = ['module'] diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 877af600b..e67f32e60 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -792,8 +792,54 @@ class ModuleBayTestCase(TestCase): ) device.consoleports.first() - def test_nested_module_token(self): - pass + @tag('regression') # #19918 + def test_nested_module_bay_label_resolution(self): + """Test that nested module bay labels properly resolve {module} placeholders""" + manufacturer = Manufacturer.objects.first() + site = Site.objects.first() + device_role = DeviceRole.objects.first() + + # Create device type with module bay template (position='A') + device_type = DeviceType.objects.create( + manufacturer=manufacturer, + model='Device with Bays', + slug='device-with-bays' + ) + ModuleBayTemplate.objects.create( + device_type=device_type, + name='Bay A', + position='A' + ) + + # Create module type with nested bay template using {module} placeholder + module_type = ModuleType.objects.create( + manufacturer=manufacturer, + model='Module with Nested Bays' + ) + ModuleBayTemplate.objects.create( + module_type=module_type, + name='SFP {module}-21', + label='{module}-21', + position='21' + ) + + # Create device and install module + device = Device.objects.create( + name='Test Device', + device_type=device_type, + role=device_role, + site=site + ) + module_bay = device.modulebays.get(name='Bay A') + module = Module.objects.create( + device=device, + module_bay=module_bay, + module_type=module_type + ) + + # Verify nested bay label resolves {module} to parent position + nested_bay = module.modulebays.get(name='SFP A-21') + self.assertEqual(nested_bay.label, 'A-21') class CableTestCase(TestCase):