netbox-data-import 2.7.0__tar.gz → 2.9.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 (277) hide show
  1. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/PKG-INFO +2 -1
  2. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/__init__.py +4 -3
  3. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/adapters.py +1 -1
  4. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/cable_disclosure.py +207 -133
  5. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/cable_target.py +332 -196
  6. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/catalog.py +12 -0
  7. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/contact_resolution.py +5 -4
  8. netbox_data_import-2.9.0/netbox_data_import/device_identity.py +106 -0
  9. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/field_keys.py +23 -2
  10. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/forms.py +8 -0
  11. netbox_data_import-2.9.0/netbox_data_import/identity.py +100 -0
  12. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/import_engine.py +24 -6
  13. netbox_data_import-2.9.0/netbox_data_import/migrations/0042_tracelocationresolution.py +41 -0
  14. netbox_data_import-2.9.0/netbox_data_import/migrations/0043_decision_source_spelling.py +37 -0
  15. netbox_data_import-2.9.0/netbox_data_import/migrations/0044_cableimportsource_segment_index_unknown.py +17 -0
  16. netbox_data_import-2.9.0/netbox_data_import/migrations/0045_rekey_name_identities.py +357 -0
  17. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/models.py +97 -3
  18. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/netbox_reader.py +29 -22
  19. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/object_permissions.py +4 -0
  20. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/plan.py +33 -4
  21. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/profile_yaml.py +40 -14
  22. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/proposal_decisions.py +10 -12
  23. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/proposal_presentation.py +17 -6
  24. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/proposal_tasks.py +2 -2
  25. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/review_workspace.py +203 -37
  26. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/source_trace.py +1 -1
  27. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/static/netbox_data_import/js/split_name_modal.js +11 -5
  28. netbox_data_import-2.9.0/netbox_data_import/static/netbox_data_import/js/trace_picker.js +328 -0
  29. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/target_modules.py +38 -53
  30. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/import_preview.html +1 -0
  31. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/trace_workspace.html +135 -16
  32. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/termination_proposal.py +11 -12
  33. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/helpers.py +35 -0
  34. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_adapter_config_migration.py +4 -8
  35. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_atomic_exits.py +8 -0
  36. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_branching_refusal.py +6 -1
  37. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_cable_module.py +740 -14
  38. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_configuration_surfaces.py +35 -0
  39. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_contact_sync.py +8 -0
  40. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_cutover_edge_contracts.py +16 -9
  41. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_device_module.py +14 -2
  42. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_digest_indexed_models.py +5 -2
  43. netbox_data_import-2.9.0/netbox_data_import/tests/test_exception_text_exposure.py +477 -0
  44. netbox_data_import-2.9.0/netbox_data_import/tests/test_identity.py +233 -0
  45. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_import_cutover.py +66 -6
  46. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_import_engine.py +3 -1
  47. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_import_plan.py +41 -0
  48. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_installation_docs.py +16 -0
  49. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_job_plan_cleanup_migration.py +3 -5
  50. netbox_data_import-2.9.0/netbox_data_import/tests/test_mapping_identity_preview.py +116 -0
  51. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_migrations.py +6 -34
  52. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_module_boundaries.py +242 -0
  53. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_netbox_reader.py +78 -0
  54. netbox_data_import-2.9.0/netbox_data_import/tests/test_policy_fingerprint_refusal.py +242 -0
  55. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_proposal_decisions.py +26 -3
  56. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_proposal_tasks.py +79 -5
  57. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_proposal_workspace.py +227 -6
  58. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_rack_module.py +1 -1
  59. netbox_data_import-2.9.0/netbox_data_import/tests/test_rekey_name_identities_migration.py +548 -0
  60. netbox_data_import-2.9.0/netbox_data_import/tests/test_split_replacement_acknowledgement.py +179 -0
  61. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_tags_migration.py +5 -5
  62. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_target_field_catalog.py +3 -2
  63. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_trace_decisions.py +68 -4
  64. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_trace_device_resolution.py +237 -63
  65. netbox_data_import-2.9.0/netbox_data_import/tests/test_trace_location_resolution.py +1283 -0
  66. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_trace_workspace.py +822 -35
  67. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_views.py +191 -38
  68. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/trace_device_resolution.py +202 -191
  69. netbox_data_import-2.9.0/netbox_data_import/trace_location_resolution.py +219 -0
  70. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/trace_workbook.py +2 -1
  71. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/urls.py +10 -0
  72. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/values.py +0 -6
  73. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/views.py +539 -197
  74. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import.egg-info/PKG-INFO +2 -1
  75. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import.egg-info/SOURCES.txt +14 -3
  76. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/pyproject.toml +2 -1
  77. netbox_data_import-2.7.0/netbox_data_import/device_identity.py +0 -77
  78. netbox_data_import-2.7.0/netbox_data_import/static/netbox_data_import/js/trace_device_picker.js +0 -130
  79. netbox_data_import-2.7.0/netbox_data_import/static/netbox_data_import/js/trace_picker_search.js +0 -63
  80. netbox_data_import-2.7.0/netbox_data_import/static/netbox_data_import/js/trace_termination_picker.js +0 -126
  81. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/LICENSE +0 -0
  82. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/README.md +0 -0
  83. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/_dns_worker.py +0 -0
  84. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/adapter_config.py +0 -0
  85. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/adapter_forms.py +0 -0
  86. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/api/__init__.py +0 -0
  87. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/api/serializers.py +0 -0
  88. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/api/urls.py +0 -0
  89. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/api/views.py +0 -0
  90. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/branching.py +0 -0
  91. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/cable_policy.py +0 -0
  92. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/device_field_review.py +0 -0
  93. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/filters.py +0 -0
  94. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/flat_workbook.py +0 -0
  95. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/graphql/__init__.py +0 -0
  96. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/graphql/filters.py +0 -0
  97. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/graphql/schema.py +0 -0
  98. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/graphql/types.py +0 -0
  99. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/inference_adapter.py +0 -0
  100. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/inference_backend.py +0 -0
  101. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/inference_connection_test.py +0 -0
  102. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/inference_credentials.py +0 -0
  103. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/inference_settings.py +0 -0
  104. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/inference_transport.py +0 -0
  105. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/inference_trust.py +0 -0
  106. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/ip_assignment.py +0 -0
  107. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/jobs.py +0 -0
  108. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0001_initial.py +0 -0
  109. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0002_importjob.py +0 -0
  110. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0003_remove_classrolemapping_unique_class_role_mapping_per_profile_and_more.py +0 -0
  111. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0004_features.py +0 -0
  112. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0005_deviceexistingmatch.py +0 -0
  113. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0006_deviceexistingmatch_asset_tag.py +0 -0
  114. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0007_manufacturermapping.py +0 -0
  115. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0008_unique_constraints.py +0 -0
  116. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0009_add_rack_type_to_classrolemapping.py +0 -0
  117. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0010_alter_manufacturermapping_id.py +0 -0
  118. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0011_importprofile_capture_extra_data.py +0 -0
  119. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0012_classrolemapping_rack_type_related_name.py +0 -0
  120. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0013_remove_columnmapping_unique_target.py +0 -0
  121. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0014_alter_columnmapping_target_field_and_more.py +0 -0
  122. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0015_cleanup_duplicate_device_matches.py +0 -0
  123. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0016_deviceexistingmatch_ndi_devicematch_profile_device.py +0 -0
  124. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0017_native_contact_sync.py +0 -0
  125. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0018_ignoredfielddifference.py +0 -0
  126. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0019_deviceimportsource.py +0 -0
  127. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0020_migrate_import_source_custom_field.py +0 -0
  128. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0021_importprofile_adapter_config.py +0 -0
  129. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0022_migrate_profile_adapter_config.py +0 -0
  130. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0023_drop_moved_profile_columns.py +0 -0
  131. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0024_rename_importjob_importexecution.py +0 -0
  132. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0025_remove_importexecution_dry_run_and_more.py +0 -0
  133. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0026_alter_sourcedocument_profile.py +0 -0
  134. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0027_cableclassmapping_terminationresolution.py +0 -0
  135. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0028_cableimportsource.py +0 -0
  136. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0029_alter_cableimportsource_from_text_and_more.py +0 -0
  137. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0030_remove_device_type_creation_config.py +0 -0
  138. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0031_inferencebackend.py +0 -0
  139. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0032_inference_timeout_floor.py +0 -0
  140. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0033_resolutionproposal.py +0 -0
  141. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0034_tracedeviceresolution.py +0 -0
  142. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0035_retire_superseded_proposals.py +0 -0
  143. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0036_resolutionproposal_job.py +0 -0
  144. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0037_cablesegmentoverride.py +0 -0
  145. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0038_cable_tag_integrity.py +0 -0
  146. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0039_remove_job_plan_copies.py +0 -0
  147. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0040_move_tags_to_tagged_items.py +0 -0
  148. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/0041_remove_importprofile_tags_and_more.py +0 -0
  149. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/migrations/__init__.py +0 -0
  150. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/navigation.py +0 -0
  151. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/preview_row_actions.py +0 -0
  152. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/proposal_contract.py +0 -0
  153. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/proposal_jobs.py +0 -0
  154. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/proposal_response.py +0 -0
  155. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/resolution_proposals.py +0 -0
  156. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/signals.py +0 -0
  157. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/source_resolution.py +0 -0
  158. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/static/netbox_data_import/js/conflict_modal.js +0 -0
  159. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/static/netbox_data_import/js/contact_candidate_modal.js +0 -0
  160. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/static/netbox_data_import/js/preview_row_actions.js +0 -0
  161. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/static/netbox_data_import/js/preview_row_controls.js +0 -0
  162. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/static/netbox_data_import/js/sync_row_modal.js +0 -0
  163. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/static/netbox_data_import/js/trace_proposals.js +0 -0
  164. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tables.py +0 -0
  165. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/target_runtime.py +0 -0
  166. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/template_content.py +0 -0
  167. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/_import_progress.html +0 -0
  168. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/_proposal_action.html +0 -0
  169. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/_proposal_card.html +0 -0
  170. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/analysis.html +0 -0
  171. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/branch_refused.html +0 -0
  172. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/branch_refused_anonymous.html +0 -0
  173. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/bulk_yaml_import.html +0 -0
  174. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/cableclassmapping_edit.html +0 -0
  175. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/classrolemapping_edit.html +0 -0
  176. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/columnmapping_edit.html +0 -0
  177. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/columntransformrule_edit.html +0 -0
  178. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/device_import_data.html +0 -0
  179. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/device_import_data_main_only.html +0 -0
  180. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/devicetypemapping_edit.html +0 -0
  181. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/import_profile_yaml.html +0 -0
  182. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/import_progress.html +0 -0
  183. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/import_results.html +0 -0
  184. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/import_setup.html +0 -0
  185. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/importexecution_list.html +0 -0
  186. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/importprofile.html +0 -0
  187. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/importprofile_edit.html +0 -0
  188. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/importprofile_list.html +0 -0
  189. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/inc/branch_refusal_card.html +0 -0
  190. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/inferencebackend.html +0 -0
  191. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/templates/netbox_data_import/source_resolution_list.html +0 -0
  192. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/__init__.py +0 -0
  193. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/conftest.py +0 -0
  194. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/fixtures/combined_name.xlsx +0 -0
  195. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/fixtures/sample_workbook.xlsx +0 -0
  196. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/fixtures/trace_copper.xlsx +0 -0
  197. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/fixtures/trace_fiber.xlsx +0 -0
  198. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/inference_http.py +0 -0
  199. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/isolated_settings.py +0 -0
  200. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/mixins.py +0 -0
  201. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/mock_discipline.py +0 -0
  202. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/netbox_configuration.py +0 -0
  203. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/parallel.py +0 -0
  204. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/plugins_config.py +0 -0
  205. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/redaction.py +0 -0
  206. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/snapshot_guard.py +0 -0
  207. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_adapter_config.py +0 -0
  208. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_api.py +0 -0
  209. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_branching.py +0 -0
  210. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_branching_absent.py +0 -0
  211. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_ci_configuration.py +0 -0
  212. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_comment_style.py +0 -0
  213. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_configuration_docs.py +0 -0
  214. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_conflict_row_actions.py +0 -0
  215. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_contact_resolution_ajax.py +0 -0
  216. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_contact_role_suggestion.py +0 -0
  217. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_devcontainer_e2e.py +0 -0
  218. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_execution_audit.py +0 -0
  219. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_graphql_filters.py +0 -0
  220. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_helpers.py +0 -0
  221. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_import_change_log.py +0 -0
  222. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_import_execution.py +0 -0
  223. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_import_source_migration.py +0 -0
  224. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_inference_adapter.py +0 -0
  225. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_inference_backend.py +0 -0
  226. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_inference_connection_test.py +0 -0
  227. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_inference_credentials.py +0 -0
  228. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_inference_secret_containment.py +0 -0
  229. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_inference_settings.py +0 -0
  230. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_inference_trust.py +0 -0
  231. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_ip_target.py +0 -0
  232. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_markdown_tables.py +0 -0
  233. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_misc_coverage.py +0 -0
  234. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_mock_discipline.py +0 -0
  235. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_model_base_conventions.py +0 -0
  236. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_models.py +0 -0
  237. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_navigation.py +0 -0
  238. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_object_permission_writer.py +0 -0
  239. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_parallel_test_setup.py +0 -0
  240. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_placement_reference_locks.py +0 -0
  241. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_plugins_config.py +0 -0
  242. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_policy_write_refusals.py +0 -0
  243. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_preview_assets.py +0 -0
  244. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_preview_state_ownership.py +0 -0
  245. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_pristine_preview_rows.py +0 -0
  246. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_profile_policy_crud_scoping.py +0 -0
  247. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_proposal_jobs.py +0 -0
  248. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_proposal_response.py +0 -0
  249. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_quick_action_input_bounds.py +0 -0
  250. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_quick_action_object_permissions.py +0 -0
  251. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_quick_action_profile_id.py +0 -0
  252. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_release_metadata.py +0 -0
  253. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_remove_device_type_creation_config_migration.py +0 -0
  254. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_resolution_proposal_api.py +0 -0
  255. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_resolution_proposals.py +0 -0
  256. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_retire_superseded_proposals_migration.py +0 -0
  257. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_review_actions.py +0 -0
  258. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_review_resolutions.py +0 -0
  259. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_review_workspace.py +0 -0
  260. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_serial_display.py +0 -0
  261. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_snapshot_guard.py +0 -0
  262. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_source_adapter.py +0 -0
  263. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_source_trace_contract.py +0 -0
  264. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_standard_views.py +0 -0
  265. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_tag_assignments.py +0 -0
  266. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_target_module_edge_contracts.py +0 -0
  267. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_text_encoding.py +0 -0
  268. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_trace_workbook.py +0 -0
  269. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_views_coverage2.py +0 -0
  270. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/tests/test_views_extra.py +0 -0
  271. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/trace_schema.py +0 -0
  272. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/transform_regex.py +0 -0
  273. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import/utils.py +0 -0
  274. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import.egg-info/dependency_links.txt +0 -0
  275. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import.egg-info/requires.txt +0 -0
  276. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/netbox_data_import.egg-info/top_level.txt +0 -0
  277. {netbox_data_import-2.7.0 → netbox_data_import-2.9.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netbox-data-import
3
- Version: 2.7.0
3
+ Version: 2.9.0
4
4
  Summary: NetBox plugin for importing data from external DCIM systems
5
5
  Author: Marcin Zieba
6
6
  License-Expression: Apache-2.0
@@ -10,6 +10,7 @@ Project-URL: Changelog, https://github.com/marcinpsk/netbox-data-import-plugin/b
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
13
14
  Classifier: Framework :: Django
14
15
  Requires-Python: >=3.12.0
15
16
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ from typing import Any
4
4
 
5
5
  from netbox.plugins import PluginConfig
6
6
 
7
- __version__ = "2.7.0"
7
+ __version__ = "2.9.0"
8
8
 
9
9
 
10
10
  class NetBoxDataImportConfig(PluginConfig):
@@ -41,11 +41,12 @@ class NetBoxDataImportConfig(PluginConfig):
41
41
  raise ImproperlyConfigured(f"Plugin {cls.__module__} has an invalid configuration: {exc}") from exc
42
42
 
43
43
  def ready(self):
44
- """Import the modules NetBox does not load, and register with netbox-branching when it is installed."""
44
+ """Import the modules NetBox does not load, and register the Cable display vocabulary and netbox-branching."""
45
45
  super().ready()
46
46
 
47
- from . import branching, jobs, termination_proposal
47
+ from . import branching, cable_disclosure, jobs, termination_proposal
48
48
 
49
+ cable_disclosure.register()
49
50
  branching.register()
50
51
 
51
52
 
@@ -17,7 +17,7 @@ from dataclasses import dataclass, field, replace
17
17
  from typing import TYPE_CHECKING
18
18
 
19
19
  from .catalog import OutputKind, has_implemented_module
20
- from .values import identity_text
20
+ from .identity import identity_text
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from .source_trace import SourceTrace
@@ -4,99 +4,86 @@
4
4
 
5
5
  from dataclasses import replace
6
6
  from types import MappingProxyType
7
+ from typing import NamedTuple
8
+
9
+ from .catalog import TargetModuleKey
10
+ from .field_keys import CABLE_END_KINDS
11
+ from .plan import ImportPlan, register_diagnostic_display
7
12
 
8
13
  CABLE_ROW = "dcim.cable"
9
14
  CABLE_CLASS_MAPPING_ROW = "netbox_data_import.cableclassmapping"
10
15
  CABLE_SEGMENT_OVERRIDE_ROW = "netbox_data_import.cablesegmentoverride"
16
+ DEVICE_HIDDEN = "a Device you cannot view"
17
+ DEVICE_ROW = "dcim.device"
11
18
  DISCLOSURE_SOURCE = "disclosure_source"
12
19
  POLICY_HIDDEN = "a policy you cannot view"
13
20
  POLICY_VISIBLE = "policy_visible"
14
- POLICY_WRITE_REFUSED = "You cannot change a policy you cannot view."
21
+ TERMINATION_HIDDEN = "a termination you cannot view"
22
+ TERMINATION_SOURCES = "termination_sources"
23
+
24
+ # A segment names its two planned ends apart, so one hidden end leaves the other readable.
25
+ SEGMENT_END_SOURCES = MappingProxyType({"left": "left_sources", "right": "right_sources"})
15
26
 
16
27
  _REFERENCE_FIELDS = frozenset({"device", "cards", "port", "port_class"})
28
+ # These codes replace the source `port` with the NetBox port they resolved.
29
+ _RESOLVED_PORT_FIELDS = _REFERENCE_FIELDS - {"port"}
30
+
31
+
32
+ class _DisplaySchema(NamedTuple):
33
+ """The display fields one diagnostic may carry, grouped by the row that authorizes them."""
34
+
35
+ public: frozenset
36
+ cable: frozenset = frozenset()
37
+ policy: frozenset = frozenset()
38
+ termination: frozenset = frozenset()
39
+
17
40
 
18
41
  _DIAGNOSTIC_DISCLOSURE_FIELDS = MappingProxyType(
19
42
  {
20
- "cable.ambiguous_mapped_peer": (_REFERENCE_FIELDS | {"peers"}, frozenset(), frozenset()),
21
- "cable.attribute_drift": (
22
- frozenset({"segment_index"}),
23
- frozenset({"cable", "status", "type", "profile", "label"}),
24
- frozenset(),
25
- ),
26
- "cable.cableclass_unmapped": (frozenset({"segment_index", "cable_class"}), frozenset(), frozenset()),
27
- "cable.media_family_mismatch": (
28
- frozenset(),
29
- frozenset({"segments"}),
30
- frozenset({"segments"}),
43
+ "cable.ambiguous_mapped_peer": _DisplaySchema(_RESOLVED_PORT_FIELDS, termination=frozenset({"port", "peers"})),
44
+ "cable.attribute_drift": _DisplaySchema(
45
+ frozenset({"segment_index"}), cable=frozenset({"cable", "status", "type", "profile", "label"})
31
46
  ),
32
- "cable.multi_termination_conflict": (
33
- frozenset({"segment_index", "port"}),
34
- frozenset({"cable"}),
35
- frozenset(),
47
+ "cable.cableclass_unmapped": _DisplaySchema(frozenset({"segment_index", "cable_class"})),
48
+ "cable.incompatible_terminations": _DisplaySchema(
49
+ frozenset({"segment_index", "left_field_key", "right_field_key"}),
50
+ termination=frozenset({"left_model", "right_model"}),
36
51
  ),
37
- "cable.pass_through_not_mapped": (
38
- _REFERENCE_FIELDS | {"entry", "exit", "mapped"},
39
- frozenset(),
40
- frozenset(),
52
+ "cable.media_family_mismatch": _DisplaySchema(
53
+ frozenset(), cable=frozenset({"segments"}), policy=frozenset({"segments"})
41
54
  ),
42
- "cable.pass_through_verified": (
43
- _REFERENCE_FIELDS | {"entry", "exit"},
44
- frozenset(),
45
- frozenset(),
55
+ "cable.multi_termination_conflict": _DisplaySchema(
56
+ frozenset({"segment_index"}), cable=frozenset({"cable"}), termination=frozenset({"port"})
46
57
  ),
47
- "cable.permission_denied": (
48
- _REFERENCE_FIELDS | {"permission"},
49
- frozenset({"cable"}),
50
- frozenset(),
58
+ "cable.pass_through_not_mapped": _DisplaySchema(
59
+ _REFERENCE_FIELDS, termination=frozenset({"entry", "exit", "mapped"})
51
60
  ),
52
- "cable.planned_termination_conflict": (
53
- frozenset({"segment_index", "termination", "competing_trace"}),
54
- frozenset(),
55
- frozenset(),
61
+ "cable.pass_through_verified": _DisplaySchema(_REFERENCE_FIELDS, termination=frozenset({"entry", "exit"})),
62
+ "cable.permission_denied": _DisplaySchema(_REFERENCE_FIELDS | {"permission"}, cable=frozenset({"cable"})),
63
+ "cable.planned_termination_conflict": _DisplaySchema(
64
+ frozenset({"segment_index", "competing_trace"}), termination=frozenset({"termination"})
56
65
  ),
57
- "cable.policy_stale": (frozenset({"segment_index", "cable_class"}), frozenset(), frozenset()),
58
- "cable.profile_incompatible": (frozenset({"segment_index", "cable_class"}), frozenset(), frozenset()),
59
- "cable.resolved_segment_conflict": (
60
- frozenset({"segment_index", "terminations"}),
61
- frozenset(),
62
- frozenset({"cable_type", "cable_profile"}),
63
- ),
64
- "cable.same_port_continuation": (_REFERENCE_FIELDS | {"peer"}, frozenset(), frozenset()),
65
- "cable.segment_override_lost": (
66
- frozenset({"segment_index"}),
67
- frozenset(),
68
- frozenset({"cable_type", "cable_profile"}),
69
- ),
70
- "cable.segment_reused": (
66
+ "cable.policy_stale": _DisplaySchema(frozenset({"segment_index", "cable_class"})),
67
+ "cable.profile_incompatible": _DisplaySchema(frozenset({"segment_index", "cable_class"})),
68
+ "cable.resolved_segment_conflict": _DisplaySchema(
71
69
  frozenset({"segment_index"}),
72
- frozenset({"cable"}),
73
- frozenset(),
74
- ),
75
- "cable.segment_self_connection": (
76
- frozenset({"segment_index", "cable_class", "termination"}),
77
- frozenset(),
78
- frozenset(),
70
+ policy=frozenset({"cable_type", "cable_profile"}),
71
+ termination=frozenset({"terminations"}),
79
72
  ),
80
- "cable.termination_kind_mismatch": (
81
- _REFERENCE_FIELDS | {"selected_display_name", "claimed_kind", "selected_kind"},
82
- frozenset(),
83
- frozenset(),
73
+ "cable.same_port_continuation": _DisplaySchema(_RESOLVED_PORT_FIELDS, termination=frozenset({"port", "peer"})),
74
+ "cable.segment_override_lost": _DisplaySchema(
75
+ frozenset({"segment_index"}), policy=frozenset({"cable_type", "cable_profile"})
84
76
  ),
85
- "cable.termination_occupied": (
86
- frozenset({"segment_index", "port"}),
87
- frozenset({"cable"}),
88
- frozenset(),
77
+ "cable.segment_reused": _DisplaySchema(frozenset({"segment_index"}), cable=frozenset({"cable"})),
78
+ "cable.segment_self_connection": _DisplaySchema(
79
+ frozenset({"segment_index", "cable_class"}), termination=frozenset({"termination"})
89
80
  ),
90
- "cable.termination_unresolved": (
91
- _REFERENCE_FIELDS | {"matches", "selected_display_name"},
92
- frozenset(),
93
- frozenset(),
94
- ),
95
- "cable.unsupported_termination_kind": (
96
- _REFERENCE_FIELDS | {"selected_object_type"},
97
- frozenset(),
98
- frozenset(),
81
+ "cable.termination_kind_mismatch": _DisplaySchema(_REFERENCE_FIELDS | {"claimed_kind", "selected_object_type"}),
82
+ "cable.termination_occupied": _DisplaySchema(
83
+ frozenset({"segment_index"}), cable=frozenset({"cable"}), termination=frozenset({"port"})
99
84
  ),
85
+ "cable.termination_unresolved": _DisplaySchema(_REFERENCE_FIELDS),
86
+ "cable.unsupported_termination_kind": _DisplaySchema(_REFERENCE_FIELDS | {"selected_object_type"}),
100
87
  }
101
88
  )
102
89
 
@@ -105,11 +92,15 @@ CABLE_DIAGNOSTIC_FIELDS = MappingProxyType(
105
92
  )
106
93
 
107
94
  CABLE_DIAGNOSTIC_DISCLOSURES = MappingProxyType(
108
- {code: fields[1] for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields[1]}
95
+ {code: fields.cable for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields.cable}
109
96
  )
110
97
 
111
98
  POLICY_DIAGNOSTIC_DISCLOSURES = MappingProxyType(
112
- {code: fields[2] for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields[2]}
99
+ {code: fields.policy for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields.policy}
100
+ )
101
+
102
+ TERMINATION_DIAGNOSTIC_DISCLOSURES = MappingProxyType(
103
+ {code: fields.termination for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields.termination}
113
104
  )
114
105
 
115
106
 
@@ -118,6 +109,21 @@ def disclosure_source(row_kind: str, row_pk: int) -> dict:
118
109
  return {"kind": row_kind, "pk": row_pk}
119
110
 
120
111
 
112
+ def device_source(device_id: int) -> dict:
113
+ """Return the source a display needs when it names one resolved Device."""
114
+ return disclosure_source(DEVICE_ROW, device_id)
115
+
116
+
117
+ def segment_end_sources(port: tuple[str, int], device_id: int) -> list:
118
+ """Return the rows that authorize one planned segment end, which names its Device and its port."""
119
+ return [disclosure_source(*port), device_source(device_id)]
120
+
121
+
122
+ def termination_sources(*terminations) -> dict:
123
+ """Return the sources a display needs when it names the given resolved terminations."""
124
+ return {TERMINATION_SOURCES: [disclosure_source(label, object_id) for label, object_id in terminations]}
125
+
126
+
121
127
  def disclosed_cable(cable) -> dict:
122
128
  """Return the fields a planner may record for one visible Cable."""
123
129
  return {
@@ -159,12 +165,10 @@ def policy_row_is_disclosed(display: dict, row) -> bool:
159
165
 
160
166
  def validate_diagnostic_disclosures(code: str, display: dict) -> None:
161
167
  """Reject diagnostic display fields that bypass the shared disclosure vocabulary."""
162
- if not code.startswith("cable."):
163
- return
164
168
  fields = _DIAGNOSTIC_DISCLOSURE_FIELDS.get(code)
165
169
  if fields is None:
166
170
  raise ValueError(f"Diagnostic '{code}' has no registered display schema.")
167
- public, cable, policy = fields
171
+ public, cable, policy, termination = fields
168
172
  if code == "cable.media_family_mismatch":
169
173
  _validate_media_segments(display)
170
174
  return
@@ -173,10 +177,19 @@ def validate_diagnostic_disclosures(code: str, display: dict) -> None:
173
177
  metadata.update({DISCLOSURE_SOURCE, "cable_visible"})
174
178
  if policy:
175
179
  metadata.update({DISCLOSURE_SOURCE, POLICY_VISIBLE})
176
- unknown = set(display) - set(public) - set(cable) - set(policy) - metadata
180
+ if termination:
181
+ metadata.add(TERMINATION_SOURCES)
182
+ if not _well_formed_termination_sources(display.get(TERMINATION_SOURCES)):
183
+ raise ValueError(f"Diagnostic '{code}' names terminations without their sources.")
184
+ unknown = set(display) - set(public) - set(cable) - set(policy) - set(termination) - metadata
177
185
  if unknown:
178
186
  names = ", ".join(sorted(unknown))
179
187
  raise ValueError(f"Diagnostic '{code}' has unregistered display fields: {names}.")
188
+ _validate_row_flags(code, display, cable, policy)
189
+
190
+
191
+ def _validate_row_flags(code: str, display: dict, cable: frozenset, policy: frozenset) -> None:
192
+ """Reject Cable and policy fields whose visibility flag and source disagree."""
180
193
  source = display.get(DISCLOSURE_SOURCE)
181
194
  if set(display) & set(cable) or "cable_visible" in display:
182
195
  visible = display.get("cable_visible")
@@ -225,27 +238,74 @@ def _source_pk(value, row_kind: str) -> int | None:
225
238
  return row_pk if isinstance(row_pk, int) and not isinstance(row_pk, bool) else None
226
239
 
227
240
 
228
- def _source_is_visible(value, row_kinds: tuple[str, ...], visible_row_ids: dict[str, set[int]]) -> bool:
241
+ def _source_is_visible(value, row_kinds, visible_row_ids: dict[str, set[int]]) -> bool:
229
242
  """Return whether one well-formed source names a row visible in its expected kind."""
230
243
  return any(
231
- (row_pk := _source_pk(value, row_kind)) is not None and row_pk in visible_row_ids[row_kind]
244
+ (row_pk := _source_pk(value, row_kind)) is not None and row_pk in visible_row_ids.get(row_kind, ())
232
245
  for row_kind in row_kinds
233
246
  )
234
247
 
235
248
 
249
+ def _well_formed_termination_sources(sources) -> bool:
250
+ """Return whether *sources* is a nonempty list of sources that each name a Cable End Kind row."""
251
+ return (
252
+ isinstance(sources, list)
253
+ and bool(sources)
254
+ and all(any(_source_pk(source, kind) is not None for kind in CABLE_END_KINDS) for source in sources)
255
+ )
256
+
257
+
258
+ def _terminations_are_visible(sources, visible_row_ids: dict[str, set[int]]) -> bool:
259
+ """Return whether every termination one display names is live and visible."""
260
+ return _well_formed_termination_sources(sources) and all(
261
+ _source_is_visible(source, CABLE_END_KINDS, visible_row_ids) for source in sources
262
+ )
263
+
264
+
265
+ def _identity_key(identity) -> tuple[str, int] | None:
266
+ """Return the Cable End Kind row one plan identity names, or None for any other identity."""
267
+ label, _, object_id = str(identity).rpartition(":")
268
+ if label not in CABLE_END_KINDS or not object_id.isdigit():
269
+ return None
270
+ return label, int(object_id)
271
+
272
+
273
+ def _row_sources(value: dict) -> list:
274
+ """Return every row source one display mapping carries."""
275
+ sources = [value.get(DISCLOSURE_SOURCE)]
276
+ for key in (TERMINATION_SOURCES, *SEGMENT_END_SOURCES.values()):
277
+ listed = value.get(key)
278
+ # A frozen plan holds a list as a tuple.
279
+ if isinstance(listed, (list, tuple)):
280
+ sources.extend(listed)
281
+ return sources
282
+
283
+
284
+ def _end_is_visible(sources, visible_row_ids: dict[str, set[int]]) -> bool:
285
+ """Return whether a planned end's port and Device are both live and visible."""
286
+ return (
287
+ isinstance(sources, (list, tuple))
288
+ and len(sources) == 2
289
+ and _source_is_visible(sources[0], CABLE_END_KINDS, visible_row_ids)
290
+ and _source_is_visible(sources[1], (DEVICE_ROW,), visible_row_ids)
291
+ )
292
+
293
+
236
294
  def _row_ids(units) -> dict[str, set[int]]:
237
295
  row_ids: dict[str, set[int]] = {
238
296
  CABLE_ROW: set(),
239
297
  CABLE_CLASS_MAPPING_ROW: set(),
240
298
  CABLE_SEGMENT_OVERRIDE_ROW: set(),
299
+ DEVICE_ROW: set(),
300
+ **{kind: set() for kind in sorted(CABLE_END_KINDS)},
241
301
  }
242
302
 
243
303
  def collect(value) -> None:
244
304
  if isinstance(value, dict):
245
- source = value.get(DISCLOSURE_SOURCE)
246
- for row_kind, ids in row_ids.items():
247
- if row_pk := _source_pk(source, row_kind):
248
- ids.add(row_pk)
305
+ for source in _row_sources(value):
306
+ for row_kind, ids in row_ids.items():
307
+ if row_pk := _source_pk(source, row_kind):
308
+ ids.add(row_pk)
249
309
  for child in value.values():
250
310
  collect(child)
251
311
  elif isinstance(value, (list, tuple)):
@@ -256,6 +316,9 @@ def _row_ids(units) -> dict[str, set[int]]:
256
316
  collect(unit.display)
257
317
  for diagnostic in unit.diagnostics:
258
318
  collect(diagnostic.display)
319
+ for identity in diagnostic.identities:
320
+ if key := _identity_key(identity):
321
+ row_ids[key[0]].add(key[1])
259
322
  return row_ids
260
323
 
261
324
 
@@ -299,43 +362,32 @@ def _media_segment(segment: dict, visible_row_ids: dict[str, set[int]]) -> dict:
299
362
  }
300
363
 
301
364
 
302
- def _media_message(segments: list[dict]) -> str:
303
- from .cable_policy import cable_media_family_label, cable_type_label
365
+ def _redact_terminations(display: dict, keys: frozenset[str]) -> dict:
366
+ for key in keys & set(display):
367
+ display[key] = [] if isinstance(display[key], list) else TERMINATION_HIDDEN
368
+ display.pop(TERMINATION_SOURCES, None)
369
+ return display
304
370
 
305
- statements = []
306
- for segment in segments:
307
- position = segment["segment_index"] + 1
308
- if not segment["visible"]:
309
- hidden = POLICY_HIDDEN if segment.get("origin") == "policy" else "a Cable you cannot view"
310
- statements.append(f"segment {position} uses {hidden}")
311
- continue
312
- family = cable_media_family_label(segment["family"])
313
- statement = f"segment {position} is {cable_type_label(segment['cable_type'])} ({family})"
314
- if segment["retained"]:
315
- statement += ", on the Cable this import keeps"
316
- statements.append(statement)
317
- remedy = (
318
- "Correct those Cables in NetBox, then re-read."
319
- if all(segment["retained"] for segment in segments)
320
- else "Force the segment that states the wrong medium, or correct the source."
371
+
372
+ def _visible_identities(identities, visible_row_ids: dict[str, set[int]]) -> tuple:
373
+ """Return the identities a viewer may read: a hidden or deleted termination drops out."""
374
+ return tuple(
375
+ identity
376
+ for identity in identities
377
+ if (key := _identity_key(identity)) is None or key[1] in visible_row_ids.get(key[0], ())
321
378
  )
322
- return f"Verified pass-throughs join these segments, and {'; '.join(statements)}. {remedy}"
323
379
 
324
380
 
325
381
  def _diagnostic(diagnostic, visible_row_ids: dict[str, set[int]]):
326
382
  display = diagnostic.to_dict()["display"]
327
383
  if diagnostic.code == "cable.media_family_mismatch":
328
- segments = [_media_segment(segment, visible_row_ids) for segment in display["segments"]]
329
- display["segments"] = segments
330
- display["families"] = sorted(
331
- {_family_label(segment["family"]) for segment in segments if segment["visible"] and segment.get("family")}
332
- )
333
- display["message"] = _media_message(segments)
334
- elif keys := CABLE_DIAGNOSTIC_DISCLOSURES.get(diagnostic.code):
335
- if display.get("cable_visible") is True and not _source_is_visible(
336
- display.get(DISCLOSURE_SOURCE), (CABLE_ROW,), visible_row_ids
337
- ):
338
- display = _redact_cable(display, keys)
384
+ display["segments"] = [_media_segment(segment, visible_row_ids) for segment in display["segments"]]
385
+ elif (
386
+ (keys := CABLE_DIAGNOSTIC_DISCLOSURES.get(diagnostic.code))
387
+ and display.get("cable_visible") is True
388
+ and not _source_is_visible(display.get(DISCLOSURE_SOURCE), (CABLE_ROW,), visible_row_ids)
389
+ ):
390
+ display = _redact_cable(display, keys)
339
391
  if (
340
392
  diagnostic.code in POLICY_DIAGNOSTIC_DISCLOSURES
341
393
  and display.get(POLICY_VISIBLE) is True
@@ -346,13 +398,10 @@ def _diagnostic(diagnostic, visible_row_ids: dict[str, set[int]]):
346
398
  )
347
399
  ):
348
400
  display = _redact_policy(display)
349
- return replace(diagnostic, display=display)
350
-
351
-
352
- def _family_label(family: str) -> str:
353
- from .cable_policy import cable_media_family_label
354
-
355
- return cable_media_family_label(family)
401
+ keys = TERMINATION_DIAGNOSTIC_DISCLOSURES.get(diagnostic.code)
402
+ if keys and not _terminations_are_visible(display.get(TERMINATION_SOURCES), visible_row_ids):
403
+ display = _redact_terminations(display, keys)
404
+ return replace(diagnostic, display=display, identities=_visible_identities(diagnostic.identities, visible_row_ids))
356
405
 
357
406
 
358
407
  def _unit(unit, visible_row_ids: dict[str, set[int]]):
@@ -380,28 +429,41 @@ def _unit(unit, visible_row_ids: dict[str, set[int]]):
380
429
  ):
381
430
  policy.pop(DISCLOSURE_SOURCE, None)
382
431
  policy.update(_redact_policy(policy))
432
+ for question in trace.get("devices") or ():
433
+ if question.get("selected") and not _source_is_visible(
434
+ question.get(DISCLOSURE_SOURCE), (DEVICE_ROW,), visible_row_ids
435
+ ):
436
+ question.pop(DISCLOSURE_SOURCE, None)
437
+ question["selected"] = DEVICE_HIDDEN
438
+ for field in trace.get("terminations") or ():
439
+ if field.get("selected") and not _source_is_visible(
440
+ field.get(DISCLOSURE_SOURCE), CABLE_END_KINDS, visible_row_ids
441
+ ):
442
+ field.pop(DISCLOSURE_SOURCE, None)
443
+ field.update(selected=TERMINATION_HIDDEN, selected_type="")
444
+ for segment in trace.get("segments") or ():
445
+ # Only a planned segment names NetBox ports; an unplanned one repeats the source text.
446
+ if not segment.get("segment_key"):
447
+ continue
448
+ for end, source_key in SEGMENT_END_SOURCES.items():
449
+ if not _end_is_visible(segment.get(source_key), visible_row_ids):
450
+ segment.pop(source_key, None)
451
+ segment[end] = TERMINATION_HIDDEN
383
452
  diagnostics = tuple(_diagnostic(item, visible_row_ids) for item in unit.diagnostics)
384
453
  return replace(unit, diagnostics=diagnostics, display=display)
385
454
 
386
455
 
387
456
  def present_units(units, viewer) -> tuple:
388
- """Return presentation copies after one live Cable visibility query."""
457
+ """Return presentation copies after one live visibility query per referenced row kind."""
389
458
  if viewer is None:
390
459
  raise TypeError("ReviewWorkspace requires a live viewer.")
391
460
  units = tuple(units)
392
- from dcim.models import Cable
393
-
394
- from .models import CableClassMapping, CableSegmentOverride
461
+ from django.apps import apps
395
462
 
396
463
  row_ids = _row_ids(units)
397
- models = {
398
- CABLE_ROW: Cable,
399
- CABLE_CLASS_MAPPING_ROW: CableClassMapping,
400
- CABLE_SEGMENT_OVERRIDE_ROW: CableSegmentOverride,
401
- }
402
464
  visible = {}
403
- for row_kind, model in models.items():
404
- ids = row_ids[row_kind]
465
+ for row_kind, ids in row_ids.items():
466
+ model = apps.get_model(row_kind)
405
467
  visible[row_kind] = (
406
468
  set(model.objects.restrict(viewer, "view").filter(pk__in=sorted(ids)).values_list("pk", flat=True))
407
469
  if ids
@@ -412,8 +474,6 @@ def present_units(units, viewer) -> tuple:
412
474
 
413
475
  def redact_deleted_cables(plan_data: dict) -> dict:
414
476
  """Return an execution copy whose deleted Cable rows authorize no stored display text."""
415
- from .plan import ImportPlan
416
-
417
477
  plan = ImportPlan.from_dict(plan_data)
418
478
  visible = _row_ids(plan.units)
419
479
  deleted_ids = {
@@ -427,22 +487,36 @@ def redact_deleted_cables(plan_data: dict) -> dict:
427
487
  return replace(plan, units=units).to_dict()
428
488
 
429
489
 
490
+ def register() -> None:
491
+ """Register the Cable display vocabulary, so a cached plan with an unknown Cable display field is refused."""
492
+ register_diagnostic_display(TargetModuleKey.CABLE, validate_diagnostic_disclosures)
493
+
494
+
430
495
  __all__ = (
431
496
  "CABLE_CLASS_MAPPING_ROW",
432
497
  "CABLE_DIAGNOSTIC_DISCLOSURES",
433
498
  "CABLE_DIAGNOSTIC_FIELDS",
434
499
  "CABLE_ROW",
435
500
  "CABLE_SEGMENT_OVERRIDE_ROW",
501
+ "DEVICE_HIDDEN",
502
+ "DEVICE_ROW",
436
503
  "DISCLOSURE_SOURCE",
437
504
  "POLICY_DIAGNOSTIC_DISCLOSURES",
438
505
  "POLICY_HIDDEN",
439
506
  "POLICY_VISIBLE",
440
- "POLICY_WRITE_REFUSED",
507
+ "SEGMENT_END_SOURCES",
508
+ "TERMINATION_DIAGNOSTIC_DISCLOSURES",
509
+ "TERMINATION_HIDDEN",
510
+ "TERMINATION_SOURCES",
511
+ "device_source",
441
512
  "disclosed_cable",
442
513
  "disclosed_policy",
443
514
  "disclosure_source",
444
515
  "policy_row_is_disclosed",
445
516
  "present_units",
446
517
  "redact_deleted_cables",
518
+ "register",
519
+ "segment_end_sources",
520
+ "termination_sources",
447
521
  "validate_diagnostic_disclosures",
448
522
  )