""" Run INSIDE the NetBox Django shell to export the field-switch topology (switches + every patch panel / splice module they physically pass through) as JSON, for generate_drawio.py to render. Usage (from the NetBox host, e.g. /opt/netbox): docker exec netbox-netbox-1 python3 /opt/netbox/netbox/manage.py shell \ -c "$(cat topology/export_topology.py)" 2>/dev/null > topology/topo_full.json The script only reads data -- it never modifies NetBox. If a switch's connected_endpoints comes back empty when it clearly shouldn't, that is a NetBox cable-path bug, not a bug in this script -- see the "gotchas" section of the repo README before assuming the physical link doesn't exist. """ from dcim.models import Device, Interface import json switch_ids = set( Device.objects.filter( role__slug__in=["core-switch", "dist-switch", "field-switch"] ).values_list("id", flat=True) ) ifaces = Interface.objects.filter( device_id__in=switch_ids, cable__isnull=False ).select_related("device") touched_devices = set(switch_ids) edges = set() # (device_id_a, device_id_b), deduplicated, any two adjacent devices on a physical chain for i in ifaces: path = i._path if not path or not path.is_active: continue chain = [] for hop in i.trace(): for item in hop: for t in item: dev = getattr(t, "device", None) if dev: if not chain or chain[-1] != dev.id: chain.append(dev.id) touched_devices.add(dev.id) for a, b in zip(chain, chain[1:]): if a != b: edges.add(tuple(sorted((a, b)))) devs = Device.objects.filter(id__in=touched_devices).select_related( "site", "role", "device_type" ) out = { "devices": [ { "id": d.id, "name": d.name, "role": d.role.slug, "role_name": d.role.name, "site": d.site.name if d.site else "", "status": str(d.status), "device_type": d.device_type.model, } for d in devs ], "edges": [[a, b] for a, b in sorted(edges)], } print(json.dumps(out))