netbox-data-import 2.4.0__tar.gz → 2.6.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.
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/PKG-INFO +3 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/README.md +2 -2
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/__init__.py +1 -1
- netbox_data_import-2.6.0/netbox_data_import/_dns_worker.py +31 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/adapter_forms.py +1 -1
- netbox_data_import-2.6.0/netbox_data_import/cable_disclosure.py +448 -0
- netbox_data_import-2.6.0/netbox_data_import/cable_policy.py +157 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/cable_target.py +334 -57
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/catalog.py +1 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/device_field_review.py +45 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/forms.py +53 -5
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/inference_adapter.py +1 -5
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/inference_backend.py +16 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/inference_credentials.py +2 -9
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/inference_transport.py +1 -27
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/inference_trust.py +109 -2
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/jobs.py +3 -1
- netbox_data_import-2.6.0/netbox_data_import/migrations/0036_resolutionproposal_job.py +27 -0
- netbox_data_import-2.6.0/netbox_data_import/migrations/0037_cablesegmentoverride.py +42 -0
- netbox_data_import-2.6.0/netbox_data_import/migrations/0038_cable_tag_integrity.py +104 -0
- netbox_data_import-2.6.0/netbox_data_import/migrations/0039_remove_job_plan_copies.py +22 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/models.py +77 -76
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/plan.py +24 -4
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/preview_row_actions.py +1 -6
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/proposal_decisions.py +3 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/proposal_jobs.py +8 -6
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/proposal_presentation.py +92 -19
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/proposal_tasks.py +46 -6
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/resolution_proposals.py +64 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/review_workspace.py +191 -10
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/conflict_modal.js +3 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/contact_candidate_modal.js +15 -11
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/preview_row_actions.js +2 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/preview_row_controls.js +126 -4
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/sync_row_modal.js +147 -53
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/trace_picker_search.js +10 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/trace_proposals.js +7 -8
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tables.py +30 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/target_modules.py +21 -6
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/target_runtime.py +2 -7
- netbox_data_import-2.6.0/netbox_data_import/templates/netbox_data_import/_proposal_card.html +33 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/import_preview.html +28 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/import_profile_yaml.html +1 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/trace_workspace.html +208 -47
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/helpers.py +63 -13
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/mock_discipline.py +79 -21
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_adapter_config_migration.py +2 -2
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_atomic_exits.py +15 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_cable_module.py +1243 -28
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_ci_configuration.py +14 -28
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_comment_style.py +2 -2
- netbox_data_import-2.6.0/netbox_data_import/tests/test_configuration_docs.py +147 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_conflict_row_actions.py +1 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_contact_resolution_ajax.py +4 -5
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_contact_sync.py +2 -2
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_execution_audit.py +1 -1
- netbox_data_import-2.6.0/netbox_data_import/tests/test_helpers.py +43 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_import_cutover.py +110 -5
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_import_source_migration.py +2 -2
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_inference_adapter.py +1 -110
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_inference_connection_test.py +77 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_inference_trust.py +228 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_installation_docs.py +1 -1
- netbox_data_import-2.6.0/netbox_data_import/tests/test_job_plan_cleanup_migration.py +57 -0
- netbox_data_import-2.6.0/netbox_data_import/tests/test_markdown_tables.py +124 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_migrations.py +103 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_misc_coverage.py +5 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_mock_discipline.py +65 -6
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_module_boundaries.py +24 -15
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_placement_reference_locks.py +1 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_policy_write_refusals.py +1 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_preview_assets.py +483 -10
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_pristine_preview_rows.py +1 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_proposal_decisions.py +26 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_proposal_jobs.py +96 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_proposal_tasks.py +48 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_proposal_workspace.py +442 -8
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_quick_action_input_bounds.py +4 -4
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_release_metadata.py +3 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_resolution_proposals.py +19 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_retire_superseded_proposals_migration.py +1 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_review_actions.py +28 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_review_resolutions.py +1 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_review_workspace.py +8 -2
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_target_field_catalog.py +2 -2
- netbox_data_import-2.6.0/netbox_data_import/tests/test_text_encoding.py +116 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_trace_decisions.py +3 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_trace_device_resolution.py +186 -4
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_trace_workbook.py +62 -16
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_trace_workspace.py +1475 -18
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_views.py +5 -5
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/trace_workbook.py +5 -14
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/urls.py +10 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/views.py +526 -37
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import.egg-info/PKG-INFO +3 -3
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import.egg-info/SOURCES.txt +12 -1
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/pyproject.toml +13 -5
- netbox_data_import-2.4.0/netbox_data_import/templates/netbox_data_import/_proposal_card.html +0 -31
- netbox_data_import-2.4.0/netbox_data_import/tests/test_configuration_docs.py +0 -55
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/LICENSE +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/adapter_config.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/adapters.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/api/__init__.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/api/serializers.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/api/urls.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/api/views.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/contact_resolution.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/device_identity.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/field_keys.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/filters.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/flat_workbook.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/graphql/__init__.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/graphql/filters.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/graphql/schema.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/graphql/types.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/import_engine.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/inference_connection_test.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/inference_settings.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/ip_assignment.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0001_initial.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0002_importjob.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0003_remove_classrolemapping_unique_class_role_mapping_per_profile_and_more.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0004_features.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0005_deviceexistingmatch.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0006_deviceexistingmatch_asset_tag.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0007_manufacturermapping.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0008_unique_constraints.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0009_add_rack_type_to_classrolemapping.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0010_alter_manufacturermapping_id.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0011_importprofile_capture_extra_data.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0012_classrolemapping_rack_type_related_name.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0013_remove_columnmapping_unique_target.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0014_alter_columnmapping_target_field_and_more.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0015_cleanup_duplicate_device_matches.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0016_deviceexistingmatch_ndi_devicematch_profile_device.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0017_native_contact_sync.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0018_ignoredfielddifference.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0019_deviceimportsource.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0020_migrate_import_source_custom_field.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0021_importprofile_adapter_config.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0022_migrate_profile_adapter_config.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0023_drop_moved_profile_columns.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0024_rename_importjob_importexecution.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0025_remove_importexecution_dry_run_and_more.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0026_alter_sourcedocument_profile.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0027_cableclassmapping_terminationresolution.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0028_cableimportsource.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0029_alter_cableimportsource_from_text_and_more.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0030_remove_device_type_creation_config.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0031_inferencebackend.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0032_inference_timeout_floor.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0033_resolutionproposal.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0034_tracedeviceresolution.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/0035_retire_superseded_proposals.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/migrations/__init__.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/navigation.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/netbox_reader.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/object_permissions.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/profile_yaml.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/proposal_contract.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/proposal_response.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/signals.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/source_resolution.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/source_trace.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/split_name_modal.js +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/trace_device_picker.js +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/static/netbox_data_import/js/trace_termination_picker.js +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/template_content.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/_import_progress.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/_proposal_action.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/analysis.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/bulk_yaml_import.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/cableclassmapping_edit.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/classrolemapping_edit.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/columnmapping_edit.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/columntransformrule_edit.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/device_import_data.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/devicetypemapping_edit.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/import_progress.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/import_results.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/import_setup.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/importexecution_list.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/importprofile.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/importprofile_edit.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/importprofile_list.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/inferencebackend.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/templates/netbox_data_import/source_resolution_list.html +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/termination_proposal.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/__init__.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/conftest.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/fixtures/combined_name.xlsx +0 -0
- /netbox_data_import-2.4.0/netbox_data_import/tests/fixtures/sample_cans.xlsx → /netbox_data_import-2.6.0/netbox_data_import/tests/fixtures/sample_workbook.xlsx +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/fixtures/trace_copper.xlsx +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/fixtures/trace_fiber.xlsx +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/inference_http.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/isolated_settings.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/mixins.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/netbox_configuration.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/parallel.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/redaction.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_adapter_config.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_api.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_configuration_surfaces.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_contact_role_suggestion.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_cutover_edge_contracts.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_devcontainer_e2e.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_device_module.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_digest_indexed_models.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_graphql_filters.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_import_engine.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_import_execution.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_import_plan.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_inference_backend.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_inference_credentials.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_inference_secret_containment.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_inference_settings.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_ip_target.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_model_base_conventions.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_models.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_navigation.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_netbox_reader.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_object_permission_writer.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_parallel_test_setup.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_preview_state_ownership.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_profile_policy_crud_scoping.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_proposal_response.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_quick_action_object_permissions.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_quick_action_profile_id.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_rack_module.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_remove_device_type_creation_config_migration.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_resolution_proposal_api.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_serial_display.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_source_adapter.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_source_trace_contract.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_standard_views.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_target_module_edge_contracts.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_views_coverage2.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/tests/test_views_extra.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/trace_device_resolution.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/trace_schema.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/transform_regex.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/utils.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import/values.py +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import.egg-info/dependency_links.txt +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import.egg-info/requires.txt +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/netbox_data_import.egg-info/top_level.txt +0 -0
- {netbox_data_import-2.4.0 → netbox_data_import-2.6.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netbox-data-import
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.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
|
|
@@ -39,14 +39,14 @@ A NetBox plugin for importing device inventory and rack layouts from external DC
|
|
|
39
39
|
|
|
40
40
|
This plugin allows you to:
|
|
41
41
|
|
|
42
|
-
- Define **import profiles** that configure how your source data (Excel
|
|
42
|
+
- Define **import profiles** that configure how your source data (Excel) maps to NetBox objects
|
|
43
43
|
- Configure **field mappings** per column with transformation rules
|
|
44
44
|
- Define **class/role mappings** to translate your source system's device categories to NetBox roles
|
|
45
45
|
- Synchronize a mapped **primary contact** into native NetBox Contacts and Contact Assignments
|
|
46
46
|
- **Preview** the import before applying changes
|
|
47
47
|
- **Run imports in the background**, track processed rows, and resume the progress page
|
|
48
48
|
|
|
49
|
-
Currently supports importing from Excel (`.xlsx`)
|
|
49
|
+
Currently supports importing from Excel (`.xlsx`) workbooks with a flat, one-row-per-device layout. The mapping configuration is stored in NetBox, so it can be reused and adapted for other source formats.
|
|
50
50
|
|
|
51
51
|
## Requirements
|
|
52
52
|
|
|
@@ -16,14 +16,14 @@ A NetBox plugin for importing device inventory and rack layouts from external DC
|
|
|
16
16
|
|
|
17
17
|
This plugin allows you to:
|
|
18
18
|
|
|
19
|
-
- Define **import profiles** that configure how your source data (Excel
|
|
19
|
+
- Define **import profiles** that configure how your source data (Excel) maps to NetBox objects
|
|
20
20
|
- Configure **field mappings** per column with transformation rules
|
|
21
21
|
- Define **class/role mappings** to translate your source system's device categories to NetBox roles
|
|
22
22
|
- Synchronize a mapped **primary contact** into native NetBox Contacts and Contact Assignments
|
|
23
23
|
- **Preview** the import before applying changes
|
|
24
24
|
- **Run imports in the background**, track processed rows, and resume the progress page
|
|
25
25
|
|
|
26
|
-
Currently supports importing from Excel (`.xlsx`)
|
|
26
|
+
Currently supports importing from Excel (`.xlsx`) workbooks with a flat, one-row-per-device layout. The mapping configuration is stored in NetBox, so it can be reused and adapted for other source formats.
|
|
27
27
|
|
|
28
28
|
## Requirements
|
|
29
29
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Marcin Zieba <marcinpsk@gmail.com>
|
|
3
|
+
"""Resolve one host with libc in a deadline-bounded child process."""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import signal
|
|
7
|
+
import socket
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from typing import TextIO
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main(stdin: TextIO, stdout: TextIO) -> int:
|
|
14
|
+
"""Read one request from stdin and write its ordered addresses to stdout."""
|
|
15
|
+
try:
|
|
16
|
+
host, port, timeout = json.load(stdin)
|
|
17
|
+
except Exception: # noqa: BLE001 - the parent exposes one typed resolution failure
|
|
18
|
+
return 1
|
|
19
|
+
# The alarm is the deadline. An unarmed worker must crash, not read as a failed lookup.
|
|
20
|
+
signal.setitimer(signal.ITIMER_REAL, timeout)
|
|
21
|
+
try:
|
|
22
|
+
answers = socket.getaddrinfo(host, port, proto=socket.IPPROTO_TCP)
|
|
23
|
+
addresses = list(dict.fromkeys(str(answer[4][0]) for answer in answers))
|
|
24
|
+
except Exception: # noqa: BLE001 - the parent exposes one typed resolution failure
|
|
25
|
+
return 1
|
|
26
|
+
json.dump(addresses, stdout)
|
|
27
|
+
return 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__": # pragma: no cover - child entry point; main() is tested in process
|
|
31
|
+
raise SystemExit(main(sys.stdin, sys.stdout))
|
|
@@ -91,7 +91,7 @@ class FlatWorkbookConfigForm(AdapterConfigForm):
|
|
|
91
91
|
max_length=100,
|
|
92
92
|
required=False,
|
|
93
93
|
initial="",
|
|
94
|
-
help_text="NetBox custom field name to store the source ID in (e.g. '
|
|
94
|
+
help_text="NetBox custom field name to store the source ID in (e.g. 'external_id')",
|
|
95
95
|
)
|
|
96
96
|
update_existing = forms.BooleanField(
|
|
97
97
|
required=False,
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Marcin Zieba <marcinpsk@gmail.com>
|
|
3
|
+
"""Apply live row permissions to cached Cable presentation data."""
|
|
4
|
+
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
|
|
8
|
+
CABLE_ROW = "dcim.cable"
|
|
9
|
+
CABLE_CLASS_MAPPING_ROW = "netbox_data_import.cableclassmapping"
|
|
10
|
+
CABLE_SEGMENT_OVERRIDE_ROW = "netbox_data_import.cablesegmentoverride"
|
|
11
|
+
DISCLOSURE_SOURCE = "disclosure_source"
|
|
12
|
+
POLICY_HIDDEN = "a policy you cannot view"
|
|
13
|
+
POLICY_VISIBLE = "policy_visible"
|
|
14
|
+
POLICY_WRITE_REFUSED = "You cannot change a policy you cannot view."
|
|
15
|
+
|
|
16
|
+
_REFERENCE_FIELDS = frozenset({"device", "cards", "port", "port_class"})
|
|
17
|
+
|
|
18
|
+
_DIAGNOSTIC_DISCLOSURE_FIELDS = MappingProxyType(
|
|
19
|
+
{
|
|
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"}),
|
|
31
|
+
),
|
|
32
|
+
"cable.multi_termination_conflict": (
|
|
33
|
+
frozenset({"segment_index", "port"}),
|
|
34
|
+
frozenset({"cable"}),
|
|
35
|
+
frozenset(),
|
|
36
|
+
),
|
|
37
|
+
"cable.pass_through_not_mapped": (
|
|
38
|
+
_REFERENCE_FIELDS | {"entry", "exit", "mapped"},
|
|
39
|
+
frozenset(),
|
|
40
|
+
frozenset(),
|
|
41
|
+
),
|
|
42
|
+
"cable.pass_through_verified": (
|
|
43
|
+
_REFERENCE_FIELDS | {"entry", "exit"},
|
|
44
|
+
frozenset(),
|
|
45
|
+
frozenset(),
|
|
46
|
+
),
|
|
47
|
+
"cable.permission_denied": (
|
|
48
|
+
_REFERENCE_FIELDS | {"permission"},
|
|
49
|
+
frozenset({"cable"}),
|
|
50
|
+
frozenset(),
|
|
51
|
+
),
|
|
52
|
+
"cable.planned_termination_conflict": (
|
|
53
|
+
frozenset({"segment_index", "termination", "competing_trace"}),
|
|
54
|
+
frozenset(),
|
|
55
|
+
frozenset(),
|
|
56
|
+
),
|
|
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": (
|
|
71
|
+
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(),
|
|
79
|
+
),
|
|
80
|
+
"cable.termination_kind_mismatch": (
|
|
81
|
+
_REFERENCE_FIELDS | {"selected_display_name", "claimed_kind", "selected_kind"},
|
|
82
|
+
frozenset(),
|
|
83
|
+
frozenset(),
|
|
84
|
+
),
|
|
85
|
+
"cable.termination_occupied": (
|
|
86
|
+
frozenset({"segment_index", "port"}),
|
|
87
|
+
frozenset({"cable"}),
|
|
88
|
+
frozenset(),
|
|
89
|
+
),
|
|
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(),
|
|
99
|
+
),
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
CABLE_DIAGNOSTIC_FIELDS = MappingProxyType(
|
|
104
|
+
{code: frozenset().union(*fields) for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items()}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
CABLE_DIAGNOSTIC_DISCLOSURES = MappingProxyType(
|
|
108
|
+
{code: fields[1] for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields[1]}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
POLICY_DIAGNOSTIC_DISCLOSURES = MappingProxyType(
|
|
112
|
+
{code: fields[2] for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields[2]}
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def disclosure_source(row_kind: str, row_pk: int) -> dict:
|
|
117
|
+
"""Return the plan-side identity for one row that supplied display text."""
|
|
118
|
+
return {"kind": row_kind, "pk": row_pk}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def disclosed_cable(cable) -> dict:
|
|
122
|
+
"""Return the fields a planner may record for one visible Cable."""
|
|
123
|
+
return {
|
|
124
|
+
"cable_visible": True,
|
|
125
|
+
"cable": str(cable),
|
|
126
|
+
DISCLOSURE_SOURCE: disclosure_source(CABLE_ROW, cable.pk),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def policy_row_kind(row) -> str:
|
|
131
|
+
"""Return the disclosure kind for one supported Cable policy row."""
|
|
132
|
+
from .models import CableClassMapping, CableSegmentOverride
|
|
133
|
+
|
|
134
|
+
if isinstance(row, CableClassMapping):
|
|
135
|
+
return CABLE_CLASS_MAPPING_ROW
|
|
136
|
+
if isinstance(row, CableSegmentOverride):
|
|
137
|
+
return CABLE_SEGMENT_OVERRIDE_ROW
|
|
138
|
+
raise TypeError(f"Unsupported Cable policy row: {type(row).__name__}")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def disclosed_policy(row, visible: bool, display: dict) -> dict:
|
|
142
|
+
"""Return policy display values only when the planning viewer may read their row."""
|
|
143
|
+
if not visible:
|
|
144
|
+
return _redact_policy(display)
|
|
145
|
+
return {
|
|
146
|
+
**display,
|
|
147
|
+
POLICY_VISIBLE: True,
|
|
148
|
+
DISCLOSURE_SOURCE: disclosure_source(policy_row_kind(row), row.pk),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def policy_row_is_disclosed(display: dict, row) -> bool:
|
|
153
|
+
"""Return whether a presented value names this policy row as its visible source."""
|
|
154
|
+
return (
|
|
155
|
+
display.get(POLICY_VISIBLE) is True
|
|
156
|
+
and _source_pk(display.get(DISCLOSURE_SOURCE), policy_row_kind(row)) == row.pk
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def validate_diagnostic_disclosures(code: str, display: dict) -> None:
|
|
161
|
+
"""Reject diagnostic display fields that bypass the shared disclosure vocabulary."""
|
|
162
|
+
if not code.startswith("cable."):
|
|
163
|
+
return
|
|
164
|
+
fields = _DIAGNOSTIC_DISCLOSURE_FIELDS.get(code)
|
|
165
|
+
if fields is None:
|
|
166
|
+
raise ValueError(f"Diagnostic '{code}' has no registered display schema.")
|
|
167
|
+
public, cable, policy = fields
|
|
168
|
+
if code == "cable.media_family_mismatch":
|
|
169
|
+
_validate_media_segments(display)
|
|
170
|
+
return
|
|
171
|
+
metadata = set()
|
|
172
|
+
if cable:
|
|
173
|
+
metadata.update({DISCLOSURE_SOURCE, "cable_visible"})
|
|
174
|
+
if policy:
|
|
175
|
+
metadata.update({DISCLOSURE_SOURCE, POLICY_VISIBLE})
|
|
176
|
+
unknown = set(display) - set(public) - set(cable) - set(policy) - metadata
|
|
177
|
+
if unknown:
|
|
178
|
+
names = ", ".join(sorted(unknown))
|
|
179
|
+
raise ValueError(f"Diagnostic '{code}' has unregistered display fields: {names}.")
|
|
180
|
+
source = display.get(DISCLOSURE_SOURCE)
|
|
181
|
+
if set(display) & set(cable) or "cable_visible" in display:
|
|
182
|
+
visible = display.get("cable_visible")
|
|
183
|
+
if not isinstance(visible, bool):
|
|
184
|
+
raise ValueError(f"Diagnostic '{code}' has Cable fields without a visibility flag.")
|
|
185
|
+
if visible and _source_pk(source, CABLE_ROW) is None:
|
|
186
|
+
raise ValueError(f"Diagnostic '{code}' has visible Cable fields without a Cable source.")
|
|
187
|
+
if not visible and source is not None:
|
|
188
|
+
raise ValueError(f"Diagnostic '{code}' has a source for a hidden Cable.")
|
|
189
|
+
if set(display) & set(policy) or POLICY_VISIBLE in display:
|
|
190
|
+
visible = display.get(POLICY_VISIBLE)
|
|
191
|
+
if not isinstance(visible, bool):
|
|
192
|
+
raise ValueError(f"Diagnostic '{code}' has policy fields without a visibility flag.")
|
|
193
|
+
row_kinds = (CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW)
|
|
194
|
+
if visible and not any(_source_pk(source, kind) is not None for kind in row_kinds):
|
|
195
|
+
raise ValueError(f"Diagnostic '{code}' has visible policy fields without a policy source.")
|
|
196
|
+
if not visible and source is not None:
|
|
197
|
+
raise ValueError(f"Diagnostic '{code}' has a source for a hidden policy.")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _validate_media_segments(display: dict) -> None:
|
|
201
|
+
"""Reject media observations that omit provenance or add unregistered nested fields."""
|
|
202
|
+
if set(display) != {"segments"} or not isinstance(display["segments"], list):
|
|
203
|
+
raise ValueError("Diagnostic 'cable.media_family_mismatch' has an invalid display schema.")
|
|
204
|
+
base = {"segment_index", "retained", "visible", "origin"}
|
|
205
|
+
disclosed = {"cable_type", "family", DISCLOSURE_SOURCE}
|
|
206
|
+
for segment in display["segments"]:
|
|
207
|
+
if not isinstance(segment, dict) or frozenset(segment) not in {frozenset(base), frozenset(base | disclosed)}:
|
|
208
|
+
raise ValueError("Diagnostic 'cable.media_family_mismatch' has invalid segment fields.")
|
|
209
|
+
origin = segment.get("origin")
|
|
210
|
+
if origin not in {"cable", "policy"} or not isinstance(segment.get("visible"), bool):
|
|
211
|
+
raise ValueError("Diagnostic 'cable.media_family_mismatch' has an invalid segment origin.")
|
|
212
|
+
if segment["visible"] is False:
|
|
213
|
+
if set(segment) != base:
|
|
214
|
+
raise ValueError("Diagnostic 'cable.media_family_mismatch' discloses a hidden segment.")
|
|
215
|
+
continue
|
|
216
|
+
row_kinds = (CABLE_ROW,) if origin == "cable" else (CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW)
|
|
217
|
+
if not any(_source_pk(segment.get(DISCLOSURE_SOURCE), kind) is not None for kind in row_kinds):
|
|
218
|
+
raise ValueError("Diagnostic 'cable.media_family_mismatch' has a visible segment without its source.")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _source_pk(value, row_kind: str) -> int | None:
|
|
222
|
+
if not isinstance(value, dict) or value.get("kind") != row_kind:
|
|
223
|
+
return None
|
|
224
|
+
row_pk = value.get("pk")
|
|
225
|
+
return row_pk if isinstance(row_pk, int) and not isinstance(row_pk, bool) else None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _source_is_visible(value, row_kinds: tuple[str, ...], visible_row_ids: dict[str, set[int]]) -> bool:
|
|
229
|
+
"""Return whether one well-formed source names a row visible in its expected kind."""
|
|
230
|
+
return any(
|
|
231
|
+
(row_pk := _source_pk(value, row_kind)) is not None and row_pk in visible_row_ids[row_kind]
|
|
232
|
+
for row_kind in row_kinds
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _row_ids(units) -> dict[str, set[int]]:
|
|
237
|
+
row_ids: dict[str, set[int]] = {
|
|
238
|
+
CABLE_ROW: set(),
|
|
239
|
+
CABLE_CLASS_MAPPING_ROW: set(),
|
|
240
|
+
CABLE_SEGMENT_OVERRIDE_ROW: set(),
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
def collect(value) -> None:
|
|
244
|
+
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)
|
|
249
|
+
for child in value.values():
|
|
250
|
+
collect(child)
|
|
251
|
+
elif isinstance(value, (list, tuple)):
|
|
252
|
+
for child in value:
|
|
253
|
+
collect(child)
|
|
254
|
+
|
|
255
|
+
for unit in units:
|
|
256
|
+
collect(unit.display)
|
|
257
|
+
for diagnostic in unit.diagnostics:
|
|
258
|
+
collect(diagnostic.display)
|
|
259
|
+
return row_ids
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _redact_cable(display: dict, keys: frozenset[str]) -> dict:
|
|
263
|
+
for key in keys:
|
|
264
|
+
display.pop(key, None)
|
|
265
|
+
display.pop(DISCLOSURE_SOURCE, None)
|
|
266
|
+
display["cable_visible"] = False
|
|
267
|
+
return display
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _redact_policy(display: dict) -> dict:
|
|
271
|
+
display = dict(display)
|
|
272
|
+
for key in ("cable_type", "cable_profile"):
|
|
273
|
+
if key in display:
|
|
274
|
+
display[key] = POLICY_HIDDEN
|
|
275
|
+
if "policy" in display:
|
|
276
|
+
display["policy"] = {}
|
|
277
|
+
display.pop(DISCLOSURE_SOURCE, None)
|
|
278
|
+
display[POLICY_VISIBLE] = False
|
|
279
|
+
return display
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _media_segment(segment: dict, visible_row_ids: dict[str, set[int]]) -> dict:
|
|
283
|
+
source = segment.get(DISCLOSURE_SOURCE)
|
|
284
|
+
origin = segment.get("origin")
|
|
285
|
+
row_kinds = (
|
|
286
|
+
(CABLE_ROW,)
|
|
287
|
+
if origin == "cable"
|
|
288
|
+
else (CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW)
|
|
289
|
+
if origin == "policy"
|
|
290
|
+
else ()
|
|
291
|
+
)
|
|
292
|
+
if segment.get("visible") is True and _source_is_visible(source, row_kinds, visible_row_ids):
|
|
293
|
+
return segment
|
|
294
|
+
return {
|
|
295
|
+
"segment_index": segment["segment_index"],
|
|
296
|
+
"retained": segment["retained"],
|
|
297
|
+
"visible": False,
|
|
298
|
+
"origin": origin if origin in {"cable", "policy"} else "cable",
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _media_message(segments: list[dict]) -> str:
|
|
303
|
+
from .cable_policy import cable_media_family_label, cable_type_label
|
|
304
|
+
|
|
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."
|
|
321
|
+
)
|
|
322
|
+
return f"Verified pass-throughs join these segments, and {'; '.join(statements)}. {remedy}"
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _diagnostic(diagnostic, visible_row_ids: dict[str, set[int]]):
|
|
326
|
+
display = diagnostic.to_dict()["display"]
|
|
327
|
+
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)
|
|
339
|
+
if (
|
|
340
|
+
diagnostic.code in POLICY_DIAGNOSTIC_DISCLOSURES
|
|
341
|
+
and display.get(POLICY_VISIBLE) is True
|
|
342
|
+
and not _source_is_visible(
|
|
343
|
+
display.get(DISCLOSURE_SOURCE),
|
|
344
|
+
(CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW),
|
|
345
|
+
visible_row_ids,
|
|
346
|
+
)
|
|
347
|
+
):
|
|
348
|
+
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)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _unit(unit, visible_row_ids: dict[str, set[int]]):
|
|
359
|
+
display = unit.to_dict()["display"]
|
|
360
|
+
trace = display.get("trace")
|
|
361
|
+
if trace is not None:
|
|
362
|
+
logical = trace.get("logical_cable")
|
|
363
|
+
if (
|
|
364
|
+
logical is not None
|
|
365
|
+
and logical.get("visible") is True
|
|
366
|
+
and not _source_is_visible(logical.get(DISCLOSURE_SOURCE), (CABLE_ROW,), visible_row_ids)
|
|
367
|
+
):
|
|
368
|
+
trace["logical_cable"] = {"visible": False, "display": "", "description": "", "tags": []}
|
|
369
|
+
for segment in trace.get("segments") or ():
|
|
370
|
+
if segment.get(POLICY_VISIBLE) is True and not _source_is_visible(
|
|
371
|
+
segment.get(DISCLOSURE_SOURCE),
|
|
372
|
+
(CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW),
|
|
373
|
+
visible_row_ids,
|
|
374
|
+
):
|
|
375
|
+
segment.pop(DISCLOSURE_SOURCE, None)
|
|
376
|
+
segment.update(_redact_policy(segment))
|
|
377
|
+
for policy in trace.get("cable_policies") or ():
|
|
378
|
+
if policy.get(POLICY_VISIBLE) is True and not _source_is_visible(
|
|
379
|
+
policy.get(DISCLOSURE_SOURCE), (CABLE_CLASS_MAPPING_ROW,), visible_row_ids
|
|
380
|
+
):
|
|
381
|
+
policy.pop(DISCLOSURE_SOURCE, None)
|
|
382
|
+
policy.update(_redact_policy(policy))
|
|
383
|
+
diagnostics = tuple(_diagnostic(item, visible_row_ids) for item in unit.diagnostics)
|
|
384
|
+
return replace(unit, diagnostics=diagnostics, display=display)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def present_units(units, viewer) -> tuple:
|
|
388
|
+
"""Return presentation copies after one live Cable visibility query."""
|
|
389
|
+
if viewer is None:
|
|
390
|
+
raise TypeError("ReviewWorkspace requires a live viewer.")
|
|
391
|
+
units = tuple(units)
|
|
392
|
+
from dcim.models import Cable
|
|
393
|
+
|
|
394
|
+
from .models import CableClassMapping, CableSegmentOverride
|
|
395
|
+
|
|
396
|
+
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
|
+
visible = {}
|
|
403
|
+
for row_kind, model in models.items():
|
|
404
|
+
ids = row_ids[row_kind]
|
|
405
|
+
visible[row_kind] = (
|
|
406
|
+
set(model.objects.restrict(viewer, "view").filter(pk__in=sorted(ids)).values_list("pk", flat=True))
|
|
407
|
+
if ids
|
|
408
|
+
else set()
|
|
409
|
+
)
|
|
410
|
+
return tuple(_unit(unit, visible) for unit in units)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def redact_deleted_cables(plan_data: dict) -> dict:
|
|
414
|
+
"""Return an execution copy whose deleted Cable rows authorize no stored display text."""
|
|
415
|
+
from .plan import ImportPlan
|
|
416
|
+
|
|
417
|
+
plan = ImportPlan.from_dict(plan_data)
|
|
418
|
+
visible = _row_ids(plan.units)
|
|
419
|
+
deleted_ids = {
|
|
420
|
+
change.payload["cable_id"]
|
|
421
|
+
for unit in plan.units
|
|
422
|
+
for change in unit.changes
|
|
423
|
+
if change.target_module == "cable" and change.operation == "delete"
|
|
424
|
+
}
|
|
425
|
+
visible[CABLE_ROW].difference_update(deleted_ids)
|
|
426
|
+
units = tuple(_unit(unit, visible) for unit in plan.units)
|
|
427
|
+
return replace(plan, units=units).to_dict()
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
__all__ = (
|
|
431
|
+
"CABLE_CLASS_MAPPING_ROW",
|
|
432
|
+
"CABLE_DIAGNOSTIC_DISCLOSURES",
|
|
433
|
+
"CABLE_DIAGNOSTIC_FIELDS",
|
|
434
|
+
"CABLE_ROW",
|
|
435
|
+
"CABLE_SEGMENT_OVERRIDE_ROW",
|
|
436
|
+
"DISCLOSURE_SOURCE",
|
|
437
|
+
"POLICY_DIAGNOSTIC_DISCLOSURES",
|
|
438
|
+
"POLICY_HIDDEN",
|
|
439
|
+
"POLICY_VISIBLE",
|
|
440
|
+
"POLICY_WRITE_REFUSED",
|
|
441
|
+
"disclosed_cable",
|
|
442
|
+
"disclosed_policy",
|
|
443
|
+
"disclosure_source",
|
|
444
|
+
"policy_row_is_disclosed",
|
|
445
|
+
"present_units",
|
|
446
|
+
"redact_deleted_cables",
|
|
447
|
+
"validate_diagnostic_disclosures",
|
|
448
|
+
)
|