netbox-data-import 2.3.0__tar.gz → 2.5.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.3.0 → netbox_data_import-2.5.0}/PKG-INFO +3 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/README.md +2 -2
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/__init__.py +1 -1
- netbox_data_import-2.5.0/netbox_data_import/_dns_worker.py +31 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/adapter_forms.py +1 -1
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/adapters.py +53 -2
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/cable_target.py +48 -45
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/catalog.py +1 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/device_field_review.py +45 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/forms.py +24 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/import_engine.py +3 -1
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/inference_adapter.py +143 -48
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/inference_backend.py +18 -0
- netbox_data_import-2.5.0/netbox_data_import/inference_connection_test.py +146 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/inference_credentials.py +68 -24
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/inference_settings.py +3 -4
- netbox_data_import-2.5.0/netbox_data_import/inference_transport.py +323 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/inference_trust.py +109 -2
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/ip_assignment.py +72 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/jobs.py +0 -23
- netbox_data_import-2.5.0/netbox_data_import/migrations/0034_tracedeviceresolution.py +41 -0
- netbox_data_import-2.5.0/netbox_data_import/migrations/0035_retire_superseded_proposals.py +28 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/models.py +69 -11
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/netbox_reader.py +6 -0
- netbox_data_import-2.5.0/netbox_data_import/object_permissions.py +677 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/profile_yaml.py +168 -60
- netbox_data_import-2.5.0/netbox_data_import/proposal_contract.py +30 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/proposal_jobs.py +12 -17
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/proposal_response.py +36 -16
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/review_workspace.py +74 -12
- netbox_data_import-2.5.0/netbox_data_import/source_trace.py +124 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/static/netbox_data_import/js/conflict_modal.js +3 -1
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/static/netbox_data_import/js/contact_candidate_modal.js +15 -11
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/static/netbox_data_import/js/preview_row_actions.js +2 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/static/netbox_data_import/js/preview_row_controls.js +126 -4
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/static/netbox_data_import/js/sync_row_modal.js +149 -54
- netbox_data_import-2.5.0/netbox_data_import/static/netbox_data_import/js/trace_device_picker.js +130 -0
- netbox_data_import-2.5.0/netbox_data_import/static/netbox_data_import/js/trace_picker_search.js +54 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/static/netbox_data_import/js/trace_termination_picker.js +16 -37
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/target_modules.py +265 -84
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/_proposal_card.html +9 -9
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/import_preview.html +71 -6
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/import_profile_yaml.html +1 -1
- netbox_data_import-2.5.0/netbox_data_import/templates/netbox_data_import/importprofile_edit.html +45 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/inferencebackend.html +20 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/trace_workspace.html +218 -39
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/termination_proposal.py +14 -6
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/helpers.py +40 -5
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/inference_http.py +7 -2
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/mock_discipline.py +2 -1
- netbox_data_import-2.5.0/netbox_data_import/tests/redaction.py +54 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_adapter_config_migration.py +2 -2
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_atomic_exits.py +7 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_cable_module.py +75 -6
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_ci_configuration.py +14 -28
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_comment_style.py +2 -2
- netbox_data_import-2.5.0/netbox_data_import/tests/test_configuration_docs.py +147 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_configuration_surfaces.py +317 -0
- netbox_data_import-2.5.0/netbox_data_import/tests/test_conflict_row_actions.py +241 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_contact_sync.py +4 -45
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_cutover_edge_contracts.py +10 -60
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_device_module.py +190 -15
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_digest_indexed_models.py +18 -1
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_execution_audit.py +1 -1
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_import_cutover.py +194 -23
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_import_engine.py +109 -2
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_import_execution.py +105 -8
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_import_source_migration.py +2 -2
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_inference_adapter.py +323 -4
- netbox_data_import-2.5.0/netbox_data_import/tests/test_inference_connection_test.py +743 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_inference_credentials.py +80 -50
- netbox_data_import-2.5.0/netbox_data_import/tests/test_inference_secret_containment.py +338 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_inference_settings.py +6 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_inference_trust.py +228 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_installation_docs.py +1 -1
- netbox_data_import-2.5.0/netbox_data_import/tests/test_markdown_tables.py +124 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_migrations.py +2 -1
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_mock_discipline.py +21 -5
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_module_boundaries.py +76 -12
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_object_permission_writer.py +609 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_placement_reference_locks.py +1 -1
- netbox_data_import-2.5.0/netbox_data_import/tests/test_policy_write_refusals.py +155 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_preview_assets.py +483 -10
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_pristine_preview_rows.py +3 -19
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_proposal_jobs.py +82 -4
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_proposal_response.py +36 -16
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_proposal_tasks.py +12 -2
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_proposal_workspace.py +20 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_quick_action_input_bounds.py +21 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_rack_module.py +53 -1
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_release_metadata.py +3 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_resolution_proposal_api.py +4 -3
- netbox_data_import-2.5.0/netbox_data_import/tests/test_retire_superseded_proposals_migration.py +173 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_review_actions.py +14 -0
- netbox_data_import-2.5.0/netbox_data_import/tests/test_source_trace_contract.py +66 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_standard_views.py +23 -1
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_target_field_catalog.py +1 -1
- netbox_data_import-2.5.0/netbox_data_import/tests/test_target_module_edge_contracts.py +1069 -0
- netbox_data_import-2.5.0/netbox_data_import/tests/test_text_encoding.py +116 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_trace_decisions.py +1 -1
- netbox_data_import-2.5.0/netbox_data_import/tests/test_trace_device_resolution.py +995 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_trace_workbook.py +0 -29
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_trace_workspace.py +223 -15
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_views.py +63 -5
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_views_coverage2.py +11 -8
- netbox_data_import-2.5.0/netbox_data_import/trace_device_resolution.py +482 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/trace_workbook.py +19 -104
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/urls.py +11 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/views.py +541 -99
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import.egg-info/PKG-INFO +3 -3
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import.egg-info/SOURCES.txt +19 -2
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/pyproject.toml +18 -6
- netbox_data_import-2.3.0/netbox_data_import/inference_connection_test.py +0 -73
- netbox_data_import-2.3.0/netbox_data_import/inference_transport.py +0 -130
- netbox_data_import-2.3.0/netbox_data_import/object_permissions.py +0 -340
- netbox_data_import-2.3.0/netbox_data_import/tests/test_inference_connection_test.py +0 -452
- netbox_data_import-2.3.0/netbox_data_import/tests/test_inference_secret_containment.py +0 -216
- netbox_data_import-2.3.0/netbox_data_import/tests/test_legacy_profile_yaml.py +0 -171
- netbox_data_import-2.3.0/netbox_data_import/tests/test_target_module_edge_contracts.py +0 -449
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/LICENSE +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/adapter_config.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/api/__init__.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/api/serializers.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/api/urls.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/api/views.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/contact_resolution.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/device_identity.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/field_keys.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/filters.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/flat_workbook.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/graphql/__init__.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/graphql/filters.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/graphql/schema.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/graphql/types.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0001_initial.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0002_importjob.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0003_remove_classrolemapping_unique_class_role_mapping_per_profile_and_more.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0004_features.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0005_deviceexistingmatch.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0006_deviceexistingmatch_asset_tag.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0007_manufacturermapping.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0008_unique_constraints.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0009_add_rack_type_to_classrolemapping.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0010_alter_manufacturermapping_id.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0011_importprofile_capture_extra_data.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0012_classrolemapping_rack_type_related_name.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0013_remove_columnmapping_unique_target.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0014_alter_columnmapping_target_field_and_more.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0015_cleanup_duplicate_device_matches.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0016_deviceexistingmatch_ndi_devicematch_profile_device.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0017_native_contact_sync.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0018_ignoredfielddifference.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0019_deviceimportsource.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0020_migrate_import_source_custom_field.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0021_importprofile_adapter_config.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0022_migrate_profile_adapter_config.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0023_drop_moved_profile_columns.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0024_rename_importjob_importexecution.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0025_remove_importexecution_dry_run_and_more.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0026_alter_sourcedocument_profile.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0027_cableclassmapping_terminationresolution.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0028_cableimportsource.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0029_alter_cableimportsource_from_text_and_more.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0030_remove_device_type_creation_config.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0031_inferencebackend.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0032_inference_timeout_floor.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/0033_resolutionproposal.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/migrations/__init__.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/navigation.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/plan.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/preview_row_actions.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/proposal_decisions.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/proposal_presentation.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/proposal_tasks.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/resolution_proposals.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/signals.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/source_resolution.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/static/netbox_data_import/js/split_name_modal.js +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/static/netbox_data_import/js/trace_proposals.js +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tables.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/target_runtime.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/template_content.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/_import_progress.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/_proposal_action.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/analysis.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/bulk_yaml_import.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/cableclassmapping_edit.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/classrolemapping_edit.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/columnmapping_edit.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/columntransformrule_edit.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/device_import_data.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/devicetypemapping_edit.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/import_progress.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/import_results.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/import_setup.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/importexecution_list.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/importprofile.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/importprofile_list.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/templates/netbox_data_import/source_resolution_list.html +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/__init__.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/conftest.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/fixtures/combined_name.xlsx +0 -0
- /netbox_data_import-2.3.0/netbox_data_import/tests/fixtures/sample_cans.xlsx → /netbox_data_import-2.5.0/netbox_data_import/tests/fixtures/sample_workbook.xlsx +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/fixtures/trace_copper.xlsx +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/fixtures/trace_fiber.xlsx +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/isolated_settings.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/mixins.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/netbox_configuration.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/parallel.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_adapter_config.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_api.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_contact_resolution_ajax.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_contact_role_suggestion.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_devcontainer_e2e.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_graphql_filters.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_import_plan.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_inference_backend.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_ip_target.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_misc_coverage.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_model_base_conventions.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_models.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_navigation.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_netbox_reader.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_parallel_test_setup.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_preview_state_ownership.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_profile_policy_crud_scoping.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_proposal_decisions.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_quick_action_object_permissions.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_quick_action_profile_id.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_remove_device_type_creation_config_migration.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_resolution_proposals.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_review_resolutions.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_review_workspace.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_serial_display.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_source_adapter.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/tests/test_views_extra.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/trace_schema.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/transform_regex.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/utils.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/values.py +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import.egg-info/dependency_links.txt +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import.egg-info/requires.txt +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import.egg-info/top_level.txt +0 -0
- {netbox_data_import-2.3.0 → netbox_data_import-2.5.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.5.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,
|
|
@@ -13,13 +13,14 @@ the NetBox boundary.
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
-
from dataclasses import dataclass, field
|
|
16
|
+
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
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
|
-
from .
|
|
23
|
+
from .source_trace import SourceTrace
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class UnknownSourceAdapter(Exception):
|
|
@@ -39,6 +40,16 @@ class SourceDiagnostic:
|
|
|
39
40
|
row_number: int | None = None
|
|
40
41
|
|
|
41
42
|
|
|
43
|
+
def _foreign_row_number(row) -> int | None:
|
|
44
|
+
"""Return the source row a foreign row states, so its diagnostic can name a location."""
|
|
45
|
+
provenance = getattr(row, "provenance", ())
|
|
46
|
+
if provenance:
|
|
47
|
+
return provenance[0].row_start
|
|
48
|
+
if isinstance(row, dict):
|
|
49
|
+
return row.get("_row_number")
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
42
53
|
@dataclass(frozen=True)
|
|
43
54
|
class SourceBatch:
|
|
44
55
|
"""The typed source items and source diagnostics from one file (section 1)."""
|
|
@@ -48,6 +59,46 @@ class SourceBatch:
|
|
|
48
59
|
diagnostics: tuple[SourceDiagnostic, ...] = ()
|
|
49
60
|
unused_columns: dict[str, dict] = field(default_factory=dict)
|
|
50
61
|
|
|
62
|
+
def __post_init__(self):
|
|
63
|
+
"""Exclude a row the output kinds cannot carry, and mark a Trace with empty Device references."""
|
|
64
|
+
from .source_trace import SourceTrace
|
|
65
|
+
|
|
66
|
+
trace_batch = OutputKind.SOURCE_TRACE in self.output_kinds
|
|
67
|
+
expected = SourceTrace if trace_batch else dict
|
|
68
|
+
carries = "Source Traces" if trace_batch else "source rows"
|
|
69
|
+
rows = []
|
|
70
|
+
diagnostics = list(self.diagnostics)
|
|
71
|
+
for row in self.rows:
|
|
72
|
+
if not isinstance(row, expected):
|
|
73
|
+
# Excluded here, because every reader below reads the row the output kinds declare.
|
|
74
|
+
diagnostics.append(
|
|
75
|
+
SourceDiagnostic(
|
|
76
|
+
code="source.row_type_unexpected",
|
|
77
|
+
message=f"This batch carries {carries} only; got {type(row).__name__}.",
|
|
78
|
+
row_number=_foreign_row_number(row),
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
continue
|
|
82
|
+
if not trace_batch:
|
|
83
|
+
rows.append(row)
|
|
84
|
+
continue
|
|
85
|
+
summary = row.endpoint_summary
|
|
86
|
+
references = [summary.from_termination, summary.to_termination, *row.corroboration]
|
|
87
|
+
for segment in row.segments:
|
|
88
|
+
references.extend((segment.left, segment.right))
|
|
89
|
+
if all(identity_text(reference.device) for reference in references):
|
|
90
|
+
rows.append(row)
|
|
91
|
+
continue
|
|
92
|
+
diagnostic = SourceDiagnostic(
|
|
93
|
+
code="trace.device_required",
|
|
94
|
+
message="A Source Trace Termination Reference must name a Device.",
|
|
95
|
+
row_number=row.provenance[0].row_start,
|
|
96
|
+
)
|
|
97
|
+
diagnostics.append(diagnostic)
|
|
98
|
+
rows.append(replace(row, errors=(*row.errors, diagnostic)))
|
|
99
|
+
object.__setattr__(self, "rows", tuple(rows))
|
|
100
|
+
object.__setattr__(self, "diagnostics", tuple(diagnostics))
|
|
101
|
+
|
|
51
102
|
|
|
52
103
|
class SourceAdapter:
|
|
53
104
|
"""Base class for a Source Adapter declaration."""
|
|
@@ -36,6 +36,13 @@ from .field_keys import (
|
|
|
36
36
|
from .object_permissions import enforce_saved_object_permission
|
|
37
37
|
from .plan import Diagnostic, Disposition, PlannedChange, Severity, SynchronizationUnit
|
|
38
38
|
from .target_runtime import DeletedObject, PreconditionFailed
|
|
39
|
+
from .trace_device_resolution import (
|
|
40
|
+
STALE as DEVICE_STALE,
|
|
41
|
+
collect_trace_device_evidence,
|
|
42
|
+
resolve_trace_devices,
|
|
43
|
+
resolved_trace_device,
|
|
44
|
+
source_device_key,
|
|
45
|
+
)
|
|
39
46
|
from .values import identity_text, source_text
|
|
40
47
|
|
|
41
48
|
CABLE_STATUS = "connected"
|
|
@@ -111,6 +118,7 @@ class _Termination:
|
|
|
111
118
|
kind: str
|
|
112
119
|
device_id: int
|
|
113
120
|
display: str
|
|
121
|
+
topology_display: str
|
|
114
122
|
|
|
115
123
|
@property
|
|
116
124
|
def key(self) -> tuple[str, int]:
|
|
@@ -207,7 +215,9 @@ class _TraceAnalysis:
|
|
|
207
215
|
segments: list = field(default_factory=list)
|
|
208
216
|
proven: dict = field(default_factory=dict)
|
|
209
217
|
policies: dict = field(default_factory=dict)
|
|
218
|
+
devices: dict = field(default_factory=dict)
|
|
210
219
|
terminations: dict = field(default_factory=dict)
|
|
220
|
+
resolution_started: bool = False
|
|
211
221
|
topology_read: bool = False
|
|
212
222
|
logical_cable: Any = None
|
|
213
223
|
retains_logical_cable: bool = False
|
|
@@ -303,38 +313,19 @@ def _source_record(trace, segment_index: int) -> dict:
|
|
|
303
313
|
}
|
|
304
314
|
|
|
305
315
|
|
|
306
|
-
def
|
|
307
|
-
"""Return the visible Devices at the import target, grouped by comparison name."""
|
|
308
|
-
from django.db.models import Q
|
|
309
|
-
|
|
310
|
-
wanted = {identity_text(name) for name in names if identity_text(name)}
|
|
311
|
-
if not wanted:
|
|
312
|
-
return {}
|
|
313
|
-
devices = netbox_reader.devices()
|
|
314
|
-
if netbox_reader.site is not None:
|
|
315
|
-
devices = devices.filter(site=netbox_reader.site)
|
|
316
|
-
if _lock_rows:
|
|
317
|
-
devices = devices.order_by("pk").select_for_update(of=("self",))
|
|
318
|
-
lookup = Q()
|
|
319
|
-
for name in sorted(wanted):
|
|
320
|
-
lookup |= Q(name__iexact=name)
|
|
321
|
-
grouped: dict[str, list] = {}
|
|
322
|
-
for device in devices.filter(lookup):
|
|
323
|
-
comparison = identity_text(device.name)
|
|
324
|
-
if comparison in wanted:
|
|
325
|
-
grouped.setdefault(comparison, []).append(device)
|
|
326
|
-
return grouped
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def resolved_device_for(field_key: str, netbox_reader, *, _lock_rows=False):
|
|
316
|
+
def resolved_device_for(field_key: str, netbox_reader, *, profile, _lock_rows=False):
|
|
330
317
|
"""Return the one visible Device a termination field key names, or None when it names no single one.
|
|
331
318
|
|
|
332
319
|
A proposal freezes this reference and revalidates it, so the retrieval and the freshness check
|
|
333
320
|
have to agree on what "the resolved Device" means.
|
|
334
321
|
"""
|
|
335
322
|
device_name = parse_termination_field_key(field_key)["device"]
|
|
336
|
-
|
|
337
|
-
|
|
323
|
+
return resolved_trace_device(
|
|
324
|
+
profile=profile,
|
|
325
|
+
reader=netbox_reader,
|
|
326
|
+
source_label=device_name,
|
|
327
|
+
lock_rows=_lock_rows,
|
|
328
|
+
)
|
|
338
329
|
|
|
339
330
|
|
|
340
331
|
_RESOLVED_DEVICE_UNSET = object()
|
|
@@ -361,7 +352,7 @@ def eligible_terminations(
|
|
|
361
352
|
kind = parsed["kind"]
|
|
362
353
|
accessor = _READER_ACCESSOR_BY_KIND[kind]
|
|
363
354
|
device = (
|
|
364
|
-
resolved_device_for(field_key, netbox_reader, _lock_rows=_lock_rows)
|
|
355
|
+
resolved_device_for(field_key, netbox_reader, profile=profile, _lock_rows=_lock_rows)
|
|
365
356
|
if _resolved_device is _RESOLVED_DEVICE_UNSET
|
|
366
357
|
else _resolved_device
|
|
367
358
|
)
|
|
@@ -430,6 +421,7 @@ class _CableBatch:
|
|
|
430
421
|
"""Plan every Source Trace in one batch, so an identical shared segment plans once."""
|
|
431
422
|
|
|
432
423
|
def __init__(self, traces, profile, netbox_reader, *, lock_plan_references: bool = False):
|
|
424
|
+
traces = tuple(traces)
|
|
433
425
|
self.profile = profile
|
|
434
426
|
self.reader = netbox_reader
|
|
435
427
|
self.actor = netbox_reader.actor
|
|
@@ -445,7 +437,19 @@ class _CableBatch:
|
|
|
445
437
|
self._occupied: dict[tuple[str, int], _ExistingCable] = {}
|
|
446
438
|
self._mapping_rows: dict[str, Any] | None = None
|
|
447
439
|
self._stored = self._stored_resolutions()
|
|
448
|
-
|
|
440
|
+
active_traces = (analysis.trace for analysis in self.analyses if not analysis.stopped)
|
|
441
|
+
self._device_evidence = collect_trace_device_evidence(active_traces)
|
|
442
|
+
self._device_resolutions = resolve_trace_devices(
|
|
443
|
+
profile=self.profile,
|
|
444
|
+
reader=self.reader,
|
|
445
|
+
evidence=self._device_evidence,
|
|
446
|
+
lock_rows=self.lock_plan_references,
|
|
447
|
+
)
|
|
448
|
+
self._device_displays = {
|
|
449
|
+
resolution.device.pk: str(resolution.device)
|
|
450
|
+
for resolution in self._device_resolutions.values()
|
|
451
|
+
if resolution.device is not None
|
|
452
|
+
}
|
|
449
453
|
self._resolve_terminations()
|
|
450
454
|
self._load_mappings()
|
|
451
455
|
self._build_segments()
|
|
@@ -512,16 +516,6 @@ class _CableBatch:
|
|
|
512
516
|
).select_related("selected_object_type")
|
|
513
517
|
return {row.field_key: row for row in rows}
|
|
514
518
|
|
|
515
|
-
def _load_devices(self) -> dict[str, list]:
|
|
516
|
-
"""Return the visible Devices every planned trace names, grouped by comparison name."""
|
|
517
|
-
names = [
|
|
518
|
-
reference.device
|
|
519
|
-
for analysis in self.analyses
|
|
520
|
-
if not analysis.stopped
|
|
521
|
-
for reference in self._references(analysis.trace)
|
|
522
|
-
]
|
|
523
|
-
return _visible_devices(self.reader, names)
|
|
524
|
-
|
|
525
519
|
def _components_for(self, device_id: int, kind: str) -> dict[str, list]:
|
|
526
520
|
"""Return one Device's terminations of one kind, grouped by comparison name."""
|
|
527
521
|
cached = self._components.get((device_id, kind))
|
|
@@ -537,6 +531,7 @@ class _CableBatch:
|
|
|
537
531
|
for analysis in self.analyses:
|
|
538
532
|
if analysis.stopped:
|
|
539
533
|
continue
|
|
534
|
+
analysis.resolution_started = True
|
|
540
535
|
resolved: dict[tuple, _Termination] = {}
|
|
541
536
|
for reference in self._references(analysis.trace):
|
|
542
537
|
if reference.identity_key in resolved:
|
|
@@ -548,19 +543,24 @@ class _CableBatch:
|
|
|
548
543
|
|
|
549
544
|
def _resolve_one(self, analysis: _TraceAnalysis, reference) -> _Termination | None:
|
|
550
545
|
"""Return the NetBox object one Termination Reference names, or record the open decision."""
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
546
|
+
resolution = self._device_resolutions[source_device_key(reference.device)]
|
|
547
|
+
analysis.devices.setdefault(resolution.evidence.key, resolution.to_question())
|
|
548
|
+
if resolution.device is None:
|
|
549
|
+
code = "trace.device_resolution_stale" if resolution.state == DEVICE_STALE else "trace.device_unresolved"
|
|
550
|
+
analysis.block(
|
|
551
|
+
code,
|
|
552
|
+
{**_reference_display(reference), "matches": resolution.exact_match_count},
|
|
553
|
+
)
|
|
554
554
|
# With no resolved Device there is nothing to pick from, so the picker cannot help here.
|
|
555
555
|
self._record_resolution(
|
|
556
556
|
analysis,
|
|
557
557
|
reference,
|
|
558
558
|
UNRESOLVED,
|
|
559
559
|
None,
|
|
560
|
-
reason=
|
|
560
|
+
reason="Resolve the source Device before choosing one of its ports.",
|
|
561
561
|
)
|
|
562
562
|
return None
|
|
563
|
-
device =
|
|
563
|
+
device = resolution.device
|
|
564
564
|
stored = self._stored.get(_field_key(reference))
|
|
565
565
|
if stored is not None:
|
|
566
566
|
termination = self._stored_termination(analysis, reference, device, stored)
|
|
@@ -653,6 +653,7 @@ class _CableBatch:
|
|
|
653
653
|
kind=_KIND_BY_MODEL_NAME[label.partition(".")[2]],
|
|
654
654
|
device_id=component.device_id,
|
|
655
655
|
display=str(component),
|
|
656
|
+
topology_display=f"{self._device_displays[component.device_id]} {component.name}",
|
|
656
657
|
)
|
|
657
658
|
|
|
658
659
|
def _load_mappings(self) -> None:
|
|
@@ -1197,8 +1198,8 @@ class _CableBatch:
|
|
|
1197
1198
|
for index, stated in enumerate(stated_segments):
|
|
1198
1199
|
planned = resolved.get(index)
|
|
1199
1200
|
source_left, source_right = _endpoint_label(stated.left), _endpoint_label(stated.right)
|
|
1200
|
-
left = source_left if planned is None else planned.left.
|
|
1201
|
-
right = source_right if planned is None else planned.right.
|
|
1201
|
+
left = source_left if planned is None else planned.left.topology_display
|
|
1202
|
+
right = source_right if planned is None else planned.right.topology_display
|
|
1202
1203
|
segments.append(
|
|
1203
1204
|
{
|
|
1204
1205
|
"index": index,
|
|
@@ -1222,9 +1223,11 @@ class _CableBatch:
|
|
|
1222
1223
|
},
|
|
1223
1224
|
"segments": segments,
|
|
1224
1225
|
"logical_cable": self._logical_cable_display(analysis),
|
|
1226
|
+
"resolution_started": analysis.resolution_started,
|
|
1225
1227
|
"topology_known": analysis.topology_read,
|
|
1226
1228
|
# A unit with no changes proposes nothing, so the panel must not offer to delete one.
|
|
1227
1229
|
"deletes_logical_cable": writes and analysis.deleted_logical_cable is not None,
|
|
1230
|
+
"devices": list(analysis.devices.values()),
|
|
1228
1231
|
"terminations": list(analysis.terminations.values()),
|
|
1229
1232
|
}
|
|
1230
1233
|
|
|
@@ -259,6 +259,7 @@ POLICY_SECTIONS: tuple[PolicySection, ...] = (
|
|
|
259
259
|
PolicySection("source_resolutions", "Source Resolutions", _DEVICE_ONLY),
|
|
260
260
|
PolicySection("device_existing_matches", "Device Existing Matches", _DEVICE_ONLY),
|
|
261
261
|
PolicySection("ignored_field_differences", "Ignored Field Differences", _DEVICE_ONLY),
|
|
262
|
+
PolicySection("trace_device_resolutions", "Trace Device Resolutions", _TRACE_ONLY),
|
|
262
263
|
PolicySection("termination_resolutions", "Termination Resolutions", _TRACE_ONLY),
|
|
263
264
|
PolicySection("cable_class_mappings", "CableClass Mappings", _TRACE_ONLY),
|
|
264
265
|
)
|
{netbox_data_import-2.3.0 → netbox_data_import-2.5.0}/netbox_data_import/device_field_review.py
RENAMED
|
@@ -205,6 +205,7 @@ class DeviceFieldReview:
|
|
|
205
205
|
differing: dict[str, dict[str, str]]
|
|
206
206
|
ignored: dict[str, dict[str, str]]
|
|
207
207
|
informational: dict[str, dict[str, str]]
|
|
208
|
+
matching: dict[str, dict[str, str]]
|
|
208
209
|
effective_proposal: dict[str, Any]
|
|
209
210
|
snapshots: dict[str, tuple[dict[str, str], dict[str, str]]]
|
|
210
211
|
|
|
@@ -272,7 +273,7 @@ class DeviceFieldReviewer:
|
|
|
272
273
|
display_overrides: Mapping[str, str] | None = None,
|
|
273
274
|
):
|
|
274
275
|
"""Return the writable differences and the reported-only ones as two maps."""
|
|
275
|
-
differing, informational, _ = DeviceFieldReviewer._compare(
|
|
276
|
+
differing, informational, _, _ = DeviceFieldReviewer._compare(
|
|
276
277
|
matched_device,
|
|
277
278
|
proposal,
|
|
278
279
|
display_overrides=display_overrides,
|
|
@@ -306,7 +307,7 @@ class DeviceFieldReviewer:
|
|
|
306
307
|
display_overrides: Mapping[str, str] | None = None,
|
|
307
308
|
) -> DeviceFieldReview:
|
|
308
309
|
"""Return differing, ignored, and write-safe values for one Device."""
|
|
309
|
-
differing, informational, snapshots = self._compare(
|
|
310
|
+
differing, informational, matching, snapshots = self._compare(
|
|
310
311
|
matched_device,
|
|
311
312
|
proposal,
|
|
312
313
|
display_overrides=display_overrides,
|
|
@@ -333,6 +334,7 @@ class DeviceFieldReviewer:
|
|
|
333
334
|
differing=differing,
|
|
334
335
|
ignored=ignored,
|
|
335
336
|
informational=informational,
|
|
337
|
+
matching=matching,
|
|
336
338
|
effective_proposal=effective,
|
|
337
339
|
snapshots=snapshots,
|
|
338
340
|
)
|
|
@@ -348,6 +350,7 @@ class DeviceFieldReviewer:
|
|
|
348
350
|
display_overrides = display_overrides or {}
|
|
349
351
|
differing: dict[str, dict[str, str]] = {}
|
|
350
352
|
informational: dict[str, dict[str, str]] = {}
|
|
353
|
+
matching: dict[str, dict[str, str]] = {}
|
|
351
354
|
snapshots: dict[str, tuple[dict[str, str], dict[str, str]]] = {}
|
|
352
355
|
for definition in _FIELD_DEFINITIONS:
|
|
353
356
|
if definition.target_field not in proposal or not definition.provided(proposal[definition.target_field]):
|
|
@@ -363,6 +366,11 @@ class DeviceFieldReviewer:
|
|
|
363
366
|
if definition.target_field == "rack_name":
|
|
364
367
|
_scope_rack_canonical(netbox_snapshot, _device_rack_location_id(matched_device))
|
|
365
368
|
if file_snapshot["canonical"] == netbox_snapshot["canonical"]:
|
|
369
|
+
# Recorded, not dropped: a sync has to be able to say what it leaves alone.
|
|
370
|
+
matching[definition.target_field] = {
|
|
371
|
+
"netbox": netbox_snapshot["display"],
|
|
372
|
+
"file": file_snapshot["display"],
|
|
373
|
+
}
|
|
366
374
|
continue
|
|
367
375
|
snapshots[definition.target_field] = (file_snapshot, netbox_snapshot)
|
|
368
376
|
values = {"netbox": netbox_snapshot["display"], "file": file_snapshot["display"]}
|
|
@@ -370,4 +378,38 @@ class DeviceFieldReviewer:
|
|
|
370
378
|
differing[definition.target_field] = values
|
|
371
379
|
else:
|
|
372
380
|
informational[definition.target_field] = values
|
|
373
|
-
return differing, informational, snapshots
|
|
381
|
+
return differing, informational, matching, snapshots
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
#: What a sync would do to one field. The preview and the sync modal read these same names.
|
|
385
|
+
SYNC_STATE_CHANGE = "change"
|
|
386
|
+
SYNC_STATE_UNCHANGED = "unchanged"
|
|
387
|
+
SYNC_STATE_IGNORED = "ignored"
|
|
388
|
+
SYNC_STATE_NOT_WRITTEN = "not_written"
|
|
389
|
+
|
|
390
|
+
#: The review bucket each state is built from, in the order the operator reads them.
|
|
391
|
+
_SYNC_STATE_BUCKETS: tuple[tuple[str, str], ...] = (
|
|
392
|
+
(SYNC_STATE_CHANGE, "field_diff"),
|
|
393
|
+
(SYNC_STATE_UNCHANGED, "field_matching"),
|
|
394
|
+
(SYNC_STATE_IGNORED, "field_ignored"),
|
|
395
|
+
(SYNC_STATE_NOT_WRITTEN, "field_informational"),
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def sync_change_preview(extra_data: Mapping[str, Any], labels: Mapping[str, str]) -> list[dict[str, str]]:
|
|
400
|
+
"""Return one matched row's reviewed fields, each labelled with what a sync would do to it."""
|
|
401
|
+
entries = []
|
|
402
|
+
for state, bucket in _SYNC_STATE_BUCKETS:
|
|
403
|
+
values = extra_data.get(bucket) or {}
|
|
404
|
+
for target_field in sorted(values):
|
|
405
|
+
pair = values[target_field]
|
|
406
|
+
entries.append(
|
|
407
|
+
{
|
|
408
|
+
"field": target_field,
|
|
409
|
+
"label": labels.get(target_field) or target_field,
|
|
410
|
+
"netbox": pair.get("netbox", ""),
|
|
411
|
+
"file": pair.get("file", ""),
|
|
412
|
+
"state": state,
|
|
413
|
+
}
|
|
414
|
+
)
|
|
415
|
+
return entries
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import copy
|
|
4
4
|
|
|
5
5
|
from django import forms
|
|
6
|
+
from django.utils.html import format_html
|
|
6
7
|
from dcim.models import Site, Location
|
|
7
8
|
from tenancy.models import Tenant
|
|
8
9
|
from netbox.forms import NetBoxModelBulkEditForm, NetBoxModelForm, NetBoxModelImportForm
|
|
@@ -25,6 +26,24 @@ from .models import (
|
|
|
25
26
|
|
|
26
27
|
_EXPLICIT_NONE = "__explicit_none__"
|
|
27
28
|
|
|
29
|
+
_CREDENTIAL_REFERENCE_EXAMPLE = """{
|
|
30
|
+
"backend": "vault_kv_v2",
|
|
31
|
+
"mount": "secret",
|
|
32
|
+
"path": "inference/backend",
|
|
33
|
+
"field": "api_key"
|
|
34
|
+
}"""
|
|
35
|
+
|
|
36
|
+
_CREDENTIAL_REFERENCE_HELP = format_html(
|
|
37
|
+
"This JSON tells the plugin where to find the API key in Vault. It does not contain the API key. "
|
|
38
|
+
"Configure Vault first in the NetBox plugin settings at "
|
|
39
|
+
"<code>PLUGINS_CONFIG["netbox_data_import"]["vault"]</code>. "
|
|
40
|
+
'<details class="mt-1" data-credential-reference-help>'
|
|
41
|
+
"<summary>Show the required JSON shape</summary>"
|
|
42
|
+
'<pre class="mt-2 mb-0"><code>{}</code></pre>'
|
|
43
|
+
"</details>",
|
|
44
|
+
_CREDENTIAL_REFERENCE_EXAMPLE,
|
|
45
|
+
)
|
|
46
|
+
|
|
28
47
|
|
|
29
48
|
class _RuntimeCableChoiceField(forms.ChoiceField):
|
|
30
49
|
"""Render only current choices while letting shared validation classify submitted values."""
|
|
@@ -132,7 +151,7 @@ class ImportProfileForm(NetBoxModelForm):
|
|
|
132
151
|
return self.instance.source_adapter
|
|
133
152
|
if self.is_bound:
|
|
134
153
|
return self.data.get(self.add_prefix("source_adapter")) or self.instance.source_adapter
|
|
135
|
-
return self.instance.source_adapter
|
|
154
|
+
return self.initial.get("source_adapter") or self.instance.source_adapter
|
|
136
155
|
|
|
137
156
|
def clean(self):
|
|
138
157
|
"""Collect the adapter-declared fields into ``adapter_config``."""
|
|
@@ -306,8 +325,10 @@ class InferenceBackendForm(NetBoxModelForm):
|
|
|
306
325
|
"tags",
|
|
307
326
|
)
|
|
308
327
|
help_texts = {
|
|
309
|
-
"credential_reference":
|
|
310
|
-
|
|
328
|
+
"credential_reference": _CREDENTIAL_REFERENCE_HELP,
|
|
329
|
+
"model": (
|
|
330
|
+
"Enter the exact model id. After you save the backend, run the connection test to discover "
|
|
331
|
+
"model suggestions when the endpoint supports them."
|
|
311
332
|
),
|
|
312
333
|
}
|
|
313
334
|
|
|
@@ -18,7 +18,7 @@ from django.db import DatabaseError
|
|
|
18
18
|
from . import adapter_config, adapters, catalog, target_modules
|
|
19
19
|
from .models import FailureReason, ImportExecution, SourceDocument, locked_profile_policy
|
|
20
20
|
from .netbox_reader import NetBoxReader, PlanningTargetUnavailable
|
|
21
|
-
from .object_permissions import ObjectPermissionDenied
|
|
21
|
+
from .object_permissions import ObjectPermissionDenied, clear_user_permission_caches
|
|
22
22
|
from .plan import Diagnostic, Disposition, ImportPlan, PlanInvalid, Severity, executable_units, merge_changes
|
|
23
23
|
from .source_resolution import derive_effective_rows
|
|
24
24
|
from .target_runtime import DeletedObject, ExecutionContext, PreconditionFailed
|
|
@@ -225,6 +225,7 @@ class ImportEngine:
|
|
|
225
225
|
progress_callback,
|
|
226
226
|
) -> None:
|
|
227
227
|
"""Compare the selection against a fresh plan and apply it, inside the caller's transaction."""
|
|
228
|
+
clear_user_permission_caches(actor)
|
|
228
229
|
current = cls.plan(
|
|
229
230
|
profile,
|
|
230
231
|
source_document,
|
|
@@ -253,6 +254,7 @@ class ImportEngine:
|
|
|
253
254
|
if runtime is None:
|
|
254
255
|
raise EngineConfigurationError(f"No Target Module runtime is registered for '{change.target_module}'.")
|
|
255
256
|
try:
|
|
257
|
+
clear_user_permission_caches(actor)
|
|
256
258
|
applied = runtime.apply(change, context)
|
|
257
259
|
except (PreconditionFailed, ObjectPermissionDenied, ValidationError, DatabaseError) as exc:
|
|
258
260
|
raise _ExecutionFailed(
|