Per arthanson's review request, updated docs/models/dcim/moduletype.md
to document:
- {module} placeholder behavior (single vs multiple use)
- {module_path} placeholder for full path expansion
- Position field resolution for nested module bays
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
Constants should only contain constant values, not functions with logic.
The helper function now lives in dcim/utils.py alongside other utilities
like update_interface_bridges and create_port_mappings.
Per sigprof's feedback, this implements two distinct placeholders:
- {module_path}: Always expands to full /-separated path (e.g., 1/2/3)
Use case: Generic modules like SFPs that work at any depth
- {module} (single): Expands to parent bay position only
Use case: Building custom paths via position field with user-controlled separators
- {module}/{module}: Level-by-level substitution (unchanged for backwards compat)
This design allows two ways to build module hierarchies:
1. Use {module_path} for automatic path joining (hardcodes / separator)
2. Use position field with {module} for custom separators
Fixes#20474, #20467, #19796
Per sigprof's review feedback, extract the duplicated token substitution
logic into a single resolve_module_token() helper in constants.py.
This addresses two review comments:
1. Duplication between ModuleCommonForm.clean() and resolve_name()
2. Duplication between resolve_name() and resolve_label()
Benefits:
- Single source of truth for substitution logic
- MODULE_TOKEN_SEPARATOR constant for future configurability
- Cleaner, more maintainable code (-7 net lines)
- Easier to modify separator handling in one place
Per sigprof's feedback, the previous validation (depth >= token_count)
allowed a questionable case where token_count > 1 but < depth, which
would lose position information for some levels.
New validation: token_count must be either 1 (full path expansion) or
exactly match the tree depth (level-by-level substitution).
Updated test T2 to verify this mismatched case is now rejected.
- Add resolve_position() method to ModularComponentTemplateModel
- Update ModuleBayTemplate.instantiate() to resolve {module} in position field
- Add test_module_bay_position_resolves_placeholder test
This completes the fix for nested module placeholder issues by ensuring
the position field also resolves {module} placeholders, which is required
for building correct full paths in 3+ level hierarchies.
PluginMenuItem and PluginMenuButton classes used mutable class-level
defaults for `permissions` and `buttons` attributes, causing permission
leakage between instances when these attributes were modified without
explicit parameters.
Changed to initialize these attributes as fresh lists per instance in
__init__ when not explicitly provided, following standard Python pattern
for avoiding mutable default arguments.
@@ -16,9 +16,33 @@ Note that device bays and module bays may _not_ be added to modules.
## Automatic Component Renaming
When adding component templates to a module type, the string `{module}` can be used to reference the `position` field of the module bay into which an instance of the module type is being installed.
When adding component templates to a module type, placeholders can be used to dynamically incorporate the module bay's `position` field into component names. Two placeholders are available:
For example, you can create a module type with interface templates named `Gi{module}/0/[1-48]`. When a new module of this type is "installed" to a module bay with a position of "3", NetBox will automatically name these interfaces `Gi3/0/[1-48]`.
### `{module}` Placeholder
The `{module}` placeholder references the position of the parent module bay:
* **Single use**: Expands to the immediate parent's position only
* **Multiple uses**: Each `{module}` token is replaced level-by-level (the number of tokens must match the nesting depth)
For example, a module type with interface templates named `Gi{module}/0/[1-48]`, when installed in a module bay with position "3", will create interfaces named `Gi3/0/[1-48]`.
### `{module_path}` Placeholder
The `{module_path}` placeholder expands to the full path from the root device to the current module, with positions joined by `/`. This is useful for modules that can be installed at any nesting depth without modification.
For example, consider an SFP module type with an interface template named `eth{module_path}`:
* Installed directly in slot 2: creates interface `eth2`
* Installed in slot 1's nested bay 1: creates interface `eth1/1`
* Installed in slot 1's nested bay 2's sub-bay 3: creates interface `eth1/2/3`
!!! note
`{module_path}` can only be used once per template attribute, and cannot be mixed with `{module}` in the same attribute.
### Position Field Resolution
The `{module}` placeholder can also be used in the `position` field of [module bay templates](./modulebaytemplate.md) defined on a module type. This allows nested module bays to build hierarchical position values. For example, a module bay template with `position="{module}/1"`, when its parent module is installed in a bay with position "2", will have its position resolved to "2/1".
Automatic renaming is supported for all modular component types (those listed above).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.