netbox-data-import 2.6.0__tar.gz → 2.7.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.6.0 → netbox_data_import-2.7.0}/PKG-INFO +5 -4
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/README.md +3 -3
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/__init__.py +7 -4
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/api/views.py +3 -0
- netbox_data_import-2.7.0/netbox_data_import/branching.py +236 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/contact_resolution.py +3 -0
- netbox_data_import-2.7.0/netbox_data_import/filters.py +29 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/forms.py +31 -3
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/schema.py +18 -4
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/import_engine.py +2 -1
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/ip_assignment.py +2 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/jobs.py +77 -34
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0015_cleanup_duplicate_device_matches.py +3 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0020_migrate_import_source_custom_field.py +3 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0022_migrate_profile_adapter_config.py +3 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0030_remove_device_type_creation_config.py +3 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0035_retire_superseded_proposals.py +3 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0038_cable_tag_integrity.py +3 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0039_remove_job_plan_copies.py +3 -0
- netbox_data_import-2.7.0/netbox_data_import/migrations/0040_move_tags_to_tagged_items.py +55 -0
- netbox_data_import-2.7.0/netbox_data_import/migrations/0041_remove_importprofile_tags_and_more.py +32 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/models.py +5 -11
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/object_permissions.py +2 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/plan.py +7 -2
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/profile_yaml.py +2 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_decisions.py +3 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tables.py +4 -1
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/target_modules.py +17 -6
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/template_content.py +4 -0
- netbox_data_import-2.7.0/netbox_data_import/templates/netbox_data_import/branch_refused.html +13 -0
- netbox_data_import-2.7.0/netbox_data_import/templates/netbox_data_import/branch_refused_anonymous.html +16 -0
- netbox_data_import-2.7.0/netbox_data_import/templates/netbox_data_import/device_import_data_main_only.html +14 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_preview.html +1 -1
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importprofile.html +1 -0
- netbox_data_import-2.7.0/netbox_data_import/templates/netbox_data_import/inc/branch_refusal_card.html +12 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/inferencebackend.html +1 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/conftest.py +57 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/helpers.py +63 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/netbox_configuration.py +17 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/plugins_config.py +45 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/snapshot_guard.py +126 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_atomic_exits.py +6 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/test_branching.py +438 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/test_branching_absent.py +25 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/test_branching_refusal.py +479 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_configuration_surfaces.py +20 -2
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_device_module.py +14 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/test_import_change_log.py +224 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_cutover.py +103 -1
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_backend.py +21 -18
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_connection_test.py +68 -38
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_secret_containment.py +13 -20
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_settings.py +7 -6
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_job_plan_cleanup_migration.py +6 -1
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_migrations.py +114 -6
- netbox_data_import-2.7.0/netbox_data_import/tests/test_plugins_config.py +42 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_decisions.py +3 -2
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_jobs.py +4 -3
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_workspace.py +35 -42
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_release_metadata.py +49 -1
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_review_actions.py +18 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/test_snapshot_guard.py +170 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_standard_views.py +3 -4
- netbox_data_import-2.7.0/netbox_data_import/tests/test_tag_assignments.py +239 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/test_tags_migration.py +119 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_views.py +139 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/views.py +55 -20
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/PKG-INFO +5 -4
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/SOURCES.txt +17 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/requires.txt +1 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/pyproject.toml +8 -1
- netbox_data_import-2.6.0/netbox_data_import/filters.py +0 -19
- netbox_data_import-2.6.0/netbox_data_import/tests/conftest.py +0 -25
- netbox_data_import-2.6.0/netbox_data_import/tests/netbox_configuration.py +0 -15
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/LICENSE +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/_dns_worker.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/adapter_config.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/adapter_forms.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/adapters.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/api/__init__.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/api/serializers.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/api/urls.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/cable_disclosure.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/cable_policy.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/cable_target.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/catalog.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/device_field_review.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/device_identity.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/field_keys.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/flat_workbook.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/__init__.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/filters.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/types.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_adapter.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_backend.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_connection_test.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_credentials.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_settings.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_transport.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_trust.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0001_initial.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0002_importjob.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0003_remove_classrolemapping_unique_class_role_mapping_per_profile_and_more.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0004_features.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0005_deviceexistingmatch.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0006_deviceexistingmatch_asset_tag.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0007_manufacturermapping.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0008_unique_constraints.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0009_add_rack_type_to_classrolemapping.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0010_alter_manufacturermapping_id.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0011_importprofile_capture_extra_data.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0012_classrolemapping_rack_type_related_name.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0013_remove_columnmapping_unique_target.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0014_alter_columnmapping_target_field_and_more.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0016_deviceexistingmatch_ndi_devicematch_profile_device.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0017_native_contact_sync.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0018_ignoredfielddifference.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0019_deviceimportsource.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0021_importprofile_adapter_config.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0023_drop_moved_profile_columns.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0024_rename_importjob_importexecution.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0025_remove_importexecution_dry_run_and_more.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0026_alter_sourcedocument_profile.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0027_cableclassmapping_terminationresolution.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0028_cableimportsource.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0029_alter_cableimportsource_from_text_and_more.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0031_inferencebackend.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0032_inference_timeout_floor.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0033_resolutionproposal.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0034_tracedeviceresolution.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0036_resolutionproposal_job.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0037_cablesegmentoverride.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/__init__.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/navigation.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/netbox_reader.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/preview_row_actions.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_contract.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_jobs.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_presentation.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_response.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_tasks.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/resolution_proposals.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/review_workspace.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/signals.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/source_resolution.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/source_trace.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/conflict_modal.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/contact_candidate_modal.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/preview_row_actions.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/preview_row_controls.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/split_name_modal.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/sync_row_modal.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/trace_device_picker.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/trace_picker_search.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/trace_proposals.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/trace_termination_picker.js +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/target_runtime.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/_import_progress.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/_proposal_action.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/_proposal_card.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/analysis.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/bulk_yaml_import.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/cableclassmapping_edit.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/classrolemapping_edit.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/columnmapping_edit.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/columntransformrule_edit.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/device_import_data.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/devicetypemapping_edit.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_profile_yaml.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_progress.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_results.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_setup.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importexecution_list.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importprofile_edit.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importprofile_list.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/source_resolution_list.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/trace_workspace.html +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/termination_proposal.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/__init__.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/combined_name.xlsx +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/sample_workbook.xlsx +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/trace_copper.xlsx +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/trace_fiber.xlsx +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/inference_http.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/isolated_settings.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/mixins.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/mock_discipline.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/parallel.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/redaction.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_adapter_config.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_adapter_config_migration.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_api.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_cable_module.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_ci_configuration.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_comment_style.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_configuration_docs.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_conflict_row_actions.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_contact_resolution_ajax.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_contact_role_suggestion.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_contact_sync.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_cutover_edge_contracts.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_devcontainer_e2e.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_digest_indexed_models.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_execution_audit.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_graphql_filters.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_helpers.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_engine.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_execution.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_plan.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_source_migration.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_adapter.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_credentials.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_trust.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_installation_docs.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_ip_target.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_markdown_tables.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_misc_coverage.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_mock_discipline.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_model_base_conventions.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_models.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_module_boundaries.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_navigation.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_netbox_reader.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_object_permission_writer.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_parallel_test_setup.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_placement_reference_locks.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_policy_write_refusals.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_preview_assets.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_preview_state_ownership.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_pristine_preview_rows.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_profile_policy_crud_scoping.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_response.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_tasks.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_quick_action_input_bounds.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_quick_action_object_permissions.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_quick_action_profile_id.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_rack_module.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_remove_device_type_creation_config_migration.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_resolution_proposal_api.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_resolution_proposals.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_retire_superseded_proposals_migration.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_review_resolutions.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_review_workspace.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_serial_display.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_source_adapter.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_source_trace_contract.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_target_field_catalog.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_target_module_edge_contracts.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_text_encoding.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_decisions.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_device_resolution.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_workbook.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_workspace.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_views_coverage2.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_views_extra.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/trace_device_resolution.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/trace_schema.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/trace_workbook.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/transform_regex.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/urls.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/utils.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/values.py +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/dependency_links.txt +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/top_level.txt +0 -0
- {netbox_data_import-2.6.0 → netbox_data_import-2.7.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.7.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
|
|
@@ -17,6 +17,7 @@ License-File: LICENSE
|
|
|
17
17
|
Requires-Dist: defusedxml>=0.7.1
|
|
18
18
|
Requires-Dist: google-re2>=1.1.20251105
|
|
19
19
|
Requires-Dist: openpyxl>=3.1
|
|
20
|
+
Requires-Dist: packaging>=24
|
|
20
21
|
Requires-Dist: pyyaml>=6
|
|
21
22
|
Requires-Dist: requests>=2.33.0
|
|
22
23
|
Dynamic: license-file
|
|
@@ -31,7 +32,7 @@ Copyright (C) 2025 Marcin Zieba <marcinpsk@gmail.com>
|
|
|
31
32
|
[](https://marcinpsk.github.io/netbox-data-import-plugin/coverage/)
|
|
32
33
|
[](LICENSE)
|
|
33
34
|
[](https://www.python.org/)
|
|
34
|
-
[](https://github.com/netbox-community/netbox)
|
|
35
36
|
|
|
36
37
|
A NetBox plugin for importing device inventory and rack layouts from external DCIM systems via configurable field mappings.
|
|
37
38
|
|
|
@@ -50,11 +51,11 @@ Currently supports importing from Excel (`.xlsx`) workbooks with a flat, one-row
|
|
|
50
51
|
|
|
51
52
|
## Requirements
|
|
52
53
|
|
|
53
|
-
- NetBox ≥ 4.6.
|
|
54
|
+
- NetBox ≥ 4.6.9
|
|
54
55
|
- Python ≥ 3.12
|
|
55
56
|
|
|
56
57
|
Cable trace import verifies each pass-through against the `PortMapping` model, which replaced the
|
|
57
|
-
single rear-port reference on a front port in NetBox 4.5. The plugin supports NetBox 4.6 and later.
|
|
58
|
+
single rear-port reference on a front port in NetBox 4.5. The plugin supports NetBox 4.6.9 and later.
|
|
58
59
|
|
|
59
60
|
## Installation
|
|
60
61
|
|
|
@@ -8,7 +8,7 @@ Copyright (C) 2025 Marcin Zieba <marcinpsk@gmail.com>
|
|
|
8
8
|
[](https://marcinpsk.github.io/netbox-data-import-plugin/coverage/)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://www.python.org/)
|
|
11
|
-
[](https://github.com/netbox-community/netbox)
|
|
12
12
|
|
|
13
13
|
A NetBox plugin for importing device inventory and rack layouts from external DCIM systems via configurable field mappings.
|
|
14
14
|
|
|
@@ -27,11 +27,11 @@ Currently supports importing from Excel (`.xlsx`) workbooks with a flat, one-row
|
|
|
27
27
|
|
|
28
28
|
## Requirements
|
|
29
29
|
|
|
30
|
-
- NetBox ≥ 4.6.
|
|
30
|
+
- NetBox ≥ 4.6.9
|
|
31
31
|
- Python ≥ 3.12
|
|
32
32
|
|
|
33
33
|
Cable trace import verifies each pass-through against the `PortMapping` model, which replaced the
|
|
34
|
-
single rear-port reference on a front port in NetBox 4.5. The plugin supports NetBox 4.6 and later.
|
|
34
|
+
single rear-port reference on a front port in NetBox 4.5. The plugin supports NetBox 4.6.9 and later.
|
|
35
35
|
|
|
36
36
|
## Installation
|
|
37
37
|
|
|
@@ -4,7 +4,7 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
from netbox.plugins import PluginConfig
|
|
6
6
|
|
|
7
|
-
__version__ = "2.
|
|
7
|
+
__version__ = "2.7.0"
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class NetBoxDataImportConfig(PluginConfig):
|
|
@@ -17,8 +17,9 @@ class NetBoxDataImportConfig(PluginConfig):
|
|
|
17
17
|
base_url = "data-import"
|
|
18
18
|
author = "Marcin Zieba"
|
|
19
19
|
author_email = "marcinpsk@gmail.com"
|
|
20
|
-
min_version = "4.6.
|
|
20
|
+
min_version = "4.6.9"
|
|
21
21
|
graphql_schema = "graphql.schema.schema"
|
|
22
|
+
middleware = ["netbox_data_import.branching.BranchRefusalMiddleware"]
|
|
22
23
|
|
|
23
24
|
default_settings: dict[str, Any] = {
|
|
24
25
|
# A deployment that names no allowlist reaches no origin, rather than every origin.
|
|
@@ -40,10 +41,12 @@ class NetBoxDataImportConfig(PluginConfig):
|
|
|
40
41
|
raise ImproperlyConfigured(f"Plugin {cls.__module__} has an invalid configuration: {exc}") from exc
|
|
41
42
|
|
|
42
43
|
def ready(self):
|
|
43
|
-
"""Import the modules NetBox does not load,
|
|
44
|
+
"""Import the modules NetBox does not load, and register with netbox-branching when it is installed."""
|
|
44
45
|
super().ready()
|
|
45
46
|
|
|
46
|
-
from . import jobs, termination_proposal
|
|
47
|
+
from . import branching, jobs, termination_proposal
|
|
48
|
+
|
|
49
|
+
branching.register()
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
config = NetBoxDataImportConfig
|
|
@@ -8,6 +8,7 @@ from rest_framework import mixins, permissions, viewsets
|
|
|
8
8
|
from rest_framework.exceptions import PermissionDenied, ValidationError
|
|
9
9
|
from rest_framework.permissions import DjangoModelPermissions
|
|
10
10
|
|
|
11
|
+
from ..filters import ImportProfileFilterSet, InferenceBackendFilterSet
|
|
11
12
|
from ..field_keys import SELECT_TERMINATION_TASK, parse_termination_field_key
|
|
12
13
|
from ..models import (
|
|
13
14
|
locked_profile_policy,
|
|
@@ -69,6 +70,7 @@ class ImportProfileViewSet(NetBoxModelViewSet):
|
|
|
69
70
|
"tags", "column_mappings", "class_role_mappings", "device_type_mappings"
|
|
70
71
|
)
|
|
71
72
|
serializer_class = ImportProfileSerializer
|
|
73
|
+
filterset_class = ImportProfileFilterSet
|
|
72
74
|
|
|
73
75
|
|
|
74
76
|
class _ProfileScopedQuerySetMixin(viewsets.GenericViewSet):
|
|
@@ -291,3 +293,4 @@ class InferenceBackendViewSet(NetBoxModelViewSet):
|
|
|
291
293
|
|
|
292
294
|
queryset = InferenceBackend.objects.prefetch_related("tags")
|
|
293
295
|
serializer_class = InferenceBackendSerializer
|
|
296
|
+
filterset_class = InferenceBackendFilterSet
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Marcin Zieba <marcinpsk@gmail.com>
|
|
3
|
+
"""The one owner of every netbox-branching fact the plugin relies on.
|
|
4
|
+
|
|
5
|
+
netbox-branching is optional. Without it, every function here is a no-op.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from core.choices import ObjectChangeActionChoices
|
|
9
|
+
from django.apps import apps
|
|
10
|
+
from django.contrib.messages import get_messages
|
|
11
|
+
from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed
|
|
12
|
+
from django.db import models
|
|
13
|
+
from django.http import JsonResponse
|
|
14
|
+
from django.shortcuts import render
|
|
15
|
+
from packaging.version import Version
|
|
16
|
+
from utilities.api import is_api_request
|
|
17
|
+
|
|
18
|
+
APP_LABEL = "netbox_data_import"
|
|
19
|
+
BRANCHING_APP_LABEL = "netbox_branching"
|
|
20
|
+
REFUSAL_CODE = "branch_not_supported"
|
|
21
|
+
# The design covers netbox-branching 1.2.x; 1.1.x also has register_branching_resolver.
|
|
22
|
+
MINIMUM_BRANCHING_RELEASE = (1, 2)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BranchActive(Exception):
|
|
26
|
+
"""A plugin entry point was reached while a netbox-branching branch is active."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def installed() -> bool:
|
|
30
|
+
"""Return whether netbox-branching is an installed app."""
|
|
31
|
+
return apps.is_installed(BRANCHING_APP_LABEL)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def active_branch():
|
|
35
|
+
"""Return the active Branch, or None when no branch is active or netbox-branching is absent."""
|
|
36
|
+
if not installed():
|
|
37
|
+
return None
|
|
38
|
+
from netbox_branching.contextvars import active_branch as branch_context
|
|
39
|
+
from netbox_branching.models import Branch
|
|
40
|
+
|
|
41
|
+
branch = branch_context.get()
|
|
42
|
+
# A header naming a branch that is not ready puts netbox-branching's 400 response in the context.
|
|
43
|
+
return branch if isinstance(branch, Branch) else None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# An anonymous caller is refused without the name or the state of any branch.
|
|
47
|
+
ANONYMOUS_REFUSAL = "NetBox Data Import runs on main only, and this request selects a branch."
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def refusal_message(branch) -> str:
|
|
51
|
+
"""Return the one message every refused entry point shows."""
|
|
52
|
+
if branch is None:
|
|
53
|
+
return "NetBox Data Import runs on main only, and this request selects a branch that is not active."
|
|
54
|
+
# Typographic quotes need no HTML escaping, so the page, the REST detail and GraphQL show one text.
|
|
55
|
+
return f"NetBox Data Import runs on main only, and the active branch is \u201c{branch.name}\u201d."
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def refuse_branch() -> None:
|
|
59
|
+
"""Raise BranchActive when a branch is active."""
|
|
60
|
+
branch = active_branch()
|
|
61
|
+
if branch is not None:
|
|
62
|
+
raise BranchActive(refusal_message(branch))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def fail_job_in_branch(runner) -> None:
|
|
66
|
+
"""Fail a background job with the refusal in its log when its worker has a branch active."""
|
|
67
|
+
from core.exceptions import JobFailed
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
refuse_branch()
|
|
71
|
+
except BranchActive as exc:
|
|
72
|
+
refusal = exc
|
|
73
|
+
else:
|
|
74
|
+
return
|
|
75
|
+
runner.logger.error(str(refusal))
|
|
76
|
+
raise JobFailed(str(refusal)) from refusal
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _selects_a_branch(request) -> bool:
|
|
80
|
+
"""Return whether a request names a branch, in netbox-branching's own order of precedence."""
|
|
81
|
+
from netbox_branching.constants import BRANCH_HEADER, COOKIE_NAME, QUERY_PARAM
|
|
82
|
+
from netbox_branching.utilities import is_api_request
|
|
83
|
+
|
|
84
|
+
# netbox-branching reads the header on REST and GraphQL requests only.
|
|
85
|
+
if is_api_request(request) and BRANCH_HEADER in request.headers:
|
|
86
|
+
return True
|
|
87
|
+
if QUERY_PARAM in request.GET:
|
|
88
|
+
return bool(request.GET[QUERY_PARAM])
|
|
89
|
+
return COOKIE_NAME in request.COOKIES
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def request_refusal(request) -> str | None:
|
|
93
|
+
"""Return the refusal message for a request that has a branch active or selected, else None."""
|
|
94
|
+
if not installed():
|
|
95
|
+
return None
|
|
96
|
+
branch = active_branch()
|
|
97
|
+
if branch is None and not _selects_a_branch(request):
|
|
98
|
+
return None
|
|
99
|
+
if not request.user.is_authenticated:
|
|
100
|
+
return ANONYMOUS_REFUSAL
|
|
101
|
+
return refusal_message(branch)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _is_plugin_view(view_func) -> bool:
|
|
105
|
+
owner = getattr(view_func, "view_class", None) or getattr(view_func, "cls", None) or view_func
|
|
106
|
+
module = getattr(owner, "__module__", None) or ""
|
|
107
|
+
return module == APP_LABEL or module.startswith(f"{APP_LABEL}.")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class BranchRefusalMiddleware:
|
|
111
|
+
"""Refuse a request to a plugin-owned view while a branch is active or selected."""
|
|
112
|
+
|
|
113
|
+
def __init__(self, get_response):
|
|
114
|
+
if not installed():
|
|
115
|
+
raise MiddlewareNotUsed
|
|
116
|
+
self.get_response = get_response
|
|
117
|
+
|
|
118
|
+
def __call__(self, request):
|
|
119
|
+
"""Pass the request on; the refusal needs the resolved view, so it happens in process_view."""
|
|
120
|
+
return self.get_response(request)
|
|
121
|
+
|
|
122
|
+
def process_view(self, request, view_func, view_args, view_kwargs):
|
|
123
|
+
"""Answer 409 in place of the plugin view: JSON for the REST API, a page for the UI."""
|
|
124
|
+
if not _is_plugin_view(view_func):
|
|
125
|
+
return None
|
|
126
|
+
message = request_refusal(request)
|
|
127
|
+
if message is None:
|
|
128
|
+
return None
|
|
129
|
+
if is_api_request(request):
|
|
130
|
+
return JsonResponse({"detail": message, "code": REFUSAL_CODE}, status=409)
|
|
131
|
+
# The plugin's scripts ask for JSON and show the `error` of an `ok: false` envelope.
|
|
132
|
+
if request.get_preferred_type(["text/html", "application/json"]) == "application/json":
|
|
133
|
+
return JsonResponse({"ok": False, "error": message, "code": REFUSAL_CODE}, status=409)
|
|
134
|
+
if request.user.is_authenticated:
|
|
135
|
+
return render(request, "netbox_data_import/branch_refused.html", {"message": message}, status=409)
|
|
136
|
+
# netbox-branching queues messages that name the branch; reading them here deletes them unseen.
|
|
137
|
+
list(get_messages(request))
|
|
138
|
+
# The full layout shows the branch selector, so an anonymous caller gets the bare page.
|
|
139
|
+
return render(
|
|
140
|
+
request,
|
|
141
|
+
"netbox_data_import/branch_refused_anonymous.html",
|
|
142
|
+
{"message": message, "messages": ()},
|
|
143
|
+
status=409,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _branchable_references(model: type[models.Model]) -> set[type[models.Model]]:
|
|
148
|
+
"""Return the branchable models outside the plugin that a concrete foreign key of the model references."""
|
|
149
|
+
from netbox_branching.utilities import supports_branching
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
field.related_model
|
|
153
|
+
for field in model._meta.concrete_fields
|
|
154
|
+
if isinstance(field, models.ForeignKey)
|
|
155
|
+
and field.related_model._meta.app_label != APP_LABEL
|
|
156
|
+
and supports_branching(field.related_model)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def is_branchable(model: type[models.Model]) -> bool | None:
|
|
161
|
+
"""Resolve branching support for a plugin model, and defer (None) for every other model.
|
|
162
|
+
|
|
163
|
+
A plugin model is branchable when one of its concrete foreign keys reaches a branchable model
|
|
164
|
+
outside the plugin: a core delete in a branch then cascades into that table, which must exist
|
|
165
|
+
in the branch schema.
|
|
166
|
+
"""
|
|
167
|
+
if model._meta.app_label != APP_LABEL:
|
|
168
|
+
return None
|
|
169
|
+
return bool(_branchable_references(model))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _unrevertable_models() -> set[type[models.Model]]:
|
|
173
|
+
"""Return the models whose delete a revert cannot undo without losing plugin data."""
|
|
174
|
+
from extras.models import Tag, TaggedItem
|
|
175
|
+
|
|
176
|
+
# A Tag delete removes Import Profile assignments in main, and a sync records a TaggedItem delete.
|
|
177
|
+
unrevertable = {Tag, TaggedItem}
|
|
178
|
+
for model in apps.get_app_config(APP_LABEL).get_models():
|
|
179
|
+
if references := _branchable_references(model):
|
|
180
|
+
unrevertable |= {model, *references}
|
|
181
|
+
return unrevertable
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def validate_revert(branch):
|
|
185
|
+
"""Refuse a revert of a branch that deleted an object the plugin's data depends on.
|
|
186
|
+
|
|
187
|
+
Revert replays the branch's ObjectChange rows, and plugin data has none, except the synthetic
|
|
188
|
+
deletes that a sync records. So a revert cannot restore the plugin data such a delete removed.
|
|
189
|
+
"""
|
|
190
|
+
from django.contrib.contenttypes.models import ContentType
|
|
191
|
+
from netbox_branching.utilities import BranchActionIndicator
|
|
192
|
+
|
|
193
|
+
content_types = ContentType.objects.get_for_models(*_unrevertable_models())
|
|
194
|
+
models_by_type = {content_type.pk: model for model, content_type in content_types.items()}
|
|
195
|
+
deleted = (
|
|
196
|
+
branch.get_changes()
|
|
197
|
+
.filter(action=ObjectChangeActionChoices.ACTION_DELETE, changed_object_type__in=models_by_type)
|
|
198
|
+
.order_by()
|
|
199
|
+
.values_list("changed_object_type", flat=True)
|
|
200
|
+
.distinct()
|
|
201
|
+
)
|
|
202
|
+
names = sorted(str(models_by_type[type_id]._meta.verbose_name) for type_id in deleted)
|
|
203
|
+
if not names:
|
|
204
|
+
return BranchActionIndicator(True)
|
|
205
|
+
return BranchActionIndicator(
|
|
206
|
+
False,
|
|
207
|
+
"NetBox Data Import data cannot be restored by a revert, and this branch deleted objects of these "
|
|
208
|
+
f"types: {', '.join(names)}.",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def register() -> None:
|
|
213
|
+
"""Register the resolver and the revert validator, and refuse a configuration that overrides the resolver."""
|
|
214
|
+
if not installed():
|
|
215
|
+
return
|
|
216
|
+
release = apps.get_app_config(BRANCHING_APP_LABEL).version
|
|
217
|
+
# Compare the release segment only, so a 1.2 pre-release is not refused.
|
|
218
|
+
if Version(release).release < MINIMUM_BRANCHING_RELEASE:
|
|
219
|
+
raise ImproperlyConfigured(
|
|
220
|
+
f"{APP_LABEL}: netbox-branching {release} is installed; this plugin needs 1.2 or later."
|
|
221
|
+
)
|
|
222
|
+
from netbox_branching.models import Branch
|
|
223
|
+
from netbox_branching.utilities import register_branching_resolver, supports_branching
|
|
224
|
+
|
|
225
|
+
register_branching_resolver(is_branchable)
|
|
226
|
+
Branch.register_preaction_check(validate_revert, "revert")
|
|
227
|
+
overridden = sorted(
|
|
228
|
+
model._meta.label
|
|
229
|
+
for model in apps.get_app_config(APP_LABEL).get_models()
|
|
230
|
+
if supports_branching(model) != is_branchable(model)
|
|
231
|
+
)
|
|
232
|
+
if overridden:
|
|
233
|
+
raise ImproperlyConfigured(
|
|
234
|
+
f"{APP_LABEL}: netbox-branching disagrees with the plugin on branching for {', '.join(overridden)}. "
|
|
235
|
+
f"Remove {APP_LABEL} models from the netbox_branching exempt_models setting."
|
|
236
|
+
)
|
{netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/contact_resolution.py
RENAMED
|
@@ -478,12 +478,14 @@ class PrimaryContactResolver:
|
|
|
478
478
|
assignment = None
|
|
479
479
|
if action == "replace":
|
|
480
480
|
assignment = ContactAssignment.objects.get(pk=plan["primary_assignment_id"])
|
|
481
|
+
assignment.snapshot()
|
|
481
482
|
assignment.contact = contact
|
|
482
483
|
assignment.full_clean()
|
|
483
484
|
assignment.save(update_fields=["contact"])
|
|
484
485
|
enforce_saved_object_permission(assignment, user, "change")
|
|
485
486
|
elif action == "demote_and_promote":
|
|
486
487
|
previous = ContactAssignment.objects.get(pk=plan["primary_assignment_id"])
|
|
488
|
+
previous.snapshot()
|
|
487
489
|
previous.priority = "secondary"
|
|
488
490
|
previous.full_clean()
|
|
489
491
|
previous.save(update_fields=["priority"])
|
|
@@ -498,6 +500,7 @@ class PrimaryContactResolver:
|
|
|
498
500
|
assignment.save()
|
|
499
501
|
enforce_saved_object_permission(assignment, user, "add")
|
|
500
502
|
elif assignment.priority != "primary":
|
|
503
|
+
assignment.snapshot()
|
|
501
504
|
assignment.priority = "primary"
|
|
502
505
|
assignment.full_clean()
|
|
503
506
|
assignment.save(update_fields=["priority"])
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (C) 2025 Marcin Zieba <marcinpsk@gmail.com>
|
|
3
|
+
from django.db.models import Q
|
|
4
|
+
from netbox.filtersets import NetBoxModelFilterSet
|
|
5
|
+
from .models import ImportProfile, InferenceBackend
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ImportProfileFilterSet(NetBoxModelFilterSet):
|
|
9
|
+
"""FilterSet for ImportProfile, supporting name substring search."""
|
|
10
|
+
|
|
11
|
+
class Meta:
|
|
12
|
+
model = ImportProfile
|
|
13
|
+
fields = ["name", "source_adapter"]
|
|
14
|
+
|
|
15
|
+
def search(self, queryset, name, value):
|
|
16
|
+
"""Filter profiles by name substring."""
|
|
17
|
+
return queryset.filter(name__icontains=value)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class InferenceBackendFilterSet(NetBoxModelFilterSet):
|
|
21
|
+
"""FilterSet for InferenceBackend, supporting key and display name search."""
|
|
22
|
+
|
|
23
|
+
class Meta:
|
|
24
|
+
model = InferenceBackend
|
|
25
|
+
fields = ["backend_key", "display_name", "adapter_type", "response_mode", "enabled"]
|
|
26
|
+
|
|
27
|
+
def search(self, queryset, name, value):
|
|
28
|
+
"""Filter backends by key or display name substring."""
|
|
29
|
+
return queryset.filter(Q(backend_key__icontains=value) | Q(display_name__icontains=value))
|
|
@@ -6,9 +6,11 @@ from django import forms
|
|
|
6
6
|
from django.utils.html import format_html
|
|
7
7
|
from dcim.models import Site, Location
|
|
8
8
|
from tenancy.models import Tenant
|
|
9
|
-
from netbox.forms import NetBoxModelBulkEditForm, NetBoxModelForm, NetBoxModelImportForm
|
|
10
|
-
from utilities.forms
|
|
11
|
-
from .
|
|
9
|
+
from netbox.forms import NetBoxModelBulkEditForm, NetBoxModelFilterSetForm, NetBoxModelForm, NetBoxModelImportForm
|
|
10
|
+
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
|
|
11
|
+
from utilities.forms.fields import DynamicModelChoiceField, TagFilterField
|
|
12
|
+
from utilities.forms.rendering import FieldSet
|
|
13
|
+
from .adapters import adapter_choices, get_adapter, selectable_adapter_choices
|
|
12
14
|
from .catalog import CATALOG
|
|
13
15
|
from .models import (
|
|
14
16
|
CableClassMapping,
|
|
@@ -200,6 +202,18 @@ class ImportProfileBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
200
202
|
nullable_fields = ("description",)
|
|
201
203
|
|
|
202
204
|
|
|
205
|
+
class ImportProfileFilterForm(NetBoxModelFilterSetForm):
|
|
206
|
+
"""Filters tab of the Import Profile list."""
|
|
207
|
+
|
|
208
|
+
model = ImportProfile
|
|
209
|
+
fieldsets = (
|
|
210
|
+
FieldSet("q", "filter_id", "tag"),
|
|
211
|
+
FieldSet("source_adapter", name="Attributes"),
|
|
212
|
+
)
|
|
213
|
+
source_adapter = forms.ChoiceField(choices=lambda: add_blank_choice(adapter_choices()), required=False)
|
|
214
|
+
tag = TagFilterField(model)
|
|
215
|
+
|
|
216
|
+
|
|
203
217
|
class ColumnMappingForm(forms.ModelForm):
|
|
204
218
|
"""Form for creating and editing ColumnMapping instances."""
|
|
205
219
|
|
|
@@ -381,6 +395,20 @@ class InferenceBackendForm(NetBoxModelForm):
|
|
|
381
395
|
}
|
|
382
396
|
|
|
383
397
|
|
|
398
|
+
class InferenceBackendFilterForm(NetBoxModelFilterSetForm):
|
|
399
|
+
"""Filters tab of the AI backend list."""
|
|
400
|
+
|
|
401
|
+
model = InferenceBackend
|
|
402
|
+
fieldsets = (
|
|
403
|
+
FieldSet("q", "filter_id", "tag"),
|
|
404
|
+
FieldSet("adapter_type", "response_mode", "enabled", name="Attributes"),
|
|
405
|
+
)
|
|
406
|
+
adapter_type = forms.ChoiceField(choices=add_blank_choice(InferenceBackend.ADAPTER_TYPES), required=False)
|
|
407
|
+
response_mode = forms.ChoiceField(choices=add_blank_choice(InferenceBackend.RESPONSE_MODES), required=False)
|
|
408
|
+
enabled = forms.NullBooleanField(required=False, widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES))
|
|
409
|
+
tag = TagFilterField(model)
|
|
410
|
+
|
|
411
|
+
|
|
384
412
|
class ImportSetupForm(forms.Form):
|
|
385
413
|
"""Form for the import wizard step 1: select profile, upload file, choose site/location/tenant."""
|
|
386
414
|
|
|
@@ -5,24 +5,38 @@
|
|
|
5
5
|
|
|
6
6
|
import strawberry
|
|
7
7
|
import strawberry_django
|
|
8
|
+
from strawberry.permission import BasePermission
|
|
9
|
+
|
|
10
|
+
from netbox_data_import import branching
|
|
8
11
|
|
|
9
12
|
from .types import CableClassMappingType, ImportProfileType
|
|
10
13
|
|
|
11
14
|
|
|
15
|
+
class MainOnly(BasePermission):
|
|
16
|
+
"""Refuse a plugin field on the same terms as the plugin's REST and UI views."""
|
|
17
|
+
|
|
18
|
+
def has_permission(self, source, info, **kwargs) -> bool:
|
|
19
|
+
"""Raise BranchActive, whose message becomes the GraphQL error, for a refused request."""
|
|
20
|
+
message = branching.request_refusal(info.context.request)
|
|
21
|
+
if message is not None:
|
|
22
|
+
raise branching.BranchActive(message)
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
|
|
12
26
|
@strawberry.type(name="Query")
|
|
13
27
|
class ImportProfilesQuery:
|
|
14
28
|
"""Expose import profile detail and list queries."""
|
|
15
29
|
|
|
16
|
-
import_profile: ImportProfileType = strawberry_django.field()
|
|
17
|
-
import_profile_list: list[ImportProfileType] = strawberry_django.field()
|
|
30
|
+
import_profile: ImportProfileType = strawberry_django.field(permission_classes=[MainOnly])
|
|
31
|
+
import_profile_list: list[ImportProfileType] = strawberry_django.field(permission_classes=[MainOnly])
|
|
18
32
|
|
|
19
33
|
|
|
20
34
|
@strawberry.type(name="Query")
|
|
21
35
|
class CableClassMappingsQuery:
|
|
22
36
|
"""Expose CableClass mapping detail and list queries."""
|
|
23
37
|
|
|
24
|
-
cable_class_mapping: CableClassMappingType = strawberry_django.field()
|
|
25
|
-
cable_class_mapping_list: list[CableClassMappingType] = strawberry_django.field()
|
|
38
|
+
cable_class_mapping: CableClassMappingType = strawberry_django.field(permission_classes=[MainOnly])
|
|
39
|
+
cable_class_mapping_list: list[CableClassMappingType] = strawberry_django.field(permission_classes=[MainOnly])
|
|
26
40
|
|
|
27
41
|
|
|
28
42
|
schema = [ImportProfilesQuery, CableClassMappingsQuery]
|
|
@@ -15,7 +15,7 @@ from typing import cast
|
|
|
15
15
|
from django.core.exceptions import ValidationError
|
|
16
16
|
from django.db import DatabaseError
|
|
17
17
|
|
|
18
|
-
from . import adapter_config, adapters, catalog, target_modules
|
|
18
|
+
from . import adapter_config, adapters, branching, catalog, target_modules
|
|
19
19
|
from .models import FailureReason, ImportExecution, SourceDocument, locked_profile_policy
|
|
20
20
|
from .netbox_reader import NetBoxReader, PlanningTargetUnavailable
|
|
21
21
|
from .object_permissions import ObjectPermissionDenied, clear_user_permission_caches
|
|
@@ -159,6 +159,7 @@ class ImportEngine:
|
|
|
159
159
|
progress_callback=None,
|
|
160
160
|
) -> ImportExecution:
|
|
161
161
|
"""Apply selected units from one accepted serialized plan and return their audit row."""
|
|
162
|
+
branching.refuse_branch()
|
|
162
163
|
accepted = ImportPlan.from_dict(accepted_plan)
|
|
163
164
|
selected_identities = tuple(selection)
|
|
164
165
|
if not selected_identities:
|
|
@@ -284,6 +284,8 @@ def apply(target: IPTarget, user=None):
|
|
|
284
284
|
action = "change" if address is not None else "add"
|
|
285
285
|
if address is None:
|
|
286
286
|
address = IPAddress(address=target.address, vrf=target.interface.vrf)
|
|
287
|
+
else:
|
|
288
|
+
address.snapshot()
|
|
287
289
|
address.assigned_object = target.interface
|
|
288
290
|
address.full_clean()
|
|
289
291
|
address.save()
|