Changes Script logger initialization to use `full_name` property instead
of reconstructing the namespace from `__module__` and
`__class__.__name__`.
Ensures dynamically loaded Scripts log to the correct public namespace.
Fixes#22953
Adds `add_module_bay_types` and `remove_module_bay_types` fields to
ModuleBayTemplate bulk edit form with fieldsets. Implements per-object
delta operations in post_save_operations to preserve existing type
assignments while adding/removing specified types.
Fixes#22961
Replace RelatedObjectAttr with NestedObjectAttr for Tenant Groups,
Wireless LAN Groups, and Device Type default platforms. Limit Platform
hierarchies to three levels for consistency with existing Platform
fields.
Fixes#22954
initSideNav() matched every .navbar element, and
templates/base/layout.html puts that class on both the sidebar aside and
the top header, so a second SideNav was constructed for the header.
Scope the selector to .navbar-vertical, which is what the pre-Tabler
selector .sidenav did.
Widen the sidebar element type from HTMLDivElement to HTMLElement, since
the element the selector matches is an aside.
Fixes#22929
Document the database permissions required to install the ltree extension
during an upgrade to NetBox v4.7. Clarify that installations following the
standard PostgreSQL setup already satisfy this requirement through database
ownership.
Provide commands for granting the database-level CREATE privilege where
needed, along with an administrator pre-installation option for deployments
using a restricted NetBox database role.
Co-authored-by: Martin Hauser <mhauser@netboxlabs.com>
Display ancestor hierarchy in breadcrumbs for DeviceRole, Platform, and
PowerPanel detail views. Shows full parent chain with filtering links
to improve navigation through nested object relationships.
Fixes#22957
Display ancestor hierarchy in breadcrumbs for DeviceRole, Platform, and
PowerPanel detail views. Shows full parent chain with filtering links
to improve navigation through nested object relationships.
Fixes#22957
BulkCreateView, BulkDeleteView, and ObjectDeleteView each catch an exception,
roll back the transaction, and return a normal response — but without clearing
the in-memory events queue. The queue is flushed after the view returns, so
webhooks and event rules fired for creations and deletions that were never
committed.
Send the clear_events signal from each of the affected handlers, matching the
idiom already used by the sibling handlers in these views.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
handle_location_site_change() repaired the cached scope fields of Location-
scoped Prefixes, Clusters and WirelessLANs, but not CircuitTerminations,
which cache the same ancestry under their own termination_type/termination_id
generic FK rather than CachedScopeMixin.scope. That made them invisible both
to the repair loop and to sync_cached_scope_fields().
Two cases were left wrong. A termination at a descendant Location kept its
_site, _region and _site_group entirely, since descendants are moved by a
queryset update() which fires no post_save. A termination at the moved
Location itself had _site refreshed by the denormalized-field registry, but
not _region or _site_group: those are mapped off the separate _site
registration, which requires a Site save.
Repair both cases by selecting through the generic termination fields over
the Location and its descendants alike. These columns back the site, region
and site group filters for Circuit and CircuitTermination, so a stale value
drops the circuit out of filtered lists and leaves it showing under its
former site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Updates ModuleType bulk import test to use valid test data. Replaces
reference to non-existent 'Fan' profile with test fixture profile.
Changes assertion to use assertEqual for consistency with test patterns.
Closes#22925
Replace the obsolete Django admin `vLargeTextField` class with Tabler's
`font-monospace` utility for the four banner configuration parameters.
Define the widget styling in the parameter definitions, where the metaclass
constructs the form fields, and remove the ineffective `Meta.widgets`
overrides. Add regression coverage for all six code-oriented configuration
fields, including the two JSON fields that already use a monospace font.
Update the add-config-param skill to recommend `font-monospace` so future
textarea-backed parameters do not reintroduce the obsolete admin class.
Adds comprehensive test cases for NestedObjectAttr including ancestor
traversal, max_depth limiting, null value handling, and linkify/colored
options. Uses Region model with MPTT hierarchy for testing nested
object rendering.
Closes#22956
- Document that ModuleType.to_yaml() exports module_bay_types by name but
the field isn't currently importable back through it (no ModuleTypeImportForm
field survived the CSV-import revert).
- modulebaytemplate.md's note covered only the device-type-parented import
path; ModuleBayTemplateImportForm is registered for both DeviceTypeImportView
and ModuleTypeImportView, scoping to whichever parent type's manufacturer
applies. Reworded to cover both, and added the "rejected rather than
resolved" clause for a name matching only some other manufacturer's type.
- Clarified clean_module_bay_types()'s docstring: the "never a cross-manufacturer
collision" guarantee holds only because ModularComponentTemplateModel.clean()
rejects a template with neither device_type nor module_type before this
method's result would ever be saved.
- Fixed a test docstring overstating symmetry between its two comparison arms.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per review, ditch dedupe_module_bay_types_by_manufacturer() and any import
logic that resolves module_bay_types by name alone across manufacturers.
ModuleBayType's unique constraint is (manufacturer, name), not name alone,
so resolving a bare name against an unscoped, potentially cross-manufacturer
pool -- guessing via a preference order, rejecting only on a genuine tie --
is not a reliable way to identify a specific object. CSV import forms have
no way to qualify an M2M reference beyond a bare name, so module_bay_types
is no longer exposed there at all (ModuleTypeImportForm, ModuleBayImportForm
in bulk_import.py): it's acceptable not to support this rather than resolve
it unreliably. This also reverts the netbox/tables/columns.py export_transform
API addition and the three tables' use of it, which existed only to make the
CSV round trip work.
The one import path that survives is ModuleBayTemplateImportForm (the YAML
device/module type "Import Components" flow), because it can reliably scope
module_bay_types' queryset to the parent device/module type's own
manufacturer plus global (manufacturer-less) types *before* resolving by
name -- so a name collision is never cross-manufacturer, only "this
manufacturer's own type vs. a global one of the same name," which
ModuleBayType's own uniqueness constraint makes unambiguous. A name matching
only some other manufacturer's type doesn't resolve at all, rather than
being coerced to an arbitrary guess.
Kept: the ModuleBayTemplateImportForm.enabled field/clean_enabled() fix
(default=True was previously lost on YAML re-import; unrelated to the
above), and to_yaml()'s export of module_bay_types on both ModuleType and
ModuleBayTemplate, plus the export_yaml() prefetch optimizations -- none of
these involve resolving an object's identity from an ambiguous attribute.
Trimmed the model docs to match: the modulebay.md and moduletype.md
paragraphs described capabilities (CSV import, cross-manufacturer YAML
import) that no longer exist and are removed; modulebaytemplate.md's note
is rewritten to describe the actual (manufacturer-or-global-scoped)
resolution behavior.
Allow channel subinterfaces to retain a specific physical interface type
and rename conventionally named children when their parent is renamed.
Keep mirrored cable and path state consistent when channel bindings
change, avoid unnecessary path rebuilds, and apply the same rename
behavior to interface templates.
Distinguish absent values from malformed or unwalkable paths during
condition evaluation.
Preserve valid empty-list traversal, support changes in snapshot shape,
and reject snapshot attributes which are missing from both snapshots.
Normalize absent job payloads and ensure conditioned rules fail closed
when no payload is available. Add regression coverage and streamline the
related documentation and comments.
django-tables2's ManyToManyColumn.render() and NetBox's own value() override
both call self.transform() for each item -- there's no built-in way to give
CSV export a different representation than the rendered column. Setting
transform=lambda obj: obj.name on the three module_bay_types columns to fix
CSV export therefore also dropped the manufacturer prefix from the Bay Types
column in the Module Bays, Module Bay Templates, and Module Types list
views -- the opposite of what ModuleBayType.__str__() adds that prefix for.
Verified directly: with the old transform=, two same-named bay types from
different manufacturers render as visually identical "SFP28" list items.
Add export_transform to NetBox's ManyToManyColumn subclass, defaulting to
transform so existing columns are unaffected, and used only by value()
(export) rather than render() (UI). Switch the three columns to
export_transform=lambda obj: obj.name, leaving transform unset so render()
keeps str()'s manufacturer prefix.
Extended the existing round-trip test to also assert the rendered column
still includes the manufacturer name; confirmed it fails against the old
transform= approach and passes with export_transform=.
- ModuleBayType.__str__() includes the manufacturer (e.g. "Cisco SFP28"),
but the three module_bay_types ManyToManyColumn declarations had no
transform, so django-tables2 defaulted to str() for CSV export while
the import forms resolve by name alone. Verified directly: exporting
a manufacturer-scoped bay type produced "Cisco SFP28", which then
failed to re-import with "Object not found: Cisco SFP28" -- broken
for exactly the case (manufacturer-scoped types) the preference/
ambiguity machinery exists to serve. Set transform=lambda obj: obj.name
on all three columns to match to_yaml(), and rewrote the existing
round-trip test to use a manufacturer-scoped bay type instead of a
global one, which is the only case that exercised str().
- The three ambiguity-refusal tests asserted only that the field
errored, which a plain invalid_choice (e.g. from a queryset that
excluded both candidates) would also satisfy -- masking a regression
of the manufacturer scoping removed two commits ago. Tightened each to
assert the error names both competing manufacturers.
- Corrected modulebay.md, which still described module_bay_types
resolution as "scoped to" the device's manufacturer -- the behavior
the prior commit removed as a bug; it's a preference, not a scope.
- Trimmed comments and docstrings introduced across this branch to a
more proportionate length.
Deliberately out of scope for this PR (tracked as follow-up
considerations, not fixed here): an escape hatch for a bay type name
that's genuinely ambiguous across manufacturers with no local match
(would require a new wire-format convention), and ModuleType.to_yaml()
not exporting a module-bays section at all (a separate, pre-existing
asymmetry, larger than this PR's scope).
- CSVModelMultipleChoiceField.clean() split on a bare comma with no
whitespace stripping, but ManyToManyColumn's default CSV export
separator is ", " (comma + space) -- so re-importing NetBox's own CSV
export of any multi-value column using this field (module_bay_types
among others, since this is a shared utility field) failed with
"Object not found: <value>" on every value after the first. Verified
directly against ModuleTypeTable's actual export value before fixing.
Also cast to str() before splitting: a YAML-bound caller (as opposed to
a CSV cell, always a string) can pass a non-string scalar, which
previously raised an unhandled AttributeError instead of a form error.
- Docs for module bay type resolution still described the pre-a3b5e4b
fallback ("then any remaining candidate"); updated to describe the
refusal behavior that replaced it. Added a matching note to
modulebay.md, which had none.
- dedupe_module_bay_types_by_manufacturer() collapses candidates by pk
within each name group before computing preference, so a caller
passing a duplicate row in a raw list -- the signature accepts "an
iterable," not just a queryset -- can't manufacture a same-manufacturer
tie that would then crash on None.manufacturer.name. Unreachable via
the three current callers today (each resolves from a queryset,
which can't contain a row twice), but cheap to make the helper safe
standalone.
- Fixed a stale test docstring contrasting the two import forms' field
types by a distinction (plain vs. CSV multiple-choice field) that no
longer exists since both were aligned to CSVModelMultipleChoiceField.
- Added ambiguity-refusal coverage at the other two call sites
(ModuleBayTemplateImportForm, ModuleBayImportForm) -- previously only
ModuleTypeImportForm was covered for this path.
Also found independently while verifying the above: ModuleTypeListView
.export_yaml() prefetched modulebaytemplates__module_bay_types, but
ModuleType.to_yaml() -- unlike DeviceType.to_yaml() -- never reads
self.modulebaytemplates at all (a separate, pre-existing, out-of-scope
gap: ModuleType.to_yaml() doesn't export a nested module-bays section).
That prefetch was dead weight, adding a query with no corresponding
saving. Removed it, and with it the now-meaningless "bay count doesn't
affect query count" test (nothing in ModuleType.to_yaml() ever varied
with bay count to begin with), replacing it with an exact-delta
assertion isolating what the one relevant prefetch (module_bay_types
on the module type itself) actually saves.
Pin `twine` and `packaging` versions in build job to match bundled
versions in `gh-action-pypi-publish` v1.14.2.
Enforce Core Metadata 2.4 in wheel and sdist targets with verification
in validation scripts.
Pin `twine` and `packaging` versions in build job to match bundled
versions in `gh-action-pypi-publish` v1.14.2.
Enforce Core Metadata 2.4 in wheel and sdist targets with verification
in validation scripts.
Fixes#22903
- dedupe_module_bay_types_by_manufacturer()'s lowest preference tier (a
bay type belonging to some manufacturer other than the importing
type's own) previously picked whichever candidate happened to sort
first when two or more *different* foreign manufacturers shared a
name. Verified directly: importing 'SFP28' for a Juniper module type,
with only Cisco's and Arista's same-named types in the database (no
Juniper or global one), silently linked to Arista's -- a wrong FK with
no signal to the importer. The permissive fix from the last round only
needs this tier to be reachable for the single-candidate case, not
tolerant of a genuine tie; now raises ValidationError, attributed to
module_bay_types, naming the competing manufacturers.
- ModuleBayTemplateImportForm.module_bay_types was a plain
ModelMultipleChoiceField (list only), while ModuleTypeImportForm's
otherwise-identical field is a CSVModelMultipleChoiceField (list or
comma-separated string), so `module_bay_types: SFP28` was accepted at
the module-type level and rejected under `module-bays:` within the
same YAML document. Switched to CSVModelMultipleChoiceField in both,
which costs nothing here since it passes lists through unchanged.
- ModuleBayImportForm (CSV import for real ModuleBay instances, as
opposed to ModuleBayTemplateImportForm's templates) still had no
module_bay_types support -- the same class of round-trip gap this PR
exists to close, on the instance side rather than the template side.
Added it, scoped via the importing device's own device_type.manufacturer.
- The ModuleType prefetch query-count test only varied bay count (module
type count fixed at 1), so it couldn't detect a regression in the
module_bay_types prefetch on ModuleType itself -- confirmed directly:
the test stayed green with that prefetch removed entirely. Varying
module type count instead doesn't work either, since to_yaml() touches
several other per-instance relations (manufacturer, port_mappings, ...)
that legitimately scale with row count regardless of this fix and swamp
an exact-equality comparison -- hit this myself on the first attempt.
Replaced with a with/without-prefetch comparison on the identical
queryset, which isolates the saving without that confound; verified it
fails when the prefetch is removed and passes when it's present.
port_mappings_list rendered one token per port, so a service exposing a
large contiguous range (e.g. tcp/8000-8100) filled the list column and
detail panel with hundreds of tokens. Collapse consecutive ports within a
protocol into a range for display, matching the compact form the
pre-multi-protocol Service model rendered via array_to_string().
The port-mappings CSV column expanded only comma-separated individual
protocol/port pairs, while the edit form's port field already accepted
hyphen ranges (e.g. tcp/8000-8010). Route the CSV column through the same
expand_port_mapping() helper so both entry paths accept identical port
syntax. Parsing uses the shared split_port_mapping() helper, and the
blank-protocol error is worded to fit every entry path rather than only the
form widget's dropdown.
ModuleTypeImportForm.module_bay_types uses CSVModelMultipleChoiceField
specifically because this form also serves plain CSV bulk import, where the
cell value arrives as a string rather than a list -- unlike
ModuleBayTemplateImportForm.module_bay_types, which only ever binds from a
YAML-parsed list. Every existing test exercised the list-binding path only;
verified the comma-separated-string path directly before adding permanent
coverage for it, including the empty-string case.
The manufacturer-or-null queryset scoping added to disambiguate a name shared
by a global and a manufacturer-scoped ModuleBayType went further than
intended: it also excluded a *different* manufacturer's bay type entirely.
The UI (ModuleTypeForm/ModuleBayTemplateForm) and REST API place no such
restriction -- a third-party module may legitimately declare compatibility
with another manufacturer's proprietary bay type. Confirmed the regression
directly: creating that assignment via ModuleTypeForm succeeds, but
exporting it and re-importing the same YAML failed with
"Object not found: SFP28", making valid existing data unimportable -- worse
than the bug this feature exists to fix.
Remove the queryset scoping entirely and instead make
dedupe_module_bay_types_by_manufacturer() manufacturer-aware: given the
target manufacturer, it now prefers (in order) an exact match, then a global
type, then any remaining candidate, resolved from an unscoped queryset in
clean() rather than a sibling clean_<field>() mutating the field's queryset.
This also drops the Meta.fields-ordering dependency those methods required.
Also, from the same review round:
- Test asserting Django's literal English error string now asserts the
error code instead, so it survives wording changes/translation.
- The prefetch query-count test moved from test_models.py (which doesn't
otherwise touch views) to test_views.py, and strengthened from "prefetch
saves at least one query" to "query count is constant regardless of bay
count" -- the actual invariant. Added equivalent coverage for
ModuleTypeListView, which the prior version didn't test at all.
- Corrected the export_yaml() prefetch comments to not imply the other
~11 relations to_yaml() touches are also covered -- they aren't, and
weren't before this feature either.
- Updated the model docs to describe the new (permissive, cross-manufacturer
allowed) behavior instead of the old (restrictive) one they described a
commit ago.
Adds regression tests importing a bay type belonging to a different
manufacturer than the importing device/module type, through both
ModuleBayTemplateImportForm and ModuleTypeImportForm.