All notable changes to netbox-osp are documented here. The format follows
Keep a Changelog and this project
adheres to Semantic Versioning.
Per-release NetBox / Python compatibility lives in COMPATIBILITY.md.
0.3.8 — 2026-05-19
- "Trace this core" button no longer appears twice on the Strand
detail page. v0.3.7 wired the
plugin_right_pageslot intostrand.html, which made theStrandTraceButtonPluginTemplateExtensionfire — but the template also kept the pre-v0.3.1 inline{% include 'netbox_osp/inc/trace_button.html' %}workaround, so the button rendered twice. Removed the inline include; the extension is now the single source of truth.
0.3.7 — 2026-05-19
Three audit fixes bundled together.
- Brown strand colour badges now render in actual brown. TIA-598
position 4 is Brown; NetBox doesn't ship
text-bg-brownin its badge palette so the class fell through to a near-black default. Added an explicit CSS override (#8b4513saddle brown) so a Brown strand looks brown on the strand list, tube list, and detail pages. - Strand detail page Colour badge now uses the helper. The
strand.htmltemplate called{% badge object.get_color_display %}withoutbg_color, so even though v0.3.5 added theget_color_color()helper, the detail page kept rendering Colour as grey. Now passesbg_color=object.get_color_colorand matches the list-view colour fidelity. strand.htmlandtrunkbreakout.htmlnow render plugin template-extension slots. v0.3.1 wired{% plugin_left_page %}/{% plugin_right_page %}/{% plugin_full_width_page %}into the other custom OSP detail templates, but the strand and trunkbreakout templates were missed. Attachments, Documents, and any other plugin's per-object cards are now visible on these two pages.
0.3.5 — 2026-05-19
- TIA-598 strand colour badges now actually use their named colour.
v0.3.4 added CSS overrides for
text-bg-blue/text-bg-black/text-bg-white— but they had no effect because NetBox'sChoiceFieldColumnwas rendering every TIA-598 colour astext-bg-secondary(grey). Root cause: neitherStrandnorTubedefined aget_color_color()method, so the column couldn't look up the badge colour fromTIA598ColorChoices. Added the helper on both models — colour codes now render in their actual colour (Blue is blue, Orange is orange, Brown is brown, etc.).
0.3.4 — 2026-05-19
- TIA-598 strand colour badges now legible on dark theme. NetBox's
default palette desaturated
blueto near-grey,blackwas invisible, andwhiterendered with poor contrast. Added explicit CSS overrides for.badge.text-bg-blue(vivid#1976d2),.badge.text-bg-black(#1a1a1a+ light text + outline), and.badge.text-bg-white(light bg + dark text + outline). Strand colour codes are functional information — operators identify a fibre by its position colour — so badge fidelity matters. Overrides intentionally apply globally; boosting these three is a net improvement across all NetBox badges. - Core tracer hop nodes are easier to read. Bumped
.osp-tracer-nodemin-widthfrom 120 → 160 px,font-size12 → 13 px, and increased the kind / meta sub-font sizes for better legibility at typical viewport widths.
0.3.3 — 2026-05-19
- FibreLink loss-budget gauge is now readable on dark theme. The
prior gauge inlined the numeric readout inside the SVG at
font-size: 5.5viewBox units with a near-black fill (#1a1a1a) — effectively invisible on NetBox's dark theme. The numbers are now rendered as a bold<strong>outside the SVG, the percent appears as a coloured Bootstrap badge (text-bg-success/warning/dangermatching the band), and the SVG is now a pure visual bar (~18px tall, full container width) with no text. A short legend underneath explains the thresholds. Long-standing UX bug spotted during the v0.3.x walkthrough.
0.3.2 — 2026-05-18
- MTP harness deploy form now exposes the
polarityselector. v0.3.0 added apolarityChoiceFieldtoMtpHarnessFormbutmtp_harness_deploy.htmlrenders each field explicitly via{% render_field form.X %}rather than iterating the form, so the new field never appeared in the UI. Added{% render_field form.polarity %}betweentrunk_typeandfibre_countso operators can pick the MPO polarity when deploying a harness. - Per-object detail-page maps now use the shared base-layer
manager (
spliceclosure.html,ospcable.html). The detail maps previously only fetched from the local MBTiles proxy, which bundles tiles for a Wheatstone bbox only — Perth-metro or any other location rendered as a blank dark canvas with just the marker pin. They now hookOspBaseLayers.attach(map, null, tileUrl)so they start on the preferred online layer and auto-fall-back to the bundled MBTiles after three tile errors, matching the main network map's behaviour. No dropdown UI is rendered (passingnullskips the menu); operators get a working map at any geography out of the box. Pre-existing bug noticed during the v0.3.1 live-verify.
0.3.1 — 2026-05-18
PluginTemplateExtensionhooks now render on custom detail templates. v0.3.0 shipped two newPluginTemplateExtensionclasses (SpliceClosureQrCode,SpliceTrayQrCode) plus enabled third-party plugins likenetbox-attachmentsto target our models — but our custom detail templates (spliceclosure.html,splicetray.html,fibretrunk.html,ospcable.html,fibrelink.html) override thecontentblock ofgeneric/object.htmlwholesale, which silently swallows the slot where NetBox renders plugin extensions. The Field QR code panel and the Attachments card never appeared. Each affected template now loads{% load plugins %}and renders{% plugin_right_page object %},{% plugin_left_page object %}, and{% plugin_full_width_page object %}so any plugin's hooks fire. Note thatnetbox-osp[qrcode]users on v0.3.0 will see the QR panel only after upgrading to v0.3.1.
0.3.0 — 2026-05-18
Three-PR ecosystem sprint per the v0.3 moat plan §v0.3.0 quick
wins. Two integrations + one operations feature; no breaking
changes. Migration 0005_fibretrunk_polarity is additive and
non-destructive.
- MPO polarity tracking on
FibreTrunk— addsMpoPolarityChoices(Type A / B / C / D per TIA-568.3-D) and a newpolarityCharFieldonFibreTrunk. Surfaced on the detail page (badge next toType), table column, filter UI, REST API, REST bulk-import + bulk-edit, GraphQL filter, and the MTP harness one-click deploy wizard. Blank is permitted for non-MPO trunk types (Ribbon / Loose-tube) and unknown-polarity legacy data. Addresses NetBox core issue #5798 and the VIAVI "40 % of hyperscale downtime is fibre alignment / connector issues" stat. Migration0005_fibretrunk_polarityis additive and non-destructive (empty-string default for existing rows). - Field QR codes on
SpliceClosureandSpliceTray— two newPluginTemplateExtensionclasses (SpliceClosureQrCode,SpliceTrayQrCode) inject a Field QR code panel into the right-page area of closure / tray detail pages. The QR encodes the absolute URL of the page so a field tech can scan a printed closure label and land on the splice plan with attached photos. Uses theqrcodePython library's pure-Python SVG factory (no Pillow dependency). Install withpip install netbox-osp[qrcode]. The panel quietly hides on installs without the extra so the base install isn't affected. NoPLUGINS_CONFIGchanges required. Seedocs/integrations.mdfor rationale on why we useqrcodedirectly rather than wrappingnetbox-qrcode(which hardcodes its supported model list at the class level). - netbox-attachments integration — documented
scope_filterconfiguration for netbox-attachments covering all 10 OSP models (OspCable,Tube,Strand,Splice,SpliceClosure,SpliceTray,FibreLink,FibreTrunk,TrunkBreakout,LocationGeo). Operators can now attach OTDR.sortraces, splice photos, as-built PDFs, and acceptance-test certificates to any OSP record. Install withpip install netbox-osp[attachments]. Zero plugin code — pure composition via the upstreamscope_filtersetting. Seedocs/integrations.md. Sets up the file-storage layer that the upcoming v0.3.5 OTDR moat work will read.sorfiles from.
0.2.2 — 2026-05-18
- README screenshots now use absolute
raw.githubusercontent.comURLs so they render inline on the PyPI project page (relativedocs/paths in the previous releases pointed at files PyPI doesn't bundle). - Added an 8-step demo walkthrough at
/demo/on the docs site with 10 screenshots captured live from the public demo, covering the OSP model, splice closures, loss-budget gauge, MTP harness deploy form, visual core tracer, and the plant map. - README gains a "demo walkthrough" badge linking to the new page.
- No code changes —
pip install -U netbox-ospis metadata-only.
0.2.1 — 2026-05-14
- Visual core tracer (introduced in 0.2.0) rendered an empty Path graph
because
dagre-d3.min.jsdeclares an externald3dependency rather than inlining it. The loss-budget band and hop legend rendered fine but the graph itself stayed blank with "Renderer JS not loaded". Vendoredd3.v5.min.jsalongside the dagre-d3 bundle and added the<script>tag instrand_tracer.htmlahead of dagre-d3.
0.2.0 — 2026-05-13
Five-PR cycle adding inter-rack fibre infrastructure: MPO/MTP trunks with breakouts to cassettes, one-click harness deploy, cassette device-type catalogue, and an end-to-end visual core tracer.
-
FibreTrunkmodel — parent for multi-fibre rack-to-rack physical trunks (MPO/MTP 12/24/72-fibre, Ribbon 144-fibre, loose-tube indoor runs). Carriescid,trunk_type,fibre_count,manufacturer,length_m,status(reusesOspStatusChoices), GeoJSONroutewithshow_on_mapopt-out,description,comments,tenant,tags. DB-levelCheckConstraintonfibre_count > 0plus a form-friendly validation surfacing the same on thefibre_countfield. NewTrunkTypeChoicespalette (mpo-12,mpo-24,mpo-72,ribbon-144,loose-tube-n,other) with a per-typeDEFAULT_FIBRE_COUNTmapping ready for a follow-up auto-fill. -
REST CRUD under
/api/plugins/osp/trunks/(full serializer, viewset, filter-set, router registration). -
GraphQL —
osp_fibre_trunkandosp_fibre_trunk_listqueries via the newFibreTrunkType/FibreTrunkFilter. -
Admin chrome — list / add / edit / delete / bulk-edit / bulk-delete / bulk-import / changelog views, table with status + trunk-type colour columns, filter form, sidebar entry under a new "Trunks" group with Add + Import buttons, search-index registration.
-
Tests —
tests/test_fibretrunk.pycovering__str__, defaults,clean()(fibre_count guard and GeoJSON shape), tagging, REST CRUD with auth, and GraphQL module-level exposure. -
TrunkBreakoutthrough-table bridgingFibreTrunkto NetBox's nativedcim.Cable. Captures the trunk-with-breakouts pattern: operators express "24F MTP trunk → 12F breakout to rack A + 12F breakout to rack B" as one cohesive entity with the trunk identity preserved across child cables. Fields:trunkFK (CASCADE),cableFK (PROTECT),fibre_range_start/fibre_range_end(1-indexedPositiveSmallIntegerField),description,tags.CheckConstraints enforcestart >= 1andend >= start; twounique_together(trunk, cableandtrunk, fibre_range_start) catch double-allocation.clean()rejects ranges that exceed parentfibre_countor overlap a sibling breakout — all field-keyed. -
FibreTrunk.clean()now enforces sum of child-breakout ranges ≤fibre_count(the PR-A# TODO(PR-B)marker is wired up). -
FibreTrunk.fibres_used/fibres_remaining/fibres_utilization_pctcomputed properties feed the admin table's utilisation column. -
"Import from cables" wizard at
/plugins/osp/trunks/<trunk_id>/import-cables/— multi-select unbounddcim.Cables and assign fibre ranges atomically. All-or-nothing viatransaction.atomic(); surfaces bothValidationErrorandIntegrityErrorrace-loser collisions as form errors. -
REST + GraphQL for
TrunkBreakout: CRUD at/api/plugins/osp/trunk-breakouts/, GraphQLosp_trunk_breakoutandosp_trunk_breakout_listqueries viaFibreTrunkBreakoutType/FibreTrunkBreakoutFilter. -
Admin chrome for
TrunkBreakout— list / add / edit / delete / bulk-edit / bulk-delete / bulk-import / changelog views, table with parent-trunk + cable + range columns, filter form, "Trunk Breakouts" sidebar entry under the existing Trunks group, search-index registration. -
CSV bulk import —
trunk_cid,cable_label,fibre_range_start,fibre_range_endwith friendly errors on ambiguous / missing cable labels. -
Tests —
tests/test_trunkbreakout.py(31 cases) covering modelclean()field-keyed errors,unique_togetherenforcement,FibreTrunkutilisation arithmetic, REST CRUD with auth, GraphQL surface, and the import-cables wizard view (GET, POST happy path, POST validation failure). Import-form tests added totests/test_imports.py. -
MTP harness one-click deploy form at
/plugins/osp/trunks/deploy-harness/— a single form submit creates the parentFibreTrunk+ N cassettedcim.Devices + Ndcim.Cables linking the source patch-panel RearPort to each new cassette's RearPort + NTrunkBreakoutrows binding the cables to the trunk at chosen fibre ranges. Atomic — the whole batch rolls back if any row fails validation. Two-step preview/confirm flow usesdjango.core.signing.dumps(base64-encoded, HTML-safe) to round-trip the cleaned form state safely between the preview and confirm POST steps with a 600s expiry. Sidebar entry under the existing "Trunks" group plus a green button on the FibreTrunk detail page next to "Add Breakout" / "Import from Cables". New plugin settingdefault_cable_type(default"smf"). Exception ladder mirrors PR B's import wizard:ValidationError→ form-keyed errors,IntegrityError→ race-condition message,AbortRequest→ cable-path-impossible message. Tests intests/test_mtp_harness.pycover GET auth, N=2 / N=3 happy paths, overlap rollback, missing-rack rollback, fibre-sum overflow, duplicate-rack rejection, preview-then-confirm round-trip, and permission denial. -
Bundled cassette catalogue — five
DeviceTypeJSON templates atnetbox_osp/device_types/cassettes/covering the standard MPO/MTP fibre-cassette and LC patch-panel shapes that pair with the MTP harness deploy form:mpo-12f-lc-cassette(1× MPO-12 rear → 12× LC),mpo-24f-lc-cassette(1× MPO-24 rear → 24× LC),mpo-12f-mpo-cassette(MPO pass-through),lgx-lc-12f-panel(1U LGX),ru1-lc-24f-panel(1U 24F with 2× MPO-12 rears). JSON follows thenetbox-community/devicetype-libraryschema (hyphenatedrear-ports/front-portskeys), so the same files import via the upstream loader too. -
load_osp_cassettesmanagement command —python manage.py load_osp_cassettesseeds all five cassettes into the live DB. Idempotent (slug-keyedupdate_or_create), wraps each cassette in onetransaction.atomic(), materialisesRearPortTemplate+FrontPortTemplate+PortTemplateMappingrows withfront-to-rearpin maps preserved. Ships a--dry-runflag for safe inspection. -
Cassette tests —
tests/test_cassettes.pycovering JSON validity, required-key presence, cross-reference integrity (everyrear_portreferent exists, everyrear_port_positionis within bounds), management-command happy-path + idempotency contract, a spot check on the MPO-12F-LC port-template materialisation, and a forbidden-token guard so operator-site names never bake into the generic catalogue. -
Visual core tracer (PR E) — click "Trace this core" on a
Strand,dcim.FrontPort, ordcim.Interfacedetail page to render an end-to-end fibre path as a clickable dagre-d3 graph. Each hop — interface, patch cord, cassette pass-through, MPO/MTP trunk, splice, OSP strand — shows inline loss + length; the summary band above the graph shows total dB used against the strand's parent FibreLink budget (or a configurable default), colour-codedok/warn/fail. New JSON endpoint atGET /api/plugins/osp/cores/<strand_id>/trace/returns the hop list for external tooling. dagre-d3 v0.6.4 (~700 KB minified) ships vendored instatic/netbox_osp/js/dagre-d3.min.js. Three newPluginTemplateExtensionsubclasses inject the trace button ontodcim.Interface,dcim.FrontPort, and netbox-ospStranddetail pages. New optional config keys underPLUGINS_CONFIG["netbox_osp"]:default_cassette_loss_db(0.5),default_patch_cord_loss_db(0.1),default_loss_budget_db(8.0). Zero new migrations — the tracer is read-only over the existing data model. Tests intests/test_core_tracer.pycover the JSON endpoint, the full-page HTML view, splice-chain walking, loss-math summation, the "incomplete" flag, and the trace-button template integration.
- PR E — visual core tracer.
The 0.2.0 release tag fires once all five v0.2 PRs land. This entry
documents PRs A, B, C, D, and E. All five v0.2 features
landed; v0.2.0 is ready to tag.
0.1.1 — 2026-05-13
- Corrected author / maintainer name in package metadata,
PluginConfig,mkdocs.ymlcopyright, and the icon SVG copyright comment from "John McKenzie" to "John McKean". 0.1.0 shipped with the wrong spelling and PyPI does not permit re-uploading a published version, so this metadata-only patch ships the correction.
0.1.0 — 2026-05-13
First functional release. Covers the full OSP fibre data model, the interactive plant map with online + offline base layers, REST + GraphQL APIs, bulk-import / bulk-edit, plant-boundary validation, and per-Location GPS markers.
- Data model —
OspCable,Tube,Strand,SpliceClosure,SpliceTray,Splice,FibreLink,FibreLinkStrand,LocationGeowith TIA-598-C auto-colours, capacity / utilisation accounting, and thefibre_count == tube_count × fibres_per_tubeinvariant enforced inclean(). - Fibre-link loss budget — strand attenuation × length + per-splice loss + per-connector loss, with ok / warn / fail band against a configurable target. SVG gauge bar rendered on the FibreLink detail page.
- Network map — full-screen Leaflet at
/plugins/osp/map/, filterable by status, with 7 online base layers (OSM, HOT OSM, CartoDB Positron / Dark Matter, OpenTopoMap, CyclOSM, Esri World Imagery) plus a bundled offline MBTiles bundle. Auto-falls back to offline after three tile errors within five seconds. User's explicit choice persists across reloads vialocalStorage. - GeoJSON cable-route editor — leaflet-geoman drag-to-edit with the same base-layer machinery as the main map.
- Plant-boundary trace tool — operator drags vertices to draw the
plant outline; exports a
[lon, lat]coordinate list for theplant_boundarysetting. - Optional plant-boundary validation — set
PLUGINS_CONFIG['netbox_osp'] ['plant_boundary']andOspCable.clean()rejects any cable whoseroutefalls outside the polygon. Pure-Python ray-casting; no PostGIS required. - Per-Location GPS markers (
LocationGeo) — 1:1 side-table ondcim.Locationaddinglatitude/longitude/elevation_m/marker_color. Rendered asL.circleMarkeroverlay on the map and injected as a panel on thedcim.Locationdetail page. - Bulk-import / bulk-edit forms for
Tube,Strand,Splice,SpliceTray,SpliceClosure, andLocationGeo— closes a ~300-click data-entry gap for a 288-strand cable. - GraphQL types for all nine plugin models via
strawberry-django, registered with NetBox's/graphql/endpoint. - REST API under
/api/plugins/osp/...with full CRUD on every model, filter-sets aligned with the UI list views. - Offline-first tile proxy at
/plugins/osp/tiles/<z>/<x>/<y>.<ext>serving PNG / JPEG / WebP tiles from one or more MBTiles files, with per-tileETag+304 Not Modifiedand transparent-PNG fallback for missing tiles. - Permission-matrix tests — auto-generated coverage of GET / list / create / edit / delete and their bulk variants for the SpliceClosure view set (canary; remaining seven models follow a known pattern).
- MkDocs Material docs site at https://iamjohnnymac.github.io/netbox-osp/.
- Public GitHub repo with PyPI Trusted Publishing via OIDC (no API tokens).
- CI matrix: Python 3.12 / 3.13 × NetBox 4.6 on Postgres 17 + Redis 7.
mkdocs.yml+ GitHub Pages auto-deploy on everymainpush..pre-commit-config.yamlwithruff(lint + format), configured to match the NetBox plugin ecosystem.- Apache-2.0 license.
- GraphQL schema registers cleanly but
osp_<model>_listquery fields don't surface in the merged globalQueryyet — being debugged against a live/graphql/endpoint. REST API is fully functional in the meantime. - Permission-matrix tests cover the SpliceClosure canary; the remaining
primary-object view sets will be added in a focused follow-up. REST
APIViewTestCasepermission tests are deferred until CI wiresAPI_TOKEN_PEPPERS.
0.0.1 — 2026-05-13
- PyPI name-reservation placeholder. Not functional.