netbox-pathways 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. netbox_pathways-0.1.0/PKG-INFO +145 -0
  2. netbox_pathways-0.1.0/README.md +115 -0
  3. netbox_pathways-0.1.0/netbox_pathways/__init__.py +91 -0
  4. netbox_pathways-0.1.0/netbox_pathways/api/__init__.py +0 -0
  5. netbox_pathways-0.1.0/netbox_pathways/api/external_geo.py +134 -0
  6. netbox_pathways-0.1.0/netbox_pathways/api/geo.py +360 -0
  7. netbox_pathways-0.1.0/netbox_pathways/api/serializers.py +520 -0
  8. netbox_pathways-0.1.0/netbox_pathways/api/traversal.py +48 -0
  9. netbox_pathways-0.1.0/netbox_pathways/api/urls.py +46 -0
  10. netbox_pathways-0.1.0/netbox_pathways/api/views.py +140 -0
  11. netbox_pathways-0.1.0/netbox_pathways/choices.py +122 -0
  12. netbox_pathways-0.1.0/netbox_pathways/filterforms.py +381 -0
  13. netbox_pathways-0.1.0/netbox_pathways/filters.py +622 -0
  14. netbox_pathways-0.1.0/netbox_pathways/forms.py +1020 -0
  15. netbox_pathways-0.1.0/netbox_pathways/geo.py +79 -0
  16. netbox_pathways-0.1.0/netbox_pathways/graph.py +545 -0
  17. netbox_pathways-0.1.0/netbox_pathways/management/__init__.py +0 -0
  18. netbox_pathways-0.1.0/netbox_pathways/management/commands/__init__.py +0 -0
  19. netbox_pathways-0.1.0/netbox_pathways/management/commands/_geodata_worker.py +54 -0
  20. netbox_pathways-0.1.0/netbox_pathways/management/commands/generate_qgis_project.py +141 -0
  21. netbox_pathways-0.1.0/netbox_pathways/management/commands/generate_sample_data.py +550 -0
  22. netbox_pathways-0.1.0/netbox_pathways/management/commands/import_geodata.py +705 -0
  23. netbox_pathways-0.1.0/netbox_pathways/migrations/0001_initial.py +291 -0
  24. netbox_pathways-0.1.0/netbox_pathways/migrations/0002_replace_owner_with_tenant.py +54 -0
  25. netbox_pathways-0.1.0/netbox_pathways/migrations/0003_structure_optional_site_dimensions.py +40 -0
  26. netbox_pathways-0.1.0/netbox_pathways/migrations/0004_circuit_geometry.py +40 -0
  27. netbox_pathways-0.1.0/netbox_pathways/migrations/0005_replace_unique_together_with_constraints.py +39 -0
  28. netbox_pathways-0.1.0/netbox_pathways/migrations/0006_remove_cablesegment_sequence_enter_exit.py +33 -0
  29. netbox_pathways-0.1.0/netbox_pathways/migrations/0007_cable_routing_redesign.py +36 -0
  30. netbox_pathways-0.1.0/netbox_pathways/migrations/0008_conduitbank_pathway_subclass.py +108 -0
  31. netbox_pathways-0.1.0/netbox_pathways/migrations/0009_remove_conduit_unique_position_per_bank_and_more.py +21 -0
  32. netbox_pathways-0.1.0/netbox_pathways/migrations/0010_structure_status.py +18 -0
  33. netbox_pathways-0.1.0/netbox_pathways/migrations/0011_rename_name_to_label.py +42 -0
  34. netbox_pathways-0.1.0/netbox_pathways/migrations/0012_add_filter_field_indexes.py +28 -0
  35. netbox_pathways-0.1.0/netbox_pathways/migrations/0013_plannedroute.py +45 -0
  36. netbox_pathways-0.1.0/netbox_pathways/migrations/0014_plannedroute_parent_split.py +19 -0
  37. netbox_pathways-0.1.0/netbox_pathways/migrations/__init__.py +1 -0
  38. netbox_pathways-0.1.0/netbox_pathways/models.py +879 -0
  39. netbox_pathways-0.1.0/netbox_pathways/navigation.py +207 -0
  40. netbox_pathways-0.1.0/netbox_pathways/registry.py +195 -0
  41. netbox_pathways-0.1.0/netbox_pathways/route_engine.py +255 -0
  42. netbox_pathways-0.1.0/netbox_pathways/routing.py +102 -0
  43. netbox_pathways-0.1.0/netbox_pathways/search.py +126 -0
  44. netbox_pathways-0.1.0/netbox_pathways/signals.py +24 -0
  45. netbox_pathways-0.1.0/netbox_pathways/static/netbox_pathways/css/leaflet-theme.css +375 -0
  46. netbox_pathways-0.1.0/netbox_pathways/static/netbox_pathways/css/pathways-map.css +68 -0
  47. netbox_pathways-0.1.0/netbox_pathways/static/netbox_pathways/qgis/pathways.qml +34 -0
  48. netbox_pathways-0.1.0/netbox_pathways/static/netbox_pathways/qgis/structures.qml +38 -0
  49. netbox_pathways-0.1.0/netbox_pathways/static/netbox_pathways/vendor/MarkerCluster.Default.css +60 -0
  50. netbox_pathways-0.1.0/netbox_pathways/static/netbox_pathways/vendor/MarkerCluster.css +14 -0
  51. netbox_pathways-0.1.0/netbox_pathways/static/netbox_pathways/vendor/leaflet.markercluster.js +2 -0
  52. netbox_pathways-0.1.0/netbox_pathways/tables.py +459 -0
  53. netbox_pathways-0.1.0/netbox_pathways/template_content.py +337 -0
  54. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/aerialspan.html +1 -0
  55. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/buttons/apply_route.html +5 -0
  56. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/buttons/replan_route.html +5 -0
  57. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/buttons/revert_split.html +9 -0
  58. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/buttons/split_route.html +5 -0
  59. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/buttons/view_in_map.html +5 -0
  60. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/cable_route_tab.html +46 -0
  61. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/cablesegment.html +32 -0
  62. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/conduit.html +1 -0
  63. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/conduitbank.html +1 -0
  64. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/conduitjunction.html +1 -0
  65. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/directburied.html +1 -0
  66. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/inc/cable_add_segment_form.html +23 -0
  67. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/inc/cable_route_finder_results.html +47 -0
  68. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/inc/cable_routing_panel.html +40 -0
  69. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/inc/cable_segment_table.html +97 -0
  70. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/inc/connected_structures_panel.html +9 -0
  71. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/inc/constraint_card.html +22 -0
  72. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/inc/geo_map_panel.html +53 -0
  73. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/inc/plannedroute_map_panel.html +31 -0
  74. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/inc/planner_results.html +80 -0
  75. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/innerduct.html +1 -0
  76. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/map.html +518 -0
  77. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/pathway.html +1 -0
  78. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/pathwaylocation.html +1 -0
  79. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/plannedroute.html +110 -0
  80. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/plannedroute_apply.html +56 -0
  81. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/plannedroute_split.html +53 -0
  82. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/pullsheet_detail.html +134 -0
  83. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/pullsheet_list.html +15 -0
  84. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/route_planner.html +713 -0
  85. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/sitegeometry.html +1 -0
  86. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/structure.html +13 -0
  87. netbox_pathways-0.1.0/netbox_pathways/templates/netbox_pathways/widgets/map_widget.html +10 -0
  88. netbox_pathways-0.1.0/netbox_pathways/ui/__init__.py +0 -0
  89. netbox_pathways-0.1.0/netbox_pathways/ui/panels.py +148 -0
  90. netbox_pathways-0.1.0/netbox_pathways/urls.py +243 -0
  91. netbox_pathways-0.1.0/netbox_pathways/views.py +2264 -0
  92. netbox_pathways-0.1.0/netbox_pathways.egg-info/PKG-INFO +145 -0
  93. netbox_pathways-0.1.0/netbox_pathways.egg-info/SOURCES.txt +108 -0
  94. netbox_pathways-0.1.0/netbox_pathways.egg-info/dependency_links.txt +1 -0
  95. netbox_pathways-0.1.0/netbox_pathways.egg-info/requires.txt +13 -0
  96. netbox_pathways-0.1.0/netbox_pathways.egg-info/top_level.txt +1 -0
  97. netbox_pathways-0.1.0/pyproject.toml +109 -0
  98. netbox_pathways-0.1.0/setup.cfg +4 -0
  99. netbox_pathways-0.1.0/tests/test_adjacency.py +68 -0
  100. netbox_pathways-0.1.0/tests/test_cable_segment.py +122 -0
  101. netbox_pathways-0.1.0/tests/test_circuit_geometry.py +114 -0
  102. netbox_pathways-0.1.0/tests/test_endpoint_validation.py +276 -0
  103. netbox_pathways-0.1.0/tests/test_external_geo.py +100 -0
  104. netbox_pathways-0.1.0/tests/test_geo_api.py +419 -0
  105. netbox_pathways-0.1.0/tests/test_graph.py +98 -0
  106. netbox_pathways-0.1.0/tests/test_map_view.py +169 -0
  107. netbox_pathways-0.1.0/tests/test_planned_route.py +96 -0
  108. netbox_pathways-0.1.0/tests/test_registry.py +158 -0
  109. netbox_pathways-0.1.0/tests/test_route_engine.py +260 -0
  110. netbox_pathways-0.1.0/tests/test_routing.py +85 -0
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: netbox-pathways
3
+ Version: 0.1.0
4
+ Summary: NetBox plugin for physical cable plant infrastructure documentation with GIS capabilities
5
+ Author-email: Jonathan Senecal <contact@jonathansenecal.com>
6
+ Project-URL: Homepage, https://github.com/jsenecal/netbox-pathways
7
+ Project-URL: Source, https://github.com/jsenecal/netbox-pathways
8
+ Project-URL: Tracker, https://github.com/jsenecal/netbox-pathways/issues
9
+ Project-URL: Documentation, https://jsenecal.github.io/netbox-pathways/
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: djangorestframework-gis>=1.2.0
20
+ Requires-Dist: networkx>=3.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: bumpver; extra == "dev"
23
+ Requires-Dist: pre-commit>=4.0.0; extra == "dev"
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: pytest-django>=4.5.0; extra == "dev"
26
+ Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
27
+ Requires-Dist: ruff; extra == "dev"
28
+ Provides-Extra: docs
29
+ Requires-Dist: zensical; extra == "docs"
30
+
31
+ # netbox-pathways
32
+
33
+ > A NetBox plugin for documenting physical cable plant infrastructure with PostGIS integration. Track conduits, aerial spans, structures, and cable routing with geographic data, comparable to SmallWorld or ArcGIS with ArcFM for outside/inside plant documentation.
34
+
35
+ [![PyPI](https://img.shields.io/pypi/v/netbox-pathways.svg)](https://pypi.org/project/netbox-pathways/)
36
+ [![Python](https://img.shields.io/pypi/pyversions/netbox-pathways.svg)](https://pypi.org/project/netbox-pathways/)
37
+ [![NetBox](https://img.shields.io/badge/NetBox-4.5%2B-success.svg)](https://github.com/netbox-community/netbox)
38
+ [![CI](https://github.com/jsenecal/netbox-pathways/actions/workflows/ci.yml/badge.svg)](https://github.com/jsenecal/netbox-pathways/actions/workflows/ci.yml)
39
+ [![codecov](https://codecov.io/gh/jsenecal/netbox-pathways/branch/main/graph/badge.svg)](https://codecov.io/gh/jsenecal/netbox-pathways)
40
+ ![Status](https://img.shields.io/badge/status-alpha-orange)
41
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
42
+
43
+ ## Features
44
+
45
+ - **Structures** -- poles, manholes, cabinets, equipment rooms, and more with PostGIS geometry (point or polygon).
46
+ - **Pathways** -- conduits, aerial spans, direct buried, innerducts, cable trays with PostGIS line geometry.
47
+ - **Conduit Banks and Junctions** -- model conduit bank configurations and mid-span Y-tees.
48
+ - **Cable Routing** -- track which NetBox cables traverse which pathways, in sequence.
49
+ - **Pull Sheets** -- printable cable routing documents for field crews.
50
+ - **GeoJSON API** -- standard GeoJSON endpoints for QGIS and other GIS clients.
51
+ - **QGIS Integration** -- style files, project generator, and documentation.
52
+ - **Geometry Editing** -- draw and edit geometries directly in NetBox forms via Leaflet map widgets.
53
+ - **Interactive Map** -- built-in Leaflet map for quick visualization.
54
+ - **Indoor / Outdoor** -- pathways can terminate at structures (outdoor) or NetBox locations (indoor).
55
+
56
+ ## Compatibility
57
+
58
+ | Plugin version | NetBox version | Python | PostgreSQL |
59
+ |----------------|----------------|-----------|---------------------|
60
+ | 0.1.x | 4.5.3+ | 3.12-3.14 | 16+ with PostGIS 3.4|
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ pip install netbox-pathways
66
+ ```
67
+
68
+ In your NetBox `configuration.py`:
69
+
70
+ ```python
71
+ PLUGINS = ["netbox_pathways"]
72
+
73
+ PLUGINS_CONFIG = {
74
+ "netbox_pathways": {
75
+ "srid": 3348, # REQUIRED -- your EPSG code (see warning below)
76
+ "map_center_lat": 45.5, # default map center latitude (optional)
77
+ "map_center_lon": -73.5,# default map center longitude (optional)
78
+ "map_zoom": 10, # default map zoom level (optional)
79
+ },
80
+ }
81
+ ```
82
+
83
+ Run migrations and restart:
84
+
85
+ ```bash
86
+ cd /opt/netbox/netbox
87
+ python manage.py migrate
88
+ python manage.py collectstatic --no-input
89
+ sudo systemctl restart netbox netbox-rq
90
+ ```
91
+
92
+ ## Configuration
93
+
94
+ ### SRID is immutable after installation
95
+
96
+ The `srid` setting defines the coordinate reference system used for **all** geometry columns in the database. It is baked into the database schema at migration time.
97
+
98
+ **Changing the SRID after data has been loaded WILL CORRUPT YOUR SPATIAL DATA.** PostgreSQL / PostGIS does NOT automatically re-project existing coordinates when the column SRID changes. Geometries will have wrong coordinates in the new CRS with no way to recover them automatically.
99
+
100
+ Choose your SRID carefully before first deployment. Common choices:
101
+
102
+ | EPSG | Name | Notes |
103
+ |---------|-----------------------------------------------|--------------------------------------------------|
104
+ | `4326` | WGS84 (GPS coordinates, degrees) | Global, but distorts distances and areas. |
105
+ | `3857` | Web Mercator (meters) | Used by Google Maps, OSM tiles. |
106
+ | `3348` | NAD83(CSRS) / Statistics Canada Lambert (m) | Good for Canada. |
107
+ | `2154` | RGF93 / Lambert-93 (meters) | Good for France. |
108
+ | `32632` | WGS84 / UTM zone 32N (meters) | Good for central Europe. |
109
+
110
+ If you need to change SRID after deployment, you must manually re-project all geometry data using PostGIS `ST_Transform()` and update the column SRID definitions. This is an advanced DBA operation; back up everything first.
111
+
112
+ ## QGIS quick start
113
+
114
+ Generate a QGIS project with all layers pre-configured:
115
+
116
+ ```bash
117
+ python manage.py generate_qgis_project \
118
+ --url https://your-netbox \
119
+ --token your-api-token
120
+ ```
121
+
122
+ Open the generated `.qgs` file in QGIS. Style files (`.qml`) ship under `static/netbox_pathways/qgis/` and can be loaded via Layer Properties > Style > Load Style.
123
+
124
+ ## REST and GeoJSON API
125
+
126
+ All resources are exposed under `/api/plugins/pathways/`. GeoJSON variants live under `/api/plugins/pathways/geo/` for direct QGIS / OGR consumption. See [API Examples](https://jsenecal.github.io/netbox-pathways/developer/api-examples/) for full endpoint coverage.
127
+
128
+ ## Documentation
129
+
130
+ Full documentation: **[jsenecal.github.io/netbox-pathways](https://jsenecal.github.io/netbox-pathways/)**
131
+
132
+ - [Installation](https://jsenecal.github.io/netbox-pathways/getting-started/installation/)
133
+ - [Configuration](https://jsenecal.github.io/netbox-pathways/getting-started/configuration/)
134
+ - [Concepts](https://jsenecal.github.io/netbox-pathways/user-guide/concepts/)
135
+ - [QGIS Integration](https://jsenecal.github.io/netbox-pathways/user-guide/qgis-integration/)
136
+ - [Architecture](https://jsenecal.github.io/netbox-pathways/developer/architecture/)
137
+ - [GeoJSON API reference](https://jsenecal.github.io/netbox-pathways/reference/geojson-api/)
138
+
139
+ ## Contributing
140
+
141
+ PRs welcome. Use conventional-commits PR titles (`feat:`, `fix:`, `chore:`, `docs:`, ...) -- release-drafter assembles release notes from them. Run `make setup` after cloning to install dev dependencies and the pre-commit hooks (including the AI-attribution-rejecting `commit-msg` hook).
142
+
143
+ ## License
144
+
145
+ [Apache License 2.0](LICENSE).
@@ -0,0 +1,115 @@
1
+ # netbox-pathways
2
+
3
+ > A NetBox plugin for documenting physical cable plant infrastructure with PostGIS integration. Track conduits, aerial spans, structures, and cable routing with geographic data, comparable to SmallWorld or ArcGIS with ArcFM for outside/inside plant documentation.
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/netbox-pathways.svg)](https://pypi.org/project/netbox-pathways/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/netbox-pathways.svg)](https://pypi.org/project/netbox-pathways/)
7
+ [![NetBox](https://img.shields.io/badge/NetBox-4.5%2B-success.svg)](https://github.com/netbox-community/netbox)
8
+ [![CI](https://github.com/jsenecal/netbox-pathways/actions/workflows/ci.yml/badge.svg)](https://github.com/jsenecal/netbox-pathways/actions/workflows/ci.yml)
9
+ [![codecov](https://codecov.io/gh/jsenecal/netbox-pathways/branch/main/graph/badge.svg)](https://codecov.io/gh/jsenecal/netbox-pathways)
10
+ ![Status](https://img.shields.io/badge/status-alpha-orange)
11
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
12
+
13
+ ## Features
14
+
15
+ - **Structures** -- poles, manholes, cabinets, equipment rooms, and more with PostGIS geometry (point or polygon).
16
+ - **Pathways** -- conduits, aerial spans, direct buried, innerducts, cable trays with PostGIS line geometry.
17
+ - **Conduit Banks and Junctions** -- model conduit bank configurations and mid-span Y-tees.
18
+ - **Cable Routing** -- track which NetBox cables traverse which pathways, in sequence.
19
+ - **Pull Sheets** -- printable cable routing documents for field crews.
20
+ - **GeoJSON API** -- standard GeoJSON endpoints for QGIS and other GIS clients.
21
+ - **QGIS Integration** -- style files, project generator, and documentation.
22
+ - **Geometry Editing** -- draw and edit geometries directly in NetBox forms via Leaflet map widgets.
23
+ - **Interactive Map** -- built-in Leaflet map for quick visualization.
24
+ - **Indoor / Outdoor** -- pathways can terminate at structures (outdoor) or NetBox locations (indoor).
25
+
26
+ ## Compatibility
27
+
28
+ | Plugin version | NetBox version | Python | PostgreSQL |
29
+ |----------------|----------------|-----------|---------------------|
30
+ | 0.1.x | 4.5.3+ | 3.12-3.14 | 16+ with PostGIS 3.4|
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install netbox-pathways
36
+ ```
37
+
38
+ In your NetBox `configuration.py`:
39
+
40
+ ```python
41
+ PLUGINS = ["netbox_pathways"]
42
+
43
+ PLUGINS_CONFIG = {
44
+ "netbox_pathways": {
45
+ "srid": 3348, # REQUIRED -- your EPSG code (see warning below)
46
+ "map_center_lat": 45.5, # default map center latitude (optional)
47
+ "map_center_lon": -73.5,# default map center longitude (optional)
48
+ "map_zoom": 10, # default map zoom level (optional)
49
+ },
50
+ }
51
+ ```
52
+
53
+ Run migrations and restart:
54
+
55
+ ```bash
56
+ cd /opt/netbox/netbox
57
+ python manage.py migrate
58
+ python manage.py collectstatic --no-input
59
+ sudo systemctl restart netbox netbox-rq
60
+ ```
61
+
62
+ ## Configuration
63
+
64
+ ### SRID is immutable after installation
65
+
66
+ The `srid` setting defines the coordinate reference system used for **all** geometry columns in the database. It is baked into the database schema at migration time.
67
+
68
+ **Changing the SRID after data has been loaded WILL CORRUPT YOUR SPATIAL DATA.** PostgreSQL / PostGIS does NOT automatically re-project existing coordinates when the column SRID changes. Geometries will have wrong coordinates in the new CRS with no way to recover them automatically.
69
+
70
+ Choose your SRID carefully before first deployment. Common choices:
71
+
72
+ | EPSG | Name | Notes |
73
+ |---------|-----------------------------------------------|--------------------------------------------------|
74
+ | `4326` | WGS84 (GPS coordinates, degrees) | Global, but distorts distances and areas. |
75
+ | `3857` | Web Mercator (meters) | Used by Google Maps, OSM tiles. |
76
+ | `3348` | NAD83(CSRS) / Statistics Canada Lambert (m) | Good for Canada. |
77
+ | `2154` | RGF93 / Lambert-93 (meters) | Good for France. |
78
+ | `32632` | WGS84 / UTM zone 32N (meters) | Good for central Europe. |
79
+
80
+ If you need to change SRID after deployment, you must manually re-project all geometry data using PostGIS `ST_Transform()` and update the column SRID definitions. This is an advanced DBA operation; back up everything first.
81
+
82
+ ## QGIS quick start
83
+
84
+ Generate a QGIS project with all layers pre-configured:
85
+
86
+ ```bash
87
+ python manage.py generate_qgis_project \
88
+ --url https://your-netbox \
89
+ --token your-api-token
90
+ ```
91
+
92
+ Open the generated `.qgs` file in QGIS. Style files (`.qml`) ship under `static/netbox_pathways/qgis/` and can be loaded via Layer Properties > Style > Load Style.
93
+
94
+ ## REST and GeoJSON API
95
+
96
+ All resources are exposed under `/api/plugins/pathways/`. GeoJSON variants live under `/api/plugins/pathways/geo/` for direct QGIS / OGR consumption. See [API Examples](https://jsenecal.github.io/netbox-pathways/developer/api-examples/) for full endpoint coverage.
97
+
98
+ ## Documentation
99
+
100
+ Full documentation: **[jsenecal.github.io/netbox-pathways](https://jsenecal.github.io/netbox-pathways/)**
101
+
102
+ - [Installation](https://jsenecal.github.io/netbox-pathways/getting-started/installation/)
103
+ - [Configuration](https://jsenecal.github.io/netbox-pathways/getting-started/configuration/)
104
+ - [Concepts](https://jsenecal.github.io/netbox-pathways/user-guide/concepts/)
105
+ - [QGIS Integration](https://jsenecal.github.io/netbox-pathways/user-guide/qgis-integration/)
106
+ - [Architecture](https://jsenecal.github.io/netbox-pathways/developer/architecture/)
107
+ - [GeoJSON API reference](https://jsenecal.github.io/netbox-pathways/reference/geojson-api/)
108
+
109
+ ## Contributing
110
+
111
+ PRs welcome. Use conventional-commits PR titles (`feat:`, `fix:`, `chore:`, `docs:`, ...) -- release-drafter assembles release notes from them. Run `make setup` after cloning to install dev dependencies and the pre-commit hooks (including the AI-attribution-rejecting `commit-msg` hook).
112
+
113
+ ## License
114
+
115
+ [Apache License 2.0](LICENSE).
@@ -0,0 +1,91 @@
1
+ from netbox.plugins import PluginConfig
2
+
3
+ __version__ = "0.1.0"
4
+
5
+
6
+ class NetBoxPathwaysConfig(PluginConfig):
7
+ name = "netbox_pathways"
8
+ verbose_name = "NetBox Pathways"
9
+ description = "Physical cable plant infrastructure documentation with GIS capabilities"
10
+ version = __version__
11
+ author = "Jonathan Senecal"
12
+ author_email = "contact@jonathansenecal.com"
13
+ base_url = "pathways"
14
+ required_settings = ["srid"]
15
+ default_settings = {
16
+ "map_center_lat": 45.5017,
17
+ "map_center_lon": -73.5673,
18
+ "map_zoom": 10,
19
+ "map_tiles": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
20
+ "map_max_native_zoom": 19,
21
+ "map_attribution": "&copy; OpenStreetMap contributors",
22
+ "map_overlays": [],
23
+ }
24
+ django_apps = [
25
+ "django.contrib.gis",
26
+ "rest_framework_gis",
27
+ ]
28
+
29
+ # Populated in ready(), consumed by template_content.py
30
+ _map_config = {}
31
+
32
+ def ready(self):
33
+ from django.conf import settings
34
+
35
+ plugin_cfg = settings.PLUGINS_CONFIG.get("netbox_pathways", {})
36
+ max_zoom = 22
37
+
38
+ base_layers = plugin_cfg.get("map_base_layers")
39
+ if base_layers:
40
+ tiles = []
41
+ for layer in base_layers:
42
+ tile = dict(layer.items())
43
+ tile.setdefault("maxZoom", max_zoom)
44
+ tiles.append(tile)
45
+ else:
46
+ tiles_url = plugin_cfg.get(
47
+ "map_tiles",
48
+ "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
49
+ )
50
+ max_native = plugin_cfg.get("map_max_native_zoom", 19)
51
+ attribution = plugin_cfg.get(
52
+ "map_attribution",
53
+ "&copy; OpenStreetMap contributors",
54
+ )
55
+ tiles = [
56
+ {
57
+ "name": "Street",
58
+ "url": tiles_url,
59
+ "maxZoom": max_zoom,
60
+ "maxNativeZoom": max_native,
61
+ "attribution": attribution,
62
+ },
63
+ {
64
+ "name": "Satellite",
65
+ "url": (
66
+ "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
67
+ ),
68
+ "maxZoom": max_zoom,
69
+ "maxNativeZoom": 19,
70
+ "attribution": "Esri World Imagery",
71
+ },
72
+ ]
73
+
74
+ NetBoxPathwaysConfig._map_config = {
75
+ "baseLayers": tiles,
76
+ "center": [
77
+ plugin_cfg.get("map_center_lat", 45.5017),
78
+ plugin_cfg.get("map_center_lon", -73.5673),
79
+ ],
80
+ "zoom": plugin_cfg.get("map_zoom", 10),
81
+ "minZoom": 1,
82
+ "maxZoom": max_zoom,
83
+ }
84
+
85
+ super().ready()
86
+
87
+ # Register signals
88
+ from . import signals # noqa: F401
89
+
90
+
91
+ config = NetBoxPathwaysConfig
File without changes
@@ -0,0 +1,134 @@
1
+ """GeoJSON endpoint for reference-mode external map layers.
2
+
3
+ Resolves geometry by joining through the FK declared in the layer
4
+ registration, transforms to WGS84, applies bbox filtering, and returns
5
+ a standard GeoJSON FeatureCollection.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+
12
+ from django.contrib.gis.db import models as gis_models
13
+ from django.contrib.gis.db.models.functions import Transform
14
+ from django.contrib.gis.geos import Polygon
15
+ from django.db import models as db_models
16
+ from django.http import Http404, JsonResponse
17
+ from rest_framework.permissions import IsAuthenticated
18
+ from rest_framework.views import APIView
19
+
20
+ from netbox_pathways.api.geo import MAX_GEO_RESULTS
21
+ from netbox_pathways.geo import LEAFLET_SRID
22
+ from netbox_pathways.registry import SUPPORTED_GEO_MODELS, registry
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ def _resolve_geo_column(model, geometry_field: str) -> tuple[str, str]:
28
+ """Return (fk_field__geo_column, target_model_label) for the FK.
29
+
30
+ Raises ValueError if the FK target is not in SUPPORTED_GEO_MODELS.
31
+ """
32
+ fk = model._meta.get_field(geometry_field)
33
+ target = fk.related_model
34
+ target_label = f"{target._meta.app_label}.{target._meta.model_name}"
35
+ # Case-insensitive lookup to tolerate label casing
36
+ for supported_label, geo_col in SUPPORTED_GEO_MODELS.items():
37
+ if supported_label.lower() == target_label.lower():
38
+ return f"{geometry_field}__{geo_col}", supported_label
39
+ raise ValueError(
40
+ f"FK '{geometry_field}' on {model.__name__} points to {target_label}, which is not in SUPPORTED_GEO_MODELS."
41
+ )
42
+
43
+
44
+ def _build_properties(obj, feature_fields: list[str] | None, model) -> dict:
45
+ """Build GeoJSON properties dict from model instance."""
46
+ props: dict = {"id": obj.pk}
47
+
48
+ if feature_fields is not None:
49
+ fields_to_use = feature_fields
50
+ else:
51
+ # Auto-detect scalar fields + FK display values
52
+ fields_to_use = []
53
+ for f in model._meta.get_fields():
54
+ if not hasattr(f, "column"):
55
+ continue # skip reverse relations, M2M, etc.
56
+ if f.name in ("id", "pk"):
57
+ continue # already handled above
58
+ if isinstance(f, gis_models.GeometryField):
59
+ continue # skip geometry fields
60
+ if isinstance(f, (db_models.BinaryField, db_models.JSONField)):
61
+ continue # skip non-serializable / large fields
62
+ fields_to_use.append(f.name)
63
+
64
+ for fname in fields_to_use:
65
+ val = getattr(obj, fname, None)
66
+ # FK → use __str__ of related object
67
+ if hasattr(val, "pk"):
68
+ props[fname] = str(val)
69
+ elif val is not None:
70
+ props[fname] = val
71
+ else:
72
+ props[fname] = None
73
+ return props
74
+
75
+
76
+ class ExternalLayerGeoView(APIView):
77
+ """Serve GeoJSON for a reference-mode registered layer."""
78
+
79
+ permission_classes = [IsAuthenticated]
80
+
81
+ def get(self, request, layer_name: str):
82
+ layer_reg = registry.get(layer_name)
83
+ if layer_reg is None or layer_reg.source != "reference":
84
+ raise Http404(f"No reference-mode layer named '{layer_name}'.")
85
+
86
+ qs = layer_reg.queryset(request)
87
+ model = qs.model
88
+
89
+ fk_geo_path, _target_label = _resolve_geo_column(
90
+ model,
91
+ layer_reg.geometry_field,
92
+ )
93
+
94
+ # Annotate with WGS84 geometry
95
+ qs = qs.annotate(
96
+ _geo_4326=Transform(fk_geo_path, LEAFLET_SRID),
97
+ ).exclude(_geo_4326__isnull=True)
98
+
99
+ # Bbox filtering
100
+ bbox_str = request.query_params.get("bbox", "")
101
+ if bbox_str:
102
+ try:
103
+ w, s, e, n = (float(x) for x in bbox_str.split(","))
104
+ bbox_poly = Polygon.from_bbox((w, s, e, n))
105
+ bbox_poly.srid = LEAFLET_SRID
106
+ qs = qs.filter(_geo_4326__intersects=bbox_poly)
107
+ except (ValueError, TypeError):
108
+ pass # ignore malformed bbox
109
+
110
+ qs = qs[:MAX_GEO_RESULTS]
111
+
112
+ features = []
113
+ for obj in qs:
114
+ geom = obj._geo_4326
115
+ if geom is None:
116
+ continue
117
+ props = _build_properties(obj, layer_reg.feature_fields, model)
118
+ features.append(
119
+ {
120
+ "type": "Feature",
121
+ "geometry": {
122
+ "type": geom.geom_type,
123
+ "coordinates": geom.coords,
124
+ },
125
+ "properties": props,
126
+ }
127
+ )
128
+
129
+ return JsonResponse(
130
+ {
131
+ "type": "FeatureCollection",
132
+ "features": features,
133
+ }
134
+ )