netbox-data-import 2.5.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.5.0 → netbox_data_import-2.7.0}/PKG-INFO +5 -4
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/README.md +3 -3
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/__init__.py +7 -4
- {netbox_data_import-2.5.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.7.0/netbox_data_import/cable_disclosure.py +448 -0
- netbox_data_import-2.7.0/netbox_data_import/cable_policy.py +157 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/cable_target.py +325 -55
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/catalog.py +1 -0
- {netbox_data_import-2.5.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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/forms.py +84 -8
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/schema.py +18 -4
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/import_engine.py +2 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_backend.py +16 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/ip_assignment.py +2 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/jobs.py +80 -35
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0015_cleanup_duplicate_device_matches.py +3 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0020_migrate_import_source_custom_field.py +3 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0022_migrate_profile_adapter_config.py +3 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0030_remove_device_type_creation_config.py +3 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0035_retire_superseded_proposals.py +3 -0
- netbox_data_import-2.7.0/netbox_data_import/migrations/0036_resolutionproposal_job.py +27 -0
- netbox_data_import-2.7.0/netbox_data_import/migrations/0037_cablesegmentoverride.py +42 -0
- netbox_data_import-2.7.0/netbox_data_import/migrations/0038_cable_tag_integrity.py +107 -0
- netbox_data_import-2.7.0/netbox_data_import/migrations/0039_remove_job_plan_copies.py +25 -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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/models.py +82 -87
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/object_permissions.py +2 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/plan.py +31 -6
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/preview_row_actions.py +1 -6
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/profile_yaml.py +2 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_decisions.py +6 -3
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_jobs.py +8 -6
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_presentation.py +92 -19
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_tasks.py +46 -6
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/resolution_proposals.py +64 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/review_workspace.py +191 -10
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/trace_picker_search.js +10 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/trace_proposals.js +7 -8
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tables.py +34 -2
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/target_modules.py +17 -6
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/target_runtime.py +2 -7
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/template_content.py +4 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/_proposal_card.html +6 -4
- 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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_preview.html +1 -1
- {netbox_data_import-2.5.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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/inferencebackend.html +1 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/trace_workspace.html +105 -7
- netbox_data_import-2.7.0/netbox_data_import/tests/conftest.py +57 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/helpers.py +123 -10
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/mock_discipline.py +79 -21
- 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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_atomic_exits.py +18 -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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_cable_module.py +1236 -25
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_configuration_surfaces.py +20 -2
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_conflict_row_actions.py +1 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_contact_resolution_ajax.py +4 -5
- {netbox_data_import-2.5.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_helpers.py +43 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/test_import_change_log.py +224 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_cutover.py +114 -6
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_backend.py +21 -18
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_connection_test.py +68 -38
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_secret_containment.py +13 -20
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_settings.py +7 -6
- netbox_data_import-2.7.0/netbox_data_import/tests/test_job_plan_cleanup_migration.py +62 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_migrations.py +214 -4
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_misc_coverage.py +5 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_mock_discipline.py +54 -0
- netbox_data_import-2.7.0/netbox_data_import/tests/test_plugins_config.py +42 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_decisions.py +29 -5
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_jobs.py +100 -6
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_tasks.py +48 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_workspace.py +453 -26
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_quick_action_input_bounds.py +1 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_release_metadata.py +49 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_resolution_proposals.py +19 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_review_actions.py +32 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_review_resolutions.py +1 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_review_workspace.py +8 -2
- netbox_data_import-2.7.0/netbox_data_import/tests/test_snapshot_guard.py +170 -0
- {netbox_data_import-2.5.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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_target_field_catalog.py +1 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_decisions.py +2 -2
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_device_resolution.py +32 -2
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_workbook.py +62 -16
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_workspace.py +1414 -18
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_views.py +140 -1
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/trace_workbook.py +5 -14
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/urls.py +10 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/views.py +499 -52
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/PKG-INFO +5 -4
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/SOURCES.txt +25 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/requires.txt +1 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/pyproject.toml +8 -1
- netbox_data_import-2.5.0/netbox_data_import/filters.py +0 -19
- netbox_data_import-2.5.0/netbox_data_import/tests/conftest.py +0 -25
- netbox_data_import-2.5.0/netbox_data_import/tests/netbox_configuration.py +0 -15
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/LICENSE +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/_dns_worker.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/adapter_config.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/adapter_forms.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/adapters.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/api/__init__.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/api/serializers.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/api/urls.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/device_field_review.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/device_identity.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/field_keys.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/flat_workbook.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/__init__.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/filters.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/types.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_adapter.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_connection_test.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_credentials.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_settings.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_transport.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_trust.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0001_initial.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0002_importjob.py +0 -0
- {netbox_data_import-2.5.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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0004_features.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0005_deviceexistingmatch.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0006_deviceexistingmatch_asset_tag.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0007_manufacturermapping.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0008_unique_constraints.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0009_add_rack_type_to_classrolemapping.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0010_alter_manufacturermapping_id.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0011_importprofile_capture_extra_data.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0012_classrolemapping_rack_type_related_name.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0013_remove_columnmapping_unique_target.py +0 -0
- {netbox_data_import-2.5.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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0016_deviceexistingmatch_ndi_devicematch_profile_device.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0017_native_contact_sync.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0018_ignoredfielddifference.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0019_deviceimportsource.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0021_importprofile_adapter_config.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0023_drop_moved_profile_columns.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0024_rename_importjob_importexecution.py +0 -0
- {netbox_data_import-2.5.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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0026_alter_sourcedocument_profile.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0027_cableclassmapping_terminationresolution.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0028_cableimportsource.py +0 -0
- {netbox_data_import-2.5.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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0031_inferencebackend.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0032_inference_timeout_floor.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0033_resolutionproposal.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0034_tracedeviceresolution.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/__init__.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/navigation.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/netbox_reader.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_contract.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_response.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/signals.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/source_resolution.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/source_trace.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/static/netbox_data_import/js/conflict_modal.js +0 -0
- {netbox_data_import-2.5.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.5.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.5.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.5.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.5.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.5.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.5.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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/_import_progress.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/_proposal_action.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/analysis.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/bulk_yaml_import.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/cableclassmapping_edit.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/classrolemapping_edit.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/columnmapping_edit.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/columntransformrule_edit.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/device_import_data.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/devicetypemapping_edit.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_profile_yaml.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_progress.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_results.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_setup.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importexecution_list.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importprofile_edit.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importprofile_list.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/source_resolution_list.html +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/termination_proposal.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/__init__.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/combined_name.xlsx +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/sample_workbook.xlsx +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/trace_copper.xlsx +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/trace_fiber.xlsx +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/inference_http.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/isolated_settings.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/mixins.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/parallel.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/redaction.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_adapter_config.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_adapter_config_migration.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_api.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_ci_configuration.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_comment_style.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_configuration_docs.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_contact_role_suggestion.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_contact_sync.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_cutover_edge_contracts.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_devcontainer_e2e.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_digest_indexed_models.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_execution_audit.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_graphql_filters.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_engine.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_execution.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_plan.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_source_migration.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_adapter.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_credentials.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_trust.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_installation_docs.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_ip_target.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_markdown_tables.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_model_base_conventions.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_models.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_module_boundaries.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_navigation.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_netbox_reader.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_object_permission_writer.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_parallel_test_setup.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_placement_reference_locks.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_policy_write_refusals.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_preview_assets.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_preview_state_ownership.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_pristine_preview_rows.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_profile_policy_crud_scoping.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_response.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_quick_action_object_permissions.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_quick_action_profile_id.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_rack_module.py +0 -0
- {netbox_data_import-2.5.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.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_resolution_proposal_api.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_retire_superseded_proposals_migration.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_serial_display.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_source_adapter.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_source_trace_contract.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_target_module_edge_contracts.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_text_encoding.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_views_coverage2.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_views_extra.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/trace_device_resolution.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/trace_schema.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/transform_regex.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/utils.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import/values.py +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/dependency_links.txt +0 -0
- {netbox_data_import-2.5.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/top_level.txt +0 -0
- {netbox_data_import-2.5.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
|
+
)
|