Răsfoiți Sursa

Fix {module} placeholder resolution in module bay position field (#21752)

* Fix {module} placeholder resolution in module bay position field (#20467)

The {module} placeholder in ModuleBayTemplate's position field was not
being resolved when a module was installed, leaving the literal string
"{module}" in the position. This adds a resolve_position() method and
calls it in instantiate(), consistent with how resolve_name() and
resolve_label() already work.

Consolidates the shared resolution logic into _resolve_module_placeholder()
to eliminate duplication across resolve_name, resolve_label, and the new
resolve_position.

Fixes: #20467

* Move resolve_position() to ModuleBayTemplate

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Mark Robert Coleman 1 zi în urmă
părinte
comite
e76203238d

+ 14 - 21
netbox/dcim/models/device_component_templates.py

@@ -177,29 +177,19 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
         modules.reverse()
         return modules
 
+    def _resolve_module_placeholder(self, value, module):
+        if MODULE_TOKEN not in value or not module:
+            return value
+        modules = self._get_module_tree(module)
+        for m in modules:
+            value = value.replace(MODULE_TOKEN, m.module_bay.position, 1)
+        return value
+
     def resolve_name(self, module):
-        if MODULE_TOKEN not in self.name:
-            return self.name
-
-        if module:
-            modules = self._get_module_tree(module)
-            name = self.name
-            for module in modules:
-                name = name.replace(MODULE_TOKEN, module.module_bay.position, 1)
-            return name
-        return self.name
+        return self._resolve_module_placeholder(self.name, module)
 
     def resolve_label(self, module):
-        if MODULE_TOKEN not in self.label:
-            return self.label
-
-        if module:
-            modules = self._get_module_tree(module)
-            label = self.label
-            for module in modules:
-                label = label.replace(MODULE_TOKEN, module.module_bay.position, 1)
-            return label
-        return self.label
+        return self._resolve_module_placeholder(self.label, module)
 
 
 class ConsolePortTemplate(ModularComponentTemplateModel):
@@ -729,11 +719,14 @@ class ModuleBayTemplate(ModularComponentTemplateModel):
         verbose_name = _('module bay template')
         verbose_name_plural = _('module bay templates')
 
+    def resolve_position(self, module):
+        return self._resolve_module_placeholder(self.position, module)
+
     def instantiate(self, **kwargs):
         return self.component_model(
             name=self.resolve_name(kwargs.get('module')),
             label=self.resolve_label(kwargs.get('module')),
-            position=self.position,
+            position=self.resolve_position(kwargs.get('module')),
             **kwargs
         )
     instantiate.do_not_call_in_templates = True

+ 44 - 0
netbox/dcim/tests/test_models.py

@@ -849,6 +849,50 @@ class ModuleBayTestCase(TestCase):
         nested_bay = module.modulebays.get(name='SFP A-21')
         self.assertEqual(nested_bay.label, 'A-21')
 
+    @tag('regression')  # #20467
+    def test_nested_module_bay_position_resolution(self):
+        """Test that {module} in a module bay template's position field is resolved when the module is installed."""
+        manufacturer = Manufacturer.objects.first()
+        site = Site.objects.first()
+        device_role = DeviceRole.objects.first()
+
+        device_type = DeviceType.objects.create(
+            manufacturer=manufacturer,
+            model='Device with Position Test',
+            slug='device-with-position-test'
+        )
+        ModuleBayTemplate.objects.create(
+            device_type=device_type,
+            name='Slot 1',
+            position='1'
+        )
+
+        module_type = ModuleType.objects.create(
+            manufacturer=manufacturer,
+            model='Module with Position Placeholder'
+        )
+        ModuleBayTemplate.objects.create(
+            module_type=module_type,
+            name='Sub-bay {module}-1',
+            position='{module}-1'
+        )
+
+        device = Device.objects.create(
+            name='Position Test Device',
+            device_type=device_type,
+            role=device_role,
+            site=site
+        )
+        module_bay = device.modulebays.get(name='Slot 1')
+        module = Module.objects.create(
+            device=device,
+            module_bay=module_bay,
+            module_type=module_type
+        )
+
+        nested_bay = module.modulebays.get(name='Sub-bay 1-1')
+        self.assertEqual(nested_bay.position, '1-1')
+
     @tag('regression')  # #20912
     def test_module_bay_parent_cleared_when_module_removed(self):
         """Test that the parent field is properly cleared when a module bay's module assignment is removed"""