Address sigprof's Jan 20 feedback
Some checks are pending
CI / build (20.x, 3.12) (push) Waiting to run
CI / build (20.x, 3.13) (push) Waiting to run
CI / build (20.x, 3.14) (push) Waiting to run

1. Add validation to reject mixing {module} and {module_path} in same attribute
2. Refactor resolve_position() to match resolve_name()/resolve_label() pattern
   - Moved to ModuleBayTemplate where it can access self.position directly
   - No longer takes position as argument
3. Added test for mixed placeholder validation
This commit is contained in:
Mark Coleman
2026-01-20 10:15:12 +01:00
parent 3680b0ccd4
commit 898fe8b3d8
3 changed files with 76 additions and 15 deletions

View File

@@ -130,6 +130,12 @@ class ModuleCommonForm(forms.Form):
_("Cannot install module with placeholder values in a module bay with no position defined.") _("Cannot install module with placeholder values in a module bay with no position defined.")
) )
# Cannot mix {module} and {module_path} in the same attribute
if has_module_token and has_module_path_token:
raise forms.ValidationError(
_("Cannot mix {module} and {module_path} placeholders in the same template attribute.")
)
# Validate {module_path} - can only appear once # Validate {module_path} - can only appear once
if has_module_path_token: if has_module_path_token:
path_token_count = template.name.count(MODULE_PATH_TOKEN) path_token_count = template.name.count(MODULE_PATH_TOKEN)

View File

@@ -184,20 +184,6 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
return resolve_module_placeholders(self.label, positions) return resolve_module_placeholders(self.label, positions)
return self.label return self.label
def resolve_position(self, position, module):
"""
Resolve {module} placeholder in position field.
This is used by ModuleBayTemplate to resolve positions like "{module}/1"
to actual values like "A/1" when the parent module is installed in bay "A".
Fixes Issue #20467.
"""
if module:
positions = [m.module_bay.position for m in self._get_module_tree(module)]
return resolve_module_placeholders(position, positions)
return position
class ConsolePortTemplate(ModularComponentTemplateModel): class ConsolePortTemplate(ModularComponentTemplateModel):
""" """
@@ -726,12 +712,26 @@ class ModuleBayTemplate(ModularComponentTemplateModel):
verbose_name = _('module bay template') verbose_name = _('module bay template')
verbose_name_plural = _('module bay templates') verbose_name_plural = _('module bay templates')
def resolve_position(self, module):
"""
Resolve {module} and {module_path} placeholders in position field.
This allows positions like "{module}/1" to resolve to "A/1" when
the parent module is installed in bay "A".
Fixes Issue #20467.
"""
if module:
positions = [m.module_bay.position for m in self._get_module_tree(module)]
return resolve_module_placeholders(self.position, positions)
return self.position
def instantiate(self, **kwargs): def instantiate(self, **kwargs):
module = kwargs.get('module') module = kwargs.get('module')
return self.component_model( return self.component_model(
name=self.resolve_name(module), name=self.resolve_name(module),
label=self.resolve_label(module), label=self.resolve_label(module),
position=self.resolve_position(self.position, module), position=self.resolve_position(module),
**kwargs **kwargs
) )
instantiate.do_not_call_in_templates = True instantiate.do_not_call_in_templates = True

View File

@@ -2045,6 +2045,61 @@ class ModuleBayTestCase(TestCase):
self.assertFalse(form.is_valid()) self.assertFalse(form.is_valid())
def test_mixed_module_and_module_path_fails_validation(self):
"""
Test that mixing {module} and {module_path} in the same template attribute
fails validation. Per sigprof's feedback, these cannot be combined.
"""
from dcim.forms import ModuleForm
manufacturer = Manufacturer.objects.first()
site = Site.objects.first()
device_role = DeviceRole.objects.first()
device_type = DeviceType.objects.create(
manufacturer=manufacturer,
model='Mixed Placeholder Chassis',
slug='mixed-placeholder-chassis'
)
ModuleBayTemplate.objects.create(
device_type=device_type,
name='Bay 1',
position='1'
)
# Module type with both {module} and {module_path} - should fail
bad_module_type = ModuleType.objects.create(
manufacturer=manufacturer,
model='Bad Mixed Module'
)
InterfaceTemplate.objects.create(
module_type=bad_module_type,
name='{module_path}-{module}',
type=InterfaceTypeChoices.TYPE_10GE_SFP_PLUS
)
device = Device.objects.create(
name='Mixed Placeholder Device',
device_type=device_type,
role=device_role,
site=site
)
bay = device.modulebays.get(name='Bay 1')
form = ModuleForm(data={
'device': device.pk,
'module_bay': bay.pk,
'module_type': bad_module_type.pk,
'status': 'active',
'replicate_components': True,
'adopt_components': False,
})
self.assertFalse(form.is_valid())
# Verify it's specifically the mixed placeholder error
self.assertIn('Cannot mix', str(form.errors))
class CableTestCase(TestCase): class CableTestCase(TestCase):