Fix {module} placeholder resolution + add {module_path} for nested modules #654

Closed
opened 2026-04-05 17:02:43 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @mrmrcoleman on 1/5/2026

Summary

Fixes the {module} placeholder resolution for nested module bays and introduces a new {module_path} placeholder for automatic full-path expansion.

Fixes: #20474, #20467, #19796

Problem

The current implementation requires an exact match between the number of {module} tokens in template names and the depth of the module bay tree. This forces users to create duplicate ModuleTypes for each nesting level, breaking the "pluggable transceivers" use case where a single SFP/QSFP module should install at any depth.

Additionally, the {module} placeholder was not resolved in the position field of module bays, leaving literal {module} strings in positions when installing nested modules.

Solution

This PR introduces two distinct placeholders:

{module_path} (NEW)

  • Expands to the full path from root to current position, joined by /
  • Example: At depth 3 with positions "1", "2", "3" → expands to "1/2/3"
  • Use case: Generic modules (SFPs, QSFPs) that work at any nesting depth without modification

{module} (UPDATED BEHAVIOR)

  • Single token: Expands to the immediate parent's position only (not full path)
  • Multiple tokens: Expands level-by-level (unchanged for backwards compatibility)
  • Use case: Building custom paths via position field with user-controlled separators

Why Two Placeholders?

This design supports two complementary workflows:

  1. Simple case: Use {module_path} in component names for automatic path joining
  2. Custom separators: Use {module} in position field to build paths like eth{module}-{position} where users control the separator in the position field itself

Validation Rules

  • Only one {module_path} allowed per template attribute
  • Cannot mix {module} and {module_path} in the same attribute
  • Multiple {module} tokens must match tree depth exactly (existing behavior)

Testing

  • All 8,375 tests pass locally
  • Added comprehensive test coverage for:
    • {module_path} at depths 1, 2, and 3
    • Single {module} parent-only behavior
    • Position field resolution with {module} and {module_path}
    • Validation rules for both placeholders
    • Edge case: nested position resolution without duplication (sigprof's scenario)

Why This Targets feature Branch

This PR targets feature because it introduces new functionality ({module_path} placeholder). However, all changes are backwards compatible:

  • Existing single {module} usage now gives parent position (previously gave full path for depth-1 only)
  • Multiple {module} behavior is unchanged
  • No existing workflows break

To target main for 4.5.x: The behavioral change for single {module} (parent-only vs. full-path) is technically a change, but since the previous behavior was problematic for nested modules anyway, this could be considered a bug fix. Maintainers can decide if this warrants backporting.

Changes

  • dcim/constants.py: Added MODULE_PATH_TOKEN, resolve_module_placeholders() helper
  • dcim/forms/common.py: Updated validation for both placeholder types
  • dcim/models/device_component_templates.py: Uses centralized helper for name/label/position resolution
  • dcim/tests/test_models.py: Comprehensive test coverage for all scenarios
*Originally created by @mrmrcoleman on 1/5/2026* ## Summary Fixes the `{module}` placeholder resolution for nested module bays and introduces a new `{module_path}` placeholder for automatic full-path expansion. **Fixes:** #20474, #20467, #19796 ## Problem The current implementation requires an **exact match** between the number of `{module}` tokens in template names and the depth of the module bay tree. This forces users to create duplicate ModuleTypes for each nesting level, breaking the "pluggable transceivers" use case where a single SFP/QSFP module should install at any depth. Additionally, the `{module}` placeholder was not resolved in the `position` field of module bays, leaving literal `{module}` strings in positions when installing nested modules. ## Solution This PR introduces **two distinct placeholders**: ### `{module_path}` (NEW) - Expands to the **full path** from root to current position, joined by `/` - Example: At depth 3 with positions "1", "2", "3" → expands to "1/2/3" - **Use case:** Generic modules (SFPs, QSFPs) that work at any nesting depth without modification ### `{module}` (UPDATED BEHAVIOR) - **Single token:** Expands to the **immediate parent's position only** (not full path) - **Multiple tokens:** Expands **level-by-level** (unchanged for backwards compatibility) - **Use case:** Building custom paths via position field with user-controlled separators ### Why Two Placeholders? This design supports two complementary workflows: 1. **Simple case:** Use `{module_path}` in component names for automatic path joining 2. **Custom separators:** Use `{module}` in position field to build paths like `eth{module}-{position}` where users control the separator in the position field itself ### Validation Rules - Only one `{module_path}` allowed per template attribute - Cannot mix `{module}` and `{module_path}` in the same attribute - Multiple `{module}` tokens must match tree depth exactly (existing behavior) ## Testing - All **8,375 tests pass** locally - Added comprehensive test coverage for: - `{module_path}` at depths 1, 2, and 3 - Single `{module}` parent-only behavior - Position field resolution with `{module}` and `{module_path}` - Validation rules for both placeholders - Edge case: nested position resolution without duplication (sigprof's scenario) ## Why This Targets `feature` Branch This PR targets `feature` because it introduces new functionality (`{module_path}` placeholder). However, **all changes are backwards compatible**: - Existing single `{module}` usage now gives parent position (previously gave full path for depth-1 only) - Multiple `{module}` behavior is unchanged - No existing workflows break **To target `main` for 4.5.x:** The behavioral change for single `{module}` (parent-only vs. full-path) is technically a change, but since the previous behavior was problematic for nested modules anyway, this could be considered a bug fix. Maintainers can decide if this warrants backporting. ## Changes - **`dcim/constants.py`:** Added `MODULE_PATH_TOKEN`, `resolve_module_placeholders()` helper - **`dcim/forms/common.py`:** Updated validation for both placeholder types - **`dcim/models/device_component_templates.py`:** Uses centralized helper for name/label/position resolution - **`dcim/tests/test_models.py`:** Comprehensive test coverage for all scenarios
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/netbox#654