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.
Files changed (265) hide show
  1. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/PKG-INFO +5 -4
  2. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/README.md +3 -3
  3. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/__init__.py +7 -4
  4. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/api/views.py +3 -0
  5. netbox_data_import-2.7.0/netbox_data_import/branching.py +236 -0
  6. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/contact_resolution.py +3 -0
  7. netbox_data_import-2.7.0/netbox_data_import/filters.py +29 -0
  8. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/forms.py +31 -3
  9. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/schema.py +18 -4
  10. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/import_engine.py +2 -1
  11. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/ip_assignment.py +2 -0
  12. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/jobs.py +77 -34
  13. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0015_cleanup_duplicate_device_matches.py +3 -0
  14. {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
  15. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0022_migrate_profile_adapter_config.py +3 -0
  16. {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
  17. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0035_retire_superseded_proposals.py +3 -0
  18. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0038_cable_tag_integrity.py +3 -0
  19. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0039_remove_job_plan_copies.py +3 -0
  20. netbox_data_import-2.7.0/netbox_data_import/migrations/0040_move_tags_to_tagged_items.py +55 -0
  21. netbox_data_import-2.7.0/netbox_data_import/migrations/0041_remove_importprofile_tags_and_more.py +32 -0
  22. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/models.py +5 -11
  23. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/object_permissions.py +2 -0
  24. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/plan.py +7 -2
  25. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/profile_yaml.py +2 -0
  26. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_decisions.py +3 -0
  27. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tables.py +4 -1
  28. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/target_modules.py +17 -6
  29. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/template_content.py +4 -0
  30. netbox_data_import-2.7.0/netbox_data_import/templates/netbox_data_import/branch_refused.html +13 -0
  31. netbox_data_import-2.7.0/netbox_data_import/templates/netbox_data_import/branch_refused_anonymous.html +16 -0
  32. netbox_data_import-2.7.0/netbox_data_import/templates/netbox_data_import/device_import_data_main_only.html +14 -0
  33. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_preview.html +1 -1
  34. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importprofile.html +1 -0
  35. netbox_data_import-2.7.0/netbox_data_import/templates/netbox_data_import/inc/branch_refusal_card.html +12 -0
  36. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/inferencebackend.html +1 -0
  37. netbox_data_import-2.7.0/netbox_data_import/tests/conftest.py +57 -0
  38. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/helpers.py +63 -0
  39. netbox_data_import-2.7.0/netbox_data_import/tests/netbox_configuration.py +17 -0
  40. netbox_data_import-2.7.0/netbox_data_import/tests/plugins_config.py +45 -0
  41. netbox_data_import-2.7.0/netbox_data_import/tests/snapshot_guard.py +126 -0
  42. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_atomic_exits.py +6 -0
  43. netbox_data_import-2.7.0/netbox_data_import/tests/test_branching.py +438 -0
  44. netbox_data_import-2.7.0/netbox_data_import/tests/test_branching_absent.py +25 -0
  45. netbox_data_import-2.7.0/netbox_data_import/tests/test_branching_refusal.py +479 -0
  46. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_configuration_surfaces.py +20 -2
  47. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_device_module.py +14 -0
  48. netbox_data_import-2.7.0/netbox_data_import/tests/test_import_change_log.py +224 -0
  49. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_cutover.py +103 -1
  50. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_backend.py +21 -18
  51. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_connection_test.py +68 -38
  52. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_secret_containment.py +13 -20
  53. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_settings.py +7 -6
  54. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_job_plan_cleanup_migration.py +6 -1
  55. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_migrations.py +114 -6
  56. netbox_data_import-2.7.0/netbox_data_import/tests/test_plugins_config.py +42 -0
  57. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_decisions.py +3 -2
  58. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_jobs.py +4 -3
  59. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_workspace.py +35 -42
  60. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_release_metadata.py +49 -1
  61. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_review_actions.py +18 -0
  62. netbox_data_import-2.7.0/netbox_data_import/tests/test_snapshot_guard.py +170 -0
  63. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_standard_views.py +3 -4
  64. netbox_data_import-2.7.0/netbox_data_import/tests/test_tag_assignments.py +239 -0
  65. netbox_data_import-2.7.0/netbox_data_import/tests/test_tags_migration.py +119 -0
  66. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_views.py +139 -0
  67. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/views.py +55 -20
  68. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/PKG-INFO +5 -4
  69. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/SOURCES.txt +17 -0
  70. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/requires.txt +1 -0
  71. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/pyproject.toml +8 -1
  72. netbox_data_import-2.6.0/netbox_data_import/filters.py +0 -19
  73. netbox_data_import-2.6.0/netbox_data_import/tests/conftest.py +0 -25
  74. netbox_data_import-2.6.0/netbox_data_import/tests/netbox_configuration.py +0 -15
  75. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/LICENSE +0 -0
  76. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/_dns_worker.py +0 -0
  77. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/adapter_config.py +0 -0
  78. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/adapter_forms.py +0 -0
  79. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/adapters.py +0 -0
  80. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/api/__init__.py +0 -0
  81. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/api/serializers.py +0 -0
  82. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/api/urls.py +0 -0
  83. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/cable_disclosure.py +0 -0
  84. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/cable_policy.py +0 -0
  85. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/cable_target.py +0 -0
  86. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/catalog.py +0 -0
  87. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/device_field_review.py +0 -0
  88. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/device_identity.py +0 -0
  89. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/field_keys.py +0 -0
  90. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/flat_workbook.py +0 -0
  91. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/__init__.py +0 -0
  92. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/filters.py +0 -0
  93. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/graphql/types.py +0 -0
  94. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_adapter.py +0 -0
  95. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_backend.py +0 -0
  96. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_connection_test.py +0 -0
  97. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_credentials.py +0 -0
  98. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_settings.py +0 -0
  99. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_transport.py +0 -0
  100. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/inference_trust.py +0 -0
  101. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0001_initial.py +0 -0
  102. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0002_importjob.py +0 -0
  103. {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
  104. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0004_features.py +0 -0
  105. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0005_deviceexistingmatch.py +0 -0
  106. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0006_deviceexistingmatch_asset_tag.py +0 -0
  107. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0007_manufacturermapping.py +0 -0
  108. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0008_unique_constraints.py +0 -0
  109. {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
  110. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0010_alter_manufacturermapping_id.py +0 -0
  111. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0011_importprofile_capture_extra_data.py +0 -0
  112. {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
  113. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0013_remove_columnmapping_unique_target.py +0 -0
  114. {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
  115. {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
  116. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0017_native_contact_sync.py +0 -0
  117. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0018_ignoredfielddifference.py +0 -0
  118. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0019_deviceimportsource.py +0 -0
  119. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0021_importprofile_adapter_config.py +0 -0
  120. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0023_drop_moved_profile_columns.py +0 -0
  121. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0024_rename_importjob_importexecution.py +0 -0
  122. {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
  123. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0026_alter_sourcedocument_profile.py +0 -0
  124. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0027_cableclassmapping_terminationresolution.py +0 -0
  125. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0028_cableimportsource.py +0 -0
  126. {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
  127. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0031_inferencebackend.py +0 -0
  128. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0032_inference_timeout_floor.py +0 -0
  129. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0033_resolutionproposal.py +0 -0
  130. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0034_tracedeviceresolution.py +0 -0
  131. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0036_resolutionproposal_job.py +0 -0
  132. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/0037_cablesegmentoverride.py +0 -0
  133. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/migrations/__init__.py +0 -0
  134. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/navigation.py +0 -0
  135. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/netbox_reader.py +0 -0
  136. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/preview_row_actions.py +0 -0
  137. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_contract.py +0 -0
  138. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_jobs.py +0 -0
  139. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_presentation.py +0 -0
  140. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_response.py +0 -0
  141. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/proposal_tasks.py +0 -0
  142. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/resolution_proposals.py +0 -0
  143. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/review_workspace.py +0 -0
  144. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/signals.py +0 -0
  145. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/source_resolution.py +0 -0
  146. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/source_trace.py +0 -0
  147. {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
  148. {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
  149. {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
  150. {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
  151. {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
  152. {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
  153. {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
  154. {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
  155. {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
  156. {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
  157. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/target_runtime.py +0 -0
  158. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/_import_progress.html +0 -0
  159. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/_proposal_action.html +0 -0
  160. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/_proposal_card.html +0 -0
  161. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/analysis.html +0 -0
  162. {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
  163. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/cableclassmapping_edit.html +0 -0
  164. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/classrolemapping_edit.html +0 -0
  165. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/columnmapping_edit.html +0 -0
  166. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/columntransformrule_edit.html +0 -0
  167. {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
  168. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/devicetypemapping_edit.html +0 -0
  169. {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
  170. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_progress.html +0 -0
  171. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_results.html +0 -0
  172. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/import_setup.html +0 -0
  173. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importexecution_list.html +0 -0
  174. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importprofile_edit.html +0 -0
  175. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/importprofile_list.html +0 -0
  176. {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
  177. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/templates/netbox_data_import/trace_workspace.html +0 -0
  178. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/termination_proposal.py +0 -0
  179. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/__init__.py +0 -0
  180. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/combined_name.xlsx +0 -0
  181. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/sample_workbook.xlsx +0 -0
  182. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/trace_copper.xlsx +0 -0
  183. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/fixtures/trace_fiber.xlsx +0 -0
  184. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/inference_http.py +0 -0
  185. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/isolated_settings.py +0 -0
  186. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/mixins.py +0 -0
  187. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/mock_discipline.py +0 -0
  188. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/parallel.py +0 -0
  189. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/redaction.py +0 -0
  190. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_adapter_config.py +0 -0
  191. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_adapter_config_migration.py +0 -0
  192. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_api.py +0 -0
  193. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_cable_module.py +0 -0
  194. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_ci_configuration.py +0 -0
  195. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_comment_style.py +0 -0
  196. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_configuration_docs.py +0 -0
  197. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_conflict_row_actions.py +0 -0
  198. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_contact_resolution_ajax.py +0 -0
  199. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_contact_role_suggestion.py +0 -0
  200. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_contact_sync.py +0 -0
  201. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_cutover_edge_contracts.py +0 -0
  202. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_devcontainer_e2e.py +0 -0
  203. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_digest_indexed_models.py +0 -0
  204. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_execution_audit.py +0 -0
  205. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_graphql_filters.py +0 -0
  206. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_helpers.py +0 -0
  207. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_engine.py +0 -0
  208. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_execution.py +0 -0
  209. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_plan.py +0 -0
  210. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_import_source_migration.py +0 -0
  211. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_adapter.py +0 -0
  212. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_credentials.py +0 -0
  213. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_inference_trust.py +0 -0
  214. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_installation_docs.py +0 -0
  215. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_ip_target.py +0 -0
  216. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_markdown_tables.py +0 -0
  217. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_misc_coverage.py +0 -0
  218. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_mock_discipline.py +0 -0
  219. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_model_base_conventions.py +0 -0
  220. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_models.py +0 -0
  221. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_module_boundaries.py +0 -0
  222. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_navigation.py +0 -0
  223. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_netbox_reader.py +0 -0
  224. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_object_permission_writer.py +0 -0
  225. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_parallel_test_setup.py +0 -0
  226. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_placement_reference_locks.py +0 -0
  227. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_policy_write_refusals.py +0 -0
  228. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_preview_assets.py +0 -0
  229. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_preview_state_ownership.py +0 -0
  230. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_pristine_preview_rows.py +0 -0
  231. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_profile_policy_crud_scoping.py +0 -0
  232. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_response.py +0 -0
  233. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_proposal_tasks.py +0 -0
  234. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_quick_action_input_bounds.py +0 -0
  235. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_quick_action_object_permissions.py +0 -0
  236. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_quick_action_profile_id.py +0 -0
  237. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_rack_module.py +0 -0
  238. {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
  239. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_resolution_proposal_api.py +0 -0
  240. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_resolution_proposals.py +0 -0
  241. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_retire_superseded_proposals_migration.py +0 -0
  242. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_review_resolutions.py +0 -0
  243. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_review_workspace.py +0 -0
  244. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_serial_display.py +0 -0
  245. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_source_adapter.py +0 -0
  246. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_source_trace_contract.py +0 -0
  247. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_target_field_catalog.py +0 -0
  248. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_target_module_edge_contracts.py +0 -0
  249. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_text_encoding.py +0 -0
  250. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_decisions.py +0 -0
  251. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_device_resolution.py +0 -0
  252. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_workbook.py +0 -0
  253. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_trace_workspace.py +0 -0
  254. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_views_coverage2.py +0 -0
  255. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/tests/test_views_extra.py +0 -0
  256. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/trace_device_resolution.py +0 -0
  257. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/trace_schema.py +0 -0
  258. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/trace_workbook.py +0 -0
  259. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/transform_regex.py +0 -0
  260. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/urls.py +0 -0
  261. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/utils.py +0 -0
  262. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import/values.py +0 -0
  263. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/dependency_links.txt +0 -0
  264. {netbox_data_import-2.6.0 → netbox_data_import-2.7.0}/netbox_data_import.egg-info/top_level.txt +0 -0
  265. {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.6.0
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
  [![Coverage](https://img.shields.io/endpoint?url=https://marcinpsk.github.io/netbox-data-import-plugin/coverage/badge.json)](https://marcinpsk.github.io/netbox-data-import-plugin/coverage/)
32
33
  [![License](https://img.shields.io/github/license/marcinpsk/netbox-data-import-plugin)](LICENSE)
33
34
  [![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://www.python.org/)
34
- [![NetBox](https://img.shields.io/badge/NetBox-%E2%89%A54.6.0-blue)](https://github.com/netbox-community/netbox)
35
+ [![NetBox](https://img.shields.io/badge/NetBox-%E2%89%A54.6.9-blue)](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.0
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
  [![Coverage](https://img.shields.io/endpoint?url=https://marcinpsk.github.io/netbox-data-import-plugin/coverage/badge.json)](https://marcinpsk.github.io/netbox-data-import-plugin/coverage/)
9
9
  [![License](https://img.shields.io/github/license/marcinpsk/netbox-data-import-plugin)](LICENSE)
10
10
  [![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://www.python.org/)
11
- [![NetBox](https://img.shields.io/badge/NetBox-%E2%89%A54.6.0-blue)](https://github.com/netbox-community/netbox)
11
+ [![NetBox](https://img.shields.io/badge/NetBox-%E2%89%A54.6.9-blue)](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.0
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.6.0"
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.0"
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, so their job and task registrations run."""
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
+ )
@@ -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.fields import DynamicModelChoiceField
11
- from .adapters import get_adapter, selectable_adapter_choices
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()