netbox-pathways 0.2.0__tar.gz → 0.2.2__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 (142) hide show
  1. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/PKG-INFO +16 -4
  2. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/README.md +15 -3
  3. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/__init__.py +6 -2
  4. netbox_pathways-0.2.2/netbox_pathways/checks.py +86 -0
  5. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/forms.py +8 -0
  6. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0004_circuit_geometry.py +5 -1
  7. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways.egg-info/PKG-INFO +16 -4
  8. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways.egg-info/SOURCES.txt +4 -1
  9. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/pyproject.toml +2 -2
  10. netbox_pathways-0.2.2/tests/test_map_widget.py +35 -0
  11. netbox_pathways-0.2.2/tests/test_srid_checks.py +73 -0
  12. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/api/__init__.py +0 -0
  13. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/api/external_geo.py +0 -0
  14. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/api/geo.py +0 -0
  15. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/api/serializers.py +0 -0
  16. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/api/traversal.py +0 -0
  17. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/api/urls.py +0 -0
  18. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/api/views.py +0 -0
  19. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/choices.py +0 -0
  20. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/filterforms.py +0 -0
  21. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/filters.py +0 -0
  22. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/geo.py +0 -0
  23. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/graph.py +0 -0
  24. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/graphql/__init__.py +0 -0
  25. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/graphql/filters.py +0 -0
  26. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/graphql/schema.py +0 -0
  27. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/graphql/types.py +0 -0
  28. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/management/__init__.py +0 -0
  29. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/management/commands/__init__.py +0 -0
  30. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/management/commands/_geodata_worker.py +0 -0
  31. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/management/commands/generate_qgis_project.py +0 -0
  32. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/management/commands/generate_sample_data.py +0 -0
  33. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/management/commands/import_geodata.py +0 -0
  34. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0001_initial.py +0 -0
  35. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0002_replace_owner_with_tenant.py +0 -0
  36. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0003_structure_optional_site_dimensions.py +0 -0
  37. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0005_replace_unique_together_with_constraints.py +0 -0
  38. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0006_remove_cablesegment_sequence_enter_exit.py +0 -0
  39. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0007_cable_routing_redesign.py +0 -0
  40. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0008_conduitbank_pathway_subclass.py +0 -0
  41. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0009_remove_conduit_unique_position_per_bank_and_more.py +0 -0
  42. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0010_structure_status.py +0 -0
  43. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0011_rename_name_to_label.py +0 -0
  44. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0012_add_filter_field_indexes.py +0 -0
  45. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0013_plannedroute.py +0 -0
  46. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0014_plannedroute_parent_split.py +0 -0
  47. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0015_installer_and_commissioned_date.py +0 -0
  48. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/0016_cablesegment_lashed_with.py +0 -0
  49. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/migrations/__init__.py +0 -0
  50. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/models.py +0 -0
  51. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/navigation.py +0 -0
  52. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/registry.py +0 -0
  53. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/route_engine.py +0 -0
  54. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/routing.py +0 -0
  55. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/search.py +0 -0
  56. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/signals.py +0 -0
  57. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/css/leaflet-theme.css +0 -0
  58. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/css/pathways-map.css +0 -0
  59. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/detail-map.min.js +0 -0
  60. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/detail-map.min.js.map +0 -0
  61. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/endpoint-markers.min.js +0 -0
  62. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/endpoint-markers.min.js.map +0 -0
  63. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/pathways-field.min.js +0 -0
  64. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/pathways-field.min.js.map +0 -0
  65. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/pathways-map.min.js +0 -0
  66. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/pathways-map.min.js.map +0 -0
  67. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/route-planner-map.min.js +0 -0
  68. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/dist/route-planner-map.min.js.map +0 -0
  69. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/qgis/pathways.qml +0 -0
  70. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/qgis/structures.qml +0 -0
  71. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/MarkerCluster.Default.css +0 -0
  72. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/MarkerCluster.css +0 -0
  73. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/geoman/leaflet-geoman.css +0 -0
  74. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/geoman/leaflet-geoman.js +0 -0
  75. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/layers-2x.png +0 -0
  76. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/layers.png +0 -0
  77. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/marker-icon-2x.png +0 -0
  78. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/marker-icon.png +0 -0
  79. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/leaflet/images/marker-shadow.png +0 -0
  80. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/leaflet/leaflet.css +0 -0
  81. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/leaflet/leaflet.js +0 -0
  82. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/static/netbox_pathways/vendor/leaflet.markercluster.js +0 -0
  83. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/tables.py +0 -0
  84. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/template_content.py +0 -0
  85. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/aerialspan.html +0 -0
  86. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/buttons/apply_route.html +0 -0
  87. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/buttons/replan_route.html +0 -0
  88. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/buttons/revert_split.html +0 -0
  89. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/buttons/split_route.html +0 -0
  90. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/buttons/view_in_map.html +0 -0
  91. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/cable_route_tab.html +0 -0
  92. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/cablesegment.html +0 -0
  93. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/conduit.html +0 -0
  94. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/conduitbank.html +0 -0
  95. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/conduitjunction.html +0 -0
  96. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/directburied.html +0 -0
  97. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/inc/cable_add_segment_form.html +0 -0
  98. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/inc/cable_route_finder_results.html +0 -0
  99. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/inc/cable_routing_panel.html +0 -0
  100. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/inc/cable_segment_table.html +0 -0
  101. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/inc/connected_structures_panel.html +0 -0
  102. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/inc/constraint_card.html +0 -0
  103. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/inc/geo_map_panel.html +0 -0
  104. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/inc/plannedroute_map_panel.html +0 -0
  105. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/inc/planner_results.html +0 -0
  106. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/innerduct.html +0 -0
  107. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/map.html +0 -0
  108. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/pathway.html +0 -0
  109. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/pathwaylocation.html +0 -0
  110. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/plannedroute.html +0 -0
  111. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/plannedroute_apply.html +0 -0
  112. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/plannedroute_split.html +0 -0
  113. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/pullsheet_detail.html +0 -0
  114. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/pullsheet_list.html +0 -0
  115. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/route_planner.html +0 -0
  116. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/sitegeometry.html +0 -0
  117. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/structure.html +0 -0
  118. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/templates/netbox_pathways/widgets/map_widget.html +0 -0
  119. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/ui/__init__.py +0 -0
  120. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/ui/panels.py +0 -0
  121. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/urls.py +0 -0
  122. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways/views.py +0 -0
  123. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways.egg-info/dependency_links.txt +0 -0
  124. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways.egg-info/requires.txt +0 -0
  125. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/netbox_pathways.egg-info/top_level.txt +0 -0
  126. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/setup.cfg +0 -0
  127. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_adjacency.py +0 -0
  128. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_cable_segment.py +0 -0
  129. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_circuit_geometry.py +0 -0
  130. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_endpoint_validation.py +0 -0
  131. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_external_geo.py +0 -0
  132. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_field_additions.py +0 -0
  133. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_geo_api.py +0 -0
  134. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_graph.py +0 -0
  135. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_graphql.py +0 -0
  136. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_import_geodata.py +0 -0
  137. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_lashed_with.py +0 -0
  138. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_map_view.py +0 -0
  139. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_planned_route.py +0 -0
  140. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_registry.py +0 -0
  141. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/tests/test_route_engine.py +0 -0
  142. {netbox_pathways-0.2.0 → netbox_pathways-0.2.2}/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.2
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
@@ -58,9 +58,14 @@ Requires-Dist: zensical; extra == "docs"
58
58
 
59
59
  ## Compatibility
60
60
 
61
- | Plugin version | NetBox version | Python | PostgreSQL |
62
- |----------------|----------------|-----------|---------------------|
63
- | 0.1.x | 4.5.3+ | 3.12-3.14 | 16+ with PostGIS 3.4|
61
+ | Plugin version | NetBox version | Python | PostgreSQL |
62
+ |-----------------|----------------------|-----------|----------------------|
63
+ | 0.1.x -- 0.2.1 | 4.5.3 -- 4.5.x | 3.12-3.14 | 16+ with PostGIS 3.4+|
64
+ | 0.2.2+ | 4.5.3+ (incl. 4.6.x) | 3.12-3.14 | 16+ with PostGIS 3.4+|
65
+
66
+ > NetBox 4.6 ships Django 6.0. Plugin versions 0.2.1 and earlier do not render
67
+ > the geometry map widget on NetBox 4.6 (issue #52); upgrade to 0.2.2 or later
68
+ > for NetBox 4.6 support.
64
69
 
65
70
  ## Installation
66
71
 
@@ -179,6 +184,13 @@ Full documentation: **[jsenecal.github.io/netbox-pathways](https://jsenecal.gith
179
184
  - [Architecture](https://jsenecal.github.io/netbox-pathways/developer/architecture/)
180
185
  - [GeoJSON API reference](https://jsenecal.github.io/netbox-pathways/reference/geojson-api/)
181
186
 
187
+ ## Related plugins
188
+
189
+ netbox-pathways is part of a three-plugin set that models the full optical transport stack:
190
+
191
+ - **[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*.
192
+ - **[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.
193
+
182
194
  ## Contributing
183
195
 
184
196
  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).
@@ -28,9 +28,14 @@
28
28
 
29
29
  ## Compatibility
30
30
 
31
- | Plugin version | NetBox version | Python | PostgreSQL |
32
- |----------------|----------------|-----------|---------------------|
33
- | 0.1.x | 4.5.3+ | 3.12-3.14 | 16+ with PostGIS 3.4|
31
+ | Plugin version | NetBox version | Python | PostgreSQL |
32
+ |-----------------|----------------------|-----------|----------------------|
33
+ | 0.1.x -- 0.2.1 | 4.5.3 -- 4.5.x | 3.12-3.14 | 16+ with PostGIS 3.4+|
34
+ | 0.2.2+ | 4.5.3+ (incl. 4.6.x) | 3.12-3.14 | 16+ with PostGIS 3.4+|
35
+
36
+ > NetBox 4.6 ships Django 6.0. Plugin versions 0.2.1 and earlier do not render
37
+ > the geometry map widget on NetBox 4.6 (issue #52); upgrade to 0.2.2 or later
38
+ > for NetBox 4.6 support.
34
39
 
35
40
  ## Installation
36
41
 
@@ -149,6 +154,13 @@ Full documentation: **[jsenecal.github.io/netbox-pathways](https://jsenecal.gith
149
154
  - [Architecture](https://jsenecal.github.io/netbox-pathways/developer/architecture/)
150
155
  - [GeoJSON API reference](https://jsenecal.github.io/netbox-pathways/reference/geojson-api/)
151
156
 
157
+ ## Related plugins
158
+
159
+ netbox-pathways is part of a three-plugin set that models the full optical transport stack:
160
+
161
+ - **[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*.
162
+ - **[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.
163
+
152
164
  ## Contributing
153
165
 
154
166
  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.2"
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
@@ -73,6 +73,14 @@ class PathwaysMapWidget(BaseGeometryWidget):
73
73
 
74
74
  def get_context(self, name, value, attrs):
75
75
  context = super().get_context(name, value, attrs)
76
+ # Django 6.0 stopped exposing id/name/geom_type at the top level of the
77
+ # widget context (they now live under ``widget``); our template reads
78
+ # them at the top level, so re-expose them here. setdefault keeps this
79
+ # backwards compatible with Django <= 5.2, which still sets them. (#52)
80
+ widget = context["widget"]
81
+ context.setdefault("id", widget["attrs"].get("id", ""))
82
+ context.setdefault("name", widget["name"])
83
+ context.setdefault("geom_type", widget["attrs"].get("geom_name", self.geom_type))
76
84
  if self.endpoint_geojson:
77
85
  context["endpoint_json"] = mark_safe(json.dumps(self.endpoint_geojson)) # noqa: S308
78
86
  return context
@@ -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.2
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
@@ -58,9 +58,14 @@ Requires-Dist: zensical; extra == "docs"
58
58
 
59
59
  ## Compatibility
60
60
 
61
- | Plugin version | NetBox version | Python | PostgreSQL |
62
- |----------------|----------------|-----------|---------------------|
63
- | 0.1.x | 4.5.3+ | 3.12-3.14 | 16+ with PostGIS 3.4|
61
+ | Plugin version | NetBox version | Python | PostgreSQL |
62
+ |-----------------|----------------------|-----------|----------------------|
63
+ | 0.1.x -- 0.2.1 | 4.5.3 -- 4.5.x | 3.12-3.14 | 16+ with PostGIS 3.4+|
64
+ | 0.2.2+ | 4.5.3+ (incl. 4.6.x) | 3.12-3.14 | 16+ with PostGIS 3.4+|
65
+
66
+ > NetBox 4.6 ships Django 6.0. Plugin versions 0.2.1 and earlier do not render
67
+ > the geometry map widget on NetBox 4.6 (issue #52); upgrade to 0.2.2 or later
68
+ > for NetBox 4.6 support.
64
69
 
65
70
  ## Installation
66
71
 
@@ -179,6 +184,13 @@ Full documentation: **[jsenecal.github.io/netbox-pathways](https://jsenecal.gith
179
184
  - [Architecture](https://jsenecal.github.io/netbox-pathways/developer/architecture/)
180
185
  - [GeoJSON API reference](https://jsenecal.github.io/netbox-pathways/reference/geojson-api/)
181
186
 
187
+ ## Related plugins
188
+
189
+ netbox-pathways is part of a three-plugin set that models the full optical transport stack:
190
+
191
+ - **[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*.
192
+ - **[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.
193
+
182
194
  ## Contributing
183
195
 
184
196
  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
@@ -131,7 +132,9 @@ tests/test_graphql.py
131
132
  tests/test_import_geodata.py
132
133
  tests/test_lashed_with.py
133
134
  tests/test_map_view.py
135
+ tests/test_map_widget.py
134
136
  tests/test_planned_route.py
135
137
  tests/test_registry.py
136
138
  tests/test_route_engine.py
137
- tests/test_routing.py
139
+ tests/test_routing.py
140
+ 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.2"
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.2"
64
64
  commit = true
65
65
  tag = true
66
66
  tag_name = "v{new_version}"
@@ -0,0 +1,35 @@
1
+ """Regression tests for PathwaysMapWidget rendering.
2
+
3
+ Django 6.0 changed BaseGeometryWidget.get_context so it no longer exposes the
4
+ top-level ``id``, ``name`` and ``geom_type`` context variables that the widget
5
+ template relies on (they moved under ``widget``). Without compensation the
6
+ hidden input renders with an empty ``name`` (so no geometry is submitted) and
7
+ the map container renders with an empty ``data-field-id`` (so the JS bails and
8
+ no map appears). See issue #52.
9
+ """
10
+
11
+ from netbox_pathways.forms import PathwaysMapWidget
12
+
13
+
14
+ def _render(widget):
15
+ return widget.render("location", None, attrs={"id": "id_location"})
16
+
17
+
18
+ def test_hidden_input_keeps_field_name():
19
+ """The hidden geometry input must carry name="location" so the form submits a value."""
20
+ html = _render(PathwaysMapWidget(geom_type="Geometry"))
21
+ assert 'name="location"' in html
22
+ assert 'name=""' not in html
23
+
24
+
25
+ def test_map_container_has_field_id():
26
+ """The map container needs a non-empty data-field-id for the JS to initialize."""
27
+ html = _render(PathwaysMapWidget(geom_type="Geometry"))
28
+ assert 'data-field-id="id_location"' in html
29
+ assert 'data-field-id=""' not in html
30
+
31
+
32
+ def test_geom_type_is_exposed():
33
+ """The configured geometry type must reach the template's data-geom-type."""
34
+ assert 'data-geom-type="Geometry"' in _render(PathwaysMapWidget(geom_type="Geometry"))
35
+ assert 'data-geom-type="LineString"' in _render(PathwaysMapWidget(geom_type="LineString"))
@@ -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