netbox-pathways 0.2.0__tar.gz → 0.2.1__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 (141) hide show
  1. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/PKG-INFO +8 -1
  2. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/README.md +7 -0
  3. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/__init__.py +6 -2
  4. netbox_pathways-0.2.1/netbox_pathways/checks.py +86 -0
  5. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0004_circuit_geometry.py +5 -1
  6. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways.egg-info/PKG-INFO +8 -1
  7. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways.egg-info/SOURCES.txt +3 -1
  8. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/pyproject.toml +2 -2
  9. netbox_pathways-0.2.1/tests/test_srid_checks.py +73 -0
  10. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/api/__init__.py +0 -0
  11. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/api/external_geo.py +0 -0
  12. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/api/geo.py +0 -0
  13. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/api/serializers.py +0 -0
  14. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/api/traversal.py +0 -0
  15. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/api/urls.py +0 -0
  16. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/api/views.py +0 -0
  17. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/choices.py +0 -0
  18. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/filterforms.py +0 -0
  19. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/filters.py +0 -0
  20. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/forms.py +0 -0
  21. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/geo.py +0 -0
  22. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/graph.py +0 -0
  23. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/graphql/__init__.py +0 -0
  24. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/graphql/filters.py +0 -0
  25. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/graphql/schema.py +0 -0
  26. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/graphql/types.py +0 -0
  27. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/management/__init__.py +0 -0
  28. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/management/commands/__init__.py +0 -0
  29. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/management/commands/_geodata_worker.py +0 -0
  30. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/management/commands/generate_qgis_project.py +0 -0
  31. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/management/commands/generate_sample_data.py +0 -0
  32. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/management/commands/import_geodata.py +0 -0
  33. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0001_initial.py +0 -0
  34. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0002_replace_owner_with_tenant.py +0 -0
  35. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0003_structure_optional_site_dimensions.py +0 -0
  36. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0005_replace_unique_together_with_constraints.py +0 -0
  37. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0006_remove_cablesegment_sequence_enter_exit.py +0 -0
  38. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0007_cable_routing_redesign.py +0 -0
  39. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0008_conduitbank_pathway_subclass.py +0 -0
  40. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0009_remove_conduit_unique_position_per_bank_and_more.py +0 -0
  41. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0010_structure_status.py +0 -0
  42. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0011_rename_name_to_label.py +0 -0
  43. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0012_add_filter_field_indexes.py +0 -0
  44. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0013_plannedroute.py +0 -0
  45. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0014_plannedroute_parent_split.py +0 -0
  46. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0015_installer_and_commissioned_date.py +0 -0
  47. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/0016_cablesegment_lashed_with.py +0 -0
  48. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/migrations/__init__.py +0 -0
  49. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/models.py +0 -0
  50. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/navigation.py +0 -0
  51. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/registry.py +0 -0
  52. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/route_engine.py +0 -0
  53. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/routing.py +0 -0
  54. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/search.py +0 -0
  55. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/signals.py +0 -0
  56. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/css/leaflet-theme.css +0 -0
  57. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/css/pathways-map.css +0 -0
  58. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/detail-map.min.js +0 -0
  59. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/detail-map.min.js.map +0 -0
  60. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/endpoint-markers.min.js +0 -0
  61. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/endpoint-markers.min.js.map +0 -0
  62. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/pathways-field.min.js +0 -0
  63. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/pathways-field.min.js.map +0 -0
  64. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/pathways-map.min.js +0 -0
  65. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/pathways-map.min.js.map +0 -0
  66. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/route-planner-map.min.js +0 -0
  67. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/dist/route-planner-map.min.js.map +0 -0
  68. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/qgis/pathways.qml +0 -0
  69. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/qgis/structures.qml +0 -0
  70. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/MarkerCluster.Default.css +0 -0
  71. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/MarkerCluster.css +0 -0
  72. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/geoman/leaflet-geoman.css +0 -0
  73. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/geoman/leaflet-geoman.js +0 -0
  74. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/layers-2x.png +0 -0
  75. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/layers.png +0 -0
  76. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/marker-icon-2x.png +0 -0
  77. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/marker-icon.png +0 -0
  78. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/marker-shadow.png +0 -0
  79. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/leaflet/leaflet.css +0 -0
  80. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/leaflet/leaflet.js +0 -0
  81. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/static/netbox_pathways/vendor/leaflet.markercluster.js +0 -0
  82. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/tables.py +0 -0
  83. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/template_content.py +0 -0
  84. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/aerialspan.html +0 -0
  85. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/buttons/apply_route.html +0 -0
  86. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/buttons/replan_route.html +0 -0
  87. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/buttons/revert_split.html +0 -0
  88. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/buttons/split_route.html +0 -0
  89. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/buttons/view_in_map.html +0 -0
  90. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/cable_route_tab.html +0 -0
  91. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/cablesegment.html +0 -0
  92. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/conduit.html +0 -0
  93. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/conduitbank.html +0 -0
  94. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/conduitjunction.html +0 -0
  95. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/directburied.html +0 -0
  96. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/inc/cable_add_segment_form.html +0 -0
  97. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/inc/cable_route_finder_results.html +0 -0
  98. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/inc/cable_routing_panel.html +0 -0
  99. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/inc/cable_segment_table.html +0 -0
  100. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/inc/connected_structures_panel.html +0 -0
  101. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/inc/constraint_card.html +0 -0
  102. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/inc/geo_map_panel.html +0 -0
  103. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/inc/plannedroute_map_panel.html +0 -0
  104. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/inc/planner_results.html +0 -0
  105. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/innerduct.html +0 -0
  106. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/map.html +0 -0
  107. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/pathway.html +0 -0
  108. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/pathwaylocation.html +0 -0
  109. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/plannedroute.html +0 -0
  110. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/plannedroute_apply.html +0 -0
  111. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/plannedroute_split.html +0 -0
  112. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/pullsheet_detail.html +0 -0
  113. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/pullsheet_list.html +0 -0
  114. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/route_planner.html +0 -0
  115. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/sitegeometry.html +0 -0
  116. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/structure.html +0 -0
  117. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/templates/netbox_pathways/widgets/map_widget.html +0 -0
  118. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/ui/__init__.py +0 -0
  119. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/ui/panels.py +0 -0
  120. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/urls.py +0 -0
  121. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways/views.py +0 -0
  122. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways.egg-info/dependency_links.txt +0 -0
  123. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways.egg-info/requires.txt +0 -0
  124. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/netbox_pathways.egg-info/top_level.txt +0 -0
  125. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/setup.cfg +0 -0
  126. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_adjacency.py +0 -0
  127. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_cable_segment.py +0 -0
  128. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_circuit_geometry.py +0 -0
  129. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_endpoint_validation.py +0 -0
  130. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_external_geo.py +0 -0
  131. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_field_additions.py +0 -0
  132. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_geo_api.py +0 -0
  133. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_graph.py +0 -0
  134. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_graphql.py +0 -0
  135. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_import_geodata.py +0 -0
  136. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_lashed_with.py +0 -0
  137. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_map_view.py +0 -0
  138. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_planned_route.py +0 -0
  139. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_registry.py +0 -0
  140. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_route_engine.py +0 -0
  141. {netbox_pathways-0.2.0 → netbox_pathways-0.2.1}/tests/test_routing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netbox-pathways
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: NetBox plugin for physical cable plant infrastructure documentation with GIS capabilities
5
5
  Author-email: Jonathan Senecal <contact@jonathansenecal.com>
6
6
  Project-URL: Homepage, https://github.com/jsenecal/netbox-pathways
@@ -179,6 +179,13 @@ Full documentation: **[jsenecal.github.io/netbox-pathways](https://jsenecal.gith
179
179
  - [Architecture](https://jsenecal.github.io/netbox-pathways/developer/architecture/)
180
180
  - [GeoJSON API reference](https://jsenecal.github.io/netbox-pathways/reference/geojson-api/)
181
181
 
182
+ ## Related plugins
183
+
184
+ netbox-pathways is part of a three-plugin set that models the full optical transport stack:
185
+
186
+ - **[netbox-fms](https://github.com/jsenecal/netbox-fms)** -- Fiber Management System. Defines fiber cable construction (buffer tubes, ribbons, strands), plans splices in closures, and provisions end-to-end fiber circuits. Pathways tracks *where cables run*; FMS tracks *what is inside them and how strands are spliced*.
187
+ - **[netbox-wdm](https://github.com/jsenecal/netbox-wdm)** -- WDM (Wavelength Division Multiplexing) device management. Models ITU channel plans, ROADM mappings, and wavelength services that ride on top of the fiber circuits FMS provisions, over the cables routed through Pathways.
188
+
182
189
  ## Contributing
183
190
 
184
191
  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).
@@ -149,6 +149,13 @@ Full documentation: **[jsenecal.github.io/netbox-pathways](https://jsenecal.gith
149
149
  - [Architecture](https://jsenecal.github.io/netbox-pathways/developer/architecture/)
150
150
  - [GeoJSON API reference](https://jsenecal.github.io/netbox-pathways/reference/geojson-api/)
151
151
 
152
+ ## Related plugins
153
+
154
+ netbox-pathways is part of a three-plugin set that models the full optical transport stack:
155
+
156
+ - **[netbox-fms](https://github.com/jsenecal/netbox-fms)** -- Fiber Management System. Defines fiber cable construction (buffer tubes, ribbons, strands), plans splices in closures, and provisions end-to-end fiber circuits. Pathways tracks *where cables run*; FMS tracks *what is inside them and how strands are spliced*.
157
+ - **[netbox-wdm](https://github.com/jsenecal/netbox-wdm)** -- WDM (Wavelength Division Multiplexing) device management. Models ITU channel plans, ROADM mappings, and wavelength services that ride on top of the fiber circuits FMS provisions, over the cables routed through Pathways.
158
+
152
159
  ## Contributing
153
160
 
154
161
  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).
@@ -2,7 +2,7 @@ import logging
2
2
 
3
3
  from netbox.plugins import PluginConfig
4
4
 
5
- __version__ = "0.2.0"
5
+ __version__ = "0.2.1"
6
6
 
7
7
  logger = logging.getLogger(__name__)
8
8
 
@@ -89,8 +89,12 @@ class NetBoxPathwaysConfig(PluginConfig):
89
89
 
90
90
  super().ready()
91
91
 
92
- # Register signals
92
+ from django.core.checks import register
93
+
93
94
  from . import signals # noqa: F401
95
+ from .checks import check_geometry_column_srids
96
+
97
+ register(check_geometry_column_srids)
94
98
 
95
99
  logger.info("%s plugin loaded", self.name)
96
100
 
@@ -0,0 +1,86 @@
1
+ """
2
+ System checks for the netbox_pathways plugin.
3
+
4
+ The storage SRID is baked into PostGIS columns at migrate time, but
5
+ get_srid() is read from PLUGINS_CONFIG at form/serializer save time. If
6
+ the two ever drift apart -- either because a migration shipped with a
7
+ hardcoded SRID, or because an operator edited PLUGINS_CONFIG after
8
+ applying migrations -- inserts fail with a SRID mismatch and crash the
9
+ end user back to the home page (see issue #5).
10
+
11
+ This module introspects geometry_columns and emits a checks.Error for
12
+ every column whose stored SRID does not match get_srid(), with a hint
13
+ that points at both remediation paths (revert config, or write an
14
+ ST_Transform migration).
15
+ """
16
+
17
+ from django.apps import apps
18
+ from django.core.checks import Error
19
+ from django.db import connection
20
+ from django.db.utils import OperationalError, ProgrammingError
21
+
22
+ from .geo import get_srid
23
+
24
+ CHECK_ID = "netbox_pathways.E001"
25
+
26
+
27
+ def _introspect_column_srids(connection, app_label):
28
+ """Return {(table, column): srid} for geometry columns in the app's tables."""
29
+ if connection.vendor != "postgresql":
30
+ return {}
31
+ try:
32
+ with connection.cursor() as cursor:
33
+ cursor.execute(
34
+ "SELECT f_table_name, f_geometry_column, srid FROM geometry_columns WHERE f_table_name LIKE %s",
35
+ [f"{app_label}_%"],
36
+ )
37
+ return {(row[0], row[1]): row[2] for row in cursor.fetchall()}
38
+ except (OperationalError, ProgrammingError):
39
+ return {}
40
+
41
+
42
+ def check_geometry_column_srids(app_configs=None, **kwargs):
43
+ """Compare introspected column SRIDs to PLUGINS_CONFIG['netbox_pathways']['srid']."""
44
+ from django.core.exceptions import ImproperlyConfigured
45
+
46
+ try:
47
+ expected_srid = get_srid()
48
+ except ImproperlyConfigured:
49
+ return []
50
+
51
+ actual = _introspect_column_srids(connection, "netbox_pathways")
52
+ if not actual:
53
+ return []
54
+
55
+ try:
56
+ models = apps.get_app_config("netbox_pathways").get_models()
57
+ except LookupError:
58
+ return []
59
+
60
+ errors = []
61
+ for model in models:
62
+ table = model._meta.db_table
63
+ for field in model._meta.get_fields():
64
+ if not hasattr(field, "srid") or field.srid is None:
65
+ continue
66
+ actual_srid = actual.get((table, field.column))
67
+ if actual_srid is None or actual_srid == expected_srid:
68
+ continue
69
+ errors.append(
70
+ Error(
71
+ f"Geometry column {table}.{field.column} has SRID {actual_srid}, "
72
+ f"but PLUGINS_CONFIG['netbox_pathways']['srid'] is {expected_srid}. "
73
+ "Form submissions and API writes will fail with 'Geometry SRID does "
74
+ "not match column SRID'.",
75
+ hint=(
76
+ "Storage SRID is immutable after migrations apply. Either "
77
+ f"set PLUGINS_CONFIG['netbox_pathways']['srid'] = {actual_srid} "
78
+ "to match the existing columns, or write a data migration that "
79
+ f"runs ST_Transform(<column>, {expected_srid}) on every affected "
80
+ "row before re-typing the column."
81
+ ),
82
+ id=CHECK_ID,
83
+ obj=model,
84
+ )
85
+ )
86
+ return errors
@@ -7,6 +7,10 @@ import taggit.managers
7
7
  import utilities.json
8
8
  from django.db import migrations, models
9
9
 
10
+ from netbox_pathways.geo import get_srid
11
+
12
+ _SRID = get_srid()
13
+
10
14
 
11
15
  class Migration(migrations.Migration):
12
16
 
@@ -24,7 +28,7 @@ class Migration(migrations.Migration):
24
28
  ('created', models.DateTimeField(auto_now_add=True, null=True)),
25
29
  ('last_updated', models.DateTimeField(auto_now=True, null=True)),
26
30
  ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
27
- ('path', django.contrib.gis.db.models.fields.LineStringField(srid=3348)),
31
+ ('path', django.contrib.gis.db.models.fields.LineStringField(srid=_SRID)),
28
32
  ('provider_reference', models.CharField(blank=True, max_length=200)),
29
33
  ('comments', models.TextField(blank=True)),
30
34
  ('circuit', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='pathways_route', to='circuits.circuit')),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netbox-pathways
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: NetBox plugin for physical cable plant infrastructure documentation with GIS capabilities
5
5
  Author-email: Jonathan Senecal <contact@jonathansenecal.com>
6
6
  Project-URL: Homepage, https://github.com/jsenecal/netbox-pathways
@@ -179,6 +179,13 @@ Full documentation: **[jsenecal.github.io/netbox-pathways](https://jsenecal.gith
179
179
  - [Architecture](https://jsenecal.github.io/netbox-pathways/developer/architecture/)
180
180
  - [GeoJSON API reference](https://jsenecal.github.io/netbox-pathways/reference/geojson-api/)
181
181
 
182
+ ## Related plugins
183
+
184
+ netbox-pathways is part of a three-plugin set that models the full optical transport stack:
185
+
186
+ - **[netbox-fms](https://github.com/jsenecal/netbox-fms)** -- Fiber Management System. Defines fiber cable construction (buffer tubes, ribbons, strands), plans splices in closures, and provisions end-to-end fiber circuits. Pathways tracks *where cables run*; FMS tracks *what is inside them and how strands are spliced*.
187
+ - **[netbox-wdm](https://github.com/jsenecal/netbox-wdm)** -- WDM (Wavelength Division Multiplexing) device management. Models ITU channel plans, ROADM mappings, and wavelength services that ride on top of the fiber circuits FMS provisions, over the cables routed through Pathways.
188
+
182
189
  ## Contributing
183
190
 
184
191
  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).
@@ -1,6 +1,7 @@
1
1
  README.md
2
2
  pyproject.toml
3
3
  netbox_pathways/__init__.py
4
+ netbox_pathways/checks.py
4
5
  netbox_pathways/choices.py
5
6
  netbox_pathways/filterforms.py
6
7
  netbox_pathways/filters.py
@@ -134,4 +135,5 @@ tests/test_map_view.py
134
135
  tests/test_planned_route.py
135
136
  tests/test_registry.py
136
137
  tests/test_route_engine.py
137
- tests/test_routing.py
138
+ tests/test_routing.py
139
+ tests/test_srid_checks.py
@@ -16,7 +16,7 @@ netbox_pathways = [
16
16
 
17
17
  [project]
18
18
  name = "netbox-pathways"
19
- version = "0.2.0"
19
+ version = "0.2.1"
20
20
  description = "NetBox plugin for physical cable plant infrastructure documentation with GIS capabilities"
21
21
  readme = "README.md"
22
22
  authors = [
@@ -60,7 +60,7 @@ Tracker = "https://github.com/jsenecal/netbox-pathways/issues"
60
60
  Documentation = "https://jsenecal.github.io/netbox-pathways/"
61
61
 
62
62
  [tool.bumpversion]
63
- current_version = "0.2.0"
63
+ current_version = "0.2.1"
64
64
  commit = true
65
65
  tag = true
66
66
  tag_name = "v{new_version}"
@@ -0,0 +1,73 @@
1
+ """Tests for SRID drift system check and migration consistency."""
2
+
3
+ import importlib
4
+ from copy import deepcopy
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ from django.core.checks import Error
8
+ from django.db import connection
9
+ from django.db.utils import ProgrammingError
10
+
11
+ from netbox_pathways.checks import (
12
+ _introspect_column_srids,
13
+ check_geometry_column_srids,
14
+ )
15
+
16
+
17
+ class TestMigrationSrid:
18
+ """Migrations must derive SRID from get_srid(), never from a literal."""
19
+
20
+ def test_circuit_geometry_path_srid_follows_config(self, settings):
21
+ settings.PLUGINS_CONFIG = deepcopy(settings.PLUGINS_CONFIG)
22
+ settings.PLUGINS_CONFIG.setdefault("netbox_pathways", {})["srid"] = 3857
23
+
24
+ mod = importlib.import_module("netbox_pathways.migrations.0004_circuit_geometry")
25
+ importlib.reload(mod)
26
+
27
+ operation = mod.Migration.operations[0]
28
+ fields_dict = dict(operation.fields)
29
+ assert fields_dict["path"].srid == 3857
30
+
31
+
32
+ class TestIntrospectColumnSrids:
33
+ def test_returns_empty_for_non_postgres(self):
34
+ fake_conn = MagicMock()
35
+ fake_conn.vendor = "sqlite"
36
+ assert _introspect_column_srids(fake_conn, "netbox_pathways") == {}
37
+
38
+ def test_returns_empty_on_db_error(self):
39
+ fake_conn = MagicMock()
40
+ fake_conn.vendor = "postgresql"
41
+ cursor = fake_conn.cursor.return_value.__enter__.return_value
42
+ cursor.execute.side_effect = ProgrammingError("relation does not exist")
43
+ assert _introspect_column_srids(fake_conn, "netbox_pathways") == {}
44
+
45
+ def test_introspects_real_columns(self, db):
46
+ result = _introspect_column_srids(connection, "netbox_pathways")
47
+ assert ("netbox_pathways_structure", "location") in result
48
+
49
+
50
+ class TestCheckGeometryColumnSrids:
51
+ def test_silent_when_srid_unconfigured(self):
52
+ from django.core.exceptions import ImproperlyConfigured
53
+
54
+ with patch("netbox_pathways.checks.get_srid", side_effect=ImproperlyConfigured()):
55
+ assert check_geometry_column_srids() == []
56
+
57
+ def test_silent_when_no_columns_introspected(self):
58
+ with patch("netbox_pathways.checks._introspect_column_srids", return_value={}):
59
+ assert check_geometry_column_srids() == []
60
+
61
+ def test_no_errors_when_columns_match_config(self, db):
62
+ assert check_geometry_column_srids() == []
63
+
64
+ def test_reports_error_on_mismatch(self, db):
65
+ with patch("netbox_pathways.checks.get_srid", return_value=999):
66
+ errors = check_geometry_column_srids()
67
+ assert errors, "expected at least one mismatch error"
68
+ assert all(isinstance(e, Error) for e in errors)
69
+ assert all(e.id == "netbox_pathways.E001" for e in errors)
70
+ first = errors[0]
71
+ assert "999" in first.msg
72
+ assert "PLUGINS_CONFIG" in first.hint
73
+ assert "ST_Transform" in first.hint