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