nautobot 3.0.0a2__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl

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 (659) hide show
  1. nautobot/apps/choices.py +4 -2
  2. nautobot/apps/filters.py +7 -9
  3. nautobot/apps/models.py +2 -2
  4. nautobot/apps/ui.py +13 -1
  5. nautobot/apps/utils.py +8 -0
  6. nautobot/circuits/filters.py +3 -2
  7. nautobot/circuits/navigation.py +3 -2
  8. nautobot/circuits/templates/circuits/circuit_create.html +3 -3
  9. nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
  10. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
  11. nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
  12. nautobot/circuits/tests/integration/test_circuit.py +10 -13
  13. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
  14. nautobot/circuits/views.py +6 -2
  15. nautobot/cloud/filters.py +1 -1
  16. nautobot/cloud/navigation.py +3 -2
  17. nautobot/core/api/schema.py +1 -1
  18. nautobot/core/api/serializers.py +6 -1
  19. nautobot/core/api/urls.py +2 -0
  20. nautobot/core/api/views.py +12 -0
  21. nautobot/core/apps/__init__.py +11 -10
  22. nautobot/core/celery/__init__.py +3 -5
  23. nautobot/core/checks.py +46 -0
  24. nautobot/core/choices.py +1 -1
  25. nautobot/core/cli/bootstrap_v3_to_v5.py +105 -13
  26. nautobot/core/cli/migrate_deprecated_templates.py +227 -0
  27. nautobot/core/constants.py +3 -0
  28. nautobot/core/context_processors.py +9 -1
  29. nautobot/core/filters.py +4 -0
  30. nautobot/core/forms/__init__.py +2 -0
  31. nautobot/core/forms/forms.py +1 -1
  32. nautobot/core/forms/widgets.py +21 -2
  33. nautobot/core/jobs/__init__.py +62 -3
  34. nautobot/core/jobs/groups.py +31 -1
  35. nautobot/core/management/commands/generate_test_data.py +28 -9
  36. nautobot/core/models/__init__.py +11 -0
  37. nautobot/core/models/generics.py +9 -1
  38. nautobot/core/models/tree_queries.py +10 -5
  39. nautobot/core/models/utils.py +1 -1
  40. nautobot/core/settings.py +35 -19
  41. nautobot/core/settings.yaml +17 -33
  42. nautobot/core/signals.py +12 -1
  43. nautobot/core/tables.py +13 -6
  44. nautobot/core/templates/40x.html +1 -1
  45. nautobot/core/templates/500.html +2 -2
  46. nautobot/core/templates/admin/base.html +1 -2
  47. nautobot/core/templates/admin/change_list.html +9 -12
  48. nautobot/core/templates/admin/config/config.html +12 -12
  49. nautobot/core/templates/admin/index.html +3 -3
  50. nautobot/core/templates/base_django.html +1 -2
  51. nautobot/core/templates/buttons/export.html +1 -1
  52. nautobot/core/templates/components/button/dropdown.html +5 -3
  53. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  54. nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
  55. nautobot/core/templates/components/panel/panel.html +3 -3
  56. nautobot/core/templates/components/tab/content_wrapper.html +6 -7
  57. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
  58. nautobot/core/templates/echarts/echarts.html +22 -9
  59. nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
  60. nautobot/core/templates/generic/object_bulk_create.html +6 -5
  61. nautobot/core/templates/generic/object_bulk_delete.html +1 -1
  62. nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
  63. nautobot/core/templates/generic/object_bulk_edit.html +1 -1
  64. nautobot/core/templates/generic/object_bulk_import.html +1 -1
  65. nautobot/core/templates/generic/object_bulk_remove.html +2 -2
  66. nautobot/core/templates/generic/object_bulk_update.html +5 -4
  67. nautobot/core/templates/generic/object_create.html +5 -4
  68. nautobot/core/templates/generic/object_delete.html +1 -1
  69. nautobot/core/templates/generic/object_detail.html +1 -1
  70. nautobot/core/templates/generic/object_edit.html +1 -1
  71. nautobot/core/templates/generic/object_import.html +2 -1
  72. nautobot/core/templates/generic/object_list.html +12 -4
  73. nautobot/core/templates/generic/object_notes.html +5 -3
  74. nautobot/core/templates/generic/object_retrieve.html +4 -5
  75. nautobot/core/templates/graphene/graphiql.html +7 -8
  76. nautobot/core/templates/home.html +1 -1
  77. nautobot/core/templates/import_success.html +2 -1
  78. nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
  79. nautobot/core/templates/inc/created_updated.html +7 -3
  80. nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
  81. nautobot/core/templates/inc/footer.html +3 -1
  82. nautobot/core/templates/inc/form_static_field.html +6 -0
  83. nautobot/core/templates/inc/header.html +11 -1
  84. nautobot/core/templates/inc/image_attachments.html +2 -1
  85. nautobot/core/templates/inc/media.html +14 -0
  86. nautobot/core/templates/inc/nav_menu.html +3 -9
  87. nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
  88. nautobot/core/templates/inc/search_panel.html +4 -4
  89. nautobot/core/templates/login.html +4 -2
  90. nautobot/core/templates/nautobot_config.py.j2 +6 -11
  91. nautobot/core/templates/redoc_ui.html +7 -0
  92. nautobot/core/templates/rest_framework/api.html +103 -2
  93. nautobot/core/templates/search.html +1 -1
  94. nautobot/core/templates/swagger_ui.html +17 -3
  95. nautobot/core/templates/system_jobs/import_objects.html +1 -2
  96. nautobot/core/templates/utilities/confirmation_form.html +2 -2
  97. nautobot/core/templates/utilities/obj_table.html +10 -2
  98. nautobot/core/templates/utilities/render_field.html +7 -7
  99. nautobot/core/templates/utilities/render_jinja2.html +2 -2
  100. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +37 -4
  101. nautobot/core/templates/utilities/theme_preview.html +19 -3
  102. nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
  103. nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
  104. nautobot/core/templatetags/helpers.py +76 -18
  105. nautobot/core/testing/api.py +68 -9
  106. nautobot/core/testing/filters.py +0 -23
  107. nautobot/core/testing/integration.py +41 -17
  108. nautobot/core/testing/mixins.py +2 -0
  109. nautobot/core/testing/utils.py +18 -4
  110. nautobot/core/testing/views.py +104 -13
  111. nautobot/core/tests/integration/test_app_home.py +34 -30
  112. nautobot/core/tests/integration/test_app_navbar.py +3 -0
  113. nautobot/core/tests/integration/test_filters.py +48 -11
  114. nautobot/core/tests/integration/test_theme.py +22 -21
  115. nautobot/core/tests/nautobot_config.py +3 -0
  116. nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
  117. nautobot/core/tests/runner.py +8 -1
  118. nautobot/core/tests/test_api.py +5 -3
  119. nautobot/core/tests/test_breadcrumbs.py +27 -28
  120. nautobot/core/tests/test_checks.py +28 -0
  121. nautobot/core/tests/test_cli.py +40 -0
  122. nautobot/core/tests/test_config.py +2 -1
  123. nautobot/core/tests/test_forms.py +55 -13
  124. nautobot/core/tests/test_jobs.py +144 -3
  125. nautobot/core/tests/test_nautobot_server.py +2 -0
  126. nautobot/core/tests/test_navigations.py +76 -1
  127. nautobot/core/tests/test_patch_social_django.py +42 -0
  128. nautobot/core/tests/test_renderers.py +59 -0
  129. nautobot/core/tests/test_settings_schema.py +1 -0
  130. nautobot/core/tests/test_tables.py +3 -1
  131. nautobot/core/tests/test_templatetags_helpers.py +62 -13
  132. nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
  133. nautobot/core/tests/test_titles.py +0 -16
  134. nautobot/core/tests/test_tree_queries.py +14 -1
  135. nautobot/core/tests/test_ui.py +123 -4
  136. nautobot/core/tests/test_utils.py +72 -5
  137. nautobot/core/tests/test_views.py +159 -31
  138. nautobot/core/ui/breadcrumbs.py +70 -29
  139. nautobot/core/ui/bulk_buttons.py +1 -1
  140. nautobot/core/ui/choices.py +143 -27
  141. nautobot/core/ui/constants.py +76 -12
  142. nautobot/core/ui/echarts.py +15 -20
  143. nautobot/core/ui/object_detail.py +143 -55
  144. nautobot/core/ui/titles.py +3 -6
  145. nautobot/core/urls.py +20 -9
  146. nautobot/core/utils/cache.py +2 -1
  147. nautobot/core/utils/filtering.py +28 -18
  148. nautobot/core/utils/lookup.py +49 -8
  149. nautobot/core/utils/module_loading.py +21 -0
  150. nautobot/core/utils/patch_social_django.py +128 -0
  151. nautobot/core/views/__init__.py +38 -1
  152. nautobot/core/views/generic.py +3 -3
  153. nautobot/core/views/mixins.py +45 -22
  154. nautobot/core/views/renderers.py +4 -3
  155. nautobot/core/views/viewsets.py +2 -1
  156. nautobot/data_validation/apps.py +1 -5
  157. nautobot/data_validation/custom_validators.py +4 -4
  158. nautobot/data_validation/filters.py +1 -1
  159. nautobot/data_validation/forms.py +40 -0
  160. nautobot/data_validation/migrations/0001_initial.py +0 -7
  161. nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -14
  162. nautobot/data_validation/models.py +16 -7
  163. nautobot/data_validation/navigation.py +8 -1
  164. nautobot/data_validation/tables.py +12 -5
  165. nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
  166. nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
  167. nautobot/data_validation/tests/__init__.py +2 -2
  168. nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
  169. nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
  170. nautobot/data_validation/tests/test_filters.py +8 -6
  171. nautobot/data_validation/tests/test_models.py +15 -0
  172. nautobot/data_validation/tests/test_views.py +190 -32
  173. nautobot/data_validation/urls.py +2 -5
  174. nautobot/data_validation/views.py +73 -40
  175. nautobot/dcim/api/serializers.py +3 -13
  176. nautobot/dcim/apps.py +4 -0
  177. nautobot/dcim/choices.py +65 -0
  178. nautobot/dcim/constants.py +7 -0
  179. nautobot/dcim/custom_validators.py +84 -0
  180. nautobot/dcim/factory.py +1 -1
  181. nautobot/dcim/filter_mixins.py +353 -4
  182. nautobot/dcim/{filters/__init__.py → filters.py} +15 -36
  183. nautobot/dcim/forms.py +90 -4
  184. nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
  185. nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
  186. nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
  187. nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
  188. nautobot/dcim/migrations/0079_remove_device_location_tenant_name_uniqueness.py +16 -0
  189. nautobot/dcim/migrations/0080_device_name_data_migration.py +59 -0
  190. nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
  191. nautobot/dcim/models/device_component_templates.py +33 -1
  192. nautobot/dcim/models/device_components.py +98 -64
  193. nautobot/dcim/models/devices.py +30 -20
  194. nautobot/dcim/navigation.py +7 -6
  195. nautobot/dcim/tables/devices.py +18 -0
  196. nautobot/dcim/tables/devicetypes.py +8 -1
  197. nautobot/dcim/tables/racks.py +0 -2
  198. nautobot/dcim/tables/template_code.py +15 -15
  199. nautobot/dcim/templates/dcim/cable_connect.html +28 -112
  200. nautobot/dcim/templates/dcim/cable_trace.html +0 -4
  201. nautobot/dcim/templates/dcim/{cable_edit.html → cable_update.html} +1 -1
  202. nautobot/dcim/templates/dcim/consoleport.html +7 -6
  203. nautobot/dcim/templates/dcim/consoleserverport.html +7 -6
  204. nautobot/dcim/templates/dcim/device/config.html +2 -2
  205. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
  206. nautobot/dcim/templates/dcim/device/status.html +8 -8
  207. nautobot/dcim/templates/dcim/device.html +1 -1
  208. nautobot/dcim/templates/dcim/device_component_add.html +2 -2
  209. nautobot/dcim/templates/dcim/device_create.html +5 -3
  210. nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
  211. nautobot/dcim/templates/dcim/device_list.html +73 -10
  212. nautobot/dcim/templates/dcim/devicebay.html +1 -1
  213. nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
  214. nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
  215. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
  216. nautobot/dcim/templates/dcim/frontport.html +10 -9
  217. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  218. nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
  219. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
  220. nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
  221. nautobot/dcim/templates/dcim/interface.html +35 -7
  222. nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
  223. nautobot/dcim/templates/dcim/interface_edit.html +2 -0
  224. nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
  225. nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
  226. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  227. nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
  228. nautobot/dcim/templates/dcim/module/base.html +49 -9
  229. nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
  230. nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
  231. nautobot/dcim/templates/dcim/module_frontports.html +1 -1
  232. nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
  233. nautobot/dcim/templates/dcim/module_list.html +57 -8
  234. nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
  235. nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
  236. nautobot/dcim/templates/dcim/module_powerports.html +1 -1
  237. nautobot/dcim/templates/dcim/module_rearports.html +1 -1
  238. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  239. nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
  240. nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
  241. nautobot/dcim/templates/dcim/platform_create.html +1 -1
  242. nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
  243. nautobot/dcim/templates/dcim/powerport.html +6 -5
  244. nautobot/dcim/templates/dcim/rack_elevation_list.html +17 -5
  245. nautobot/dcim/templates/dcim/rack_retrieve.html +22 -15
  246. nautobot/dcim/templates/dcim/rearport.html +8 -7
  247. nautobot/dcim/templates/dcim/trace/cable.html +1 -1
  248. nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
  249. nautobot/dcim/templates/dcim/virtualchassis_update.html +15 -7
  250. nautobot/dcim/tests/integration/test_controller.py +4 -6
  251. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
  252. nautobot/dcim/tests/integration/test_create_device.py +0 -2
  253. nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
  254. nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
  255. nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
  256. nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
  257. nautobot/dcim/tests/test_api.py +194 -6
  258. nautobot/dcim/tests/test_custom_validators.py +229 -0
  259. nautobot/dcim/tests/test_filters.py +55 -7
  260. nautobot/dcim/tests/test_forms.py +110 -8
  261. nautobot/dcim/tests/test_graphql.py +44 -1
  262. nautobot/dcim/tests/test_models.py +328 -4
  263. nautobot/dcim/tests/test_tables.py +160 -0
  264. nautobot/dcim/tests/test_views.py +132 -29
  265. nautobot/dcim/urls.py +64 -21
  266. nautobot/dcim/utils.py +3 -3
  267. nautobot/dcim/views.py +777 -397
  268. nautobot/extras/api/views.py +60 -45
  269. nautobot/extras/choices.py +2 -13
  270. nautobot/extras/datasources/git.py +3 -1
  271. nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
  272. nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
  273. nautobot/extras/{filters/__init__.py → filters.py} +33 -48
  274. nautobot/extras/forms/forms.py +14 -15
  275. nautobot/extras/forms/mixins.py +0 -41
  276. nautobot/extras/jobs.py +2 -0
  277. nautobot/extras/jobs_ui.py +4 -3
  278. nautobot/extras/management/__init__.py +11 -0
  279. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
  280. nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
  281. nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
  282. nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
  283. nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
  284. nautobot/extras/models/__init__.py +1 -2
  285. nautobot/extras/models/approvals.py +33 -14
  286. nautobot/extras/models/change_logging.py +4 -0
  287. nautobot/extras/models/contacts.py +2 -0
  288. nautobot/extras/models/groups.py +44 -5
  289. nautobot/extras/models/jobs.py +60 -4
  290. nautobot/extras/models/mixins.py +28 -0
  291. nautobot/extras/models/models.py +23 -2
  292. nautobot/extras/models/secrets.py +1 -0
  293. nautobot/extras/models/statuses.py +0 -15
  294. nautobot/extras/navigation.py +13 -9
  295. nautobot/extras/plugins/__init__.py +33 -55
  296. nautobot/extras/plugins/marketplace_manifest.yml +49 -1
  297. nautobot/extras/plugins/tables.py +3 -3
  298. nautobot/extras/plugins/urls.py +2 -21
  299. nautobot/extras/plugins/utils.py +1 -33
  300. nautobot/extras/plugins/views.py +0 -9
  301. nautobot/extras/querysets.py +8 -0
  302. nautobot/extras/signals.py +20 -19
  303. nautobot/extras/tables.py +64 -68
  304. nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
  305. nautobot/extras/templates/extras/approval_dashboard.html +7 -5
  306. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
  307. nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
  308. nautobot/extras/templates/extras/configcontext_update.html +1 -0
  309. nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
  310. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
  311. nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
  312. nautobot/extras/templates/extras/gitrepository_result.html +0 -2
  313. nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
  314. nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
  315. nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
  316. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
  317. nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
  318. nautobot/extras/templates/extras/inc/json_format.html +10 -3
  319. nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
  320. nautobot/extras/templates/extras/job.html +29 -12
  321. nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
  322. nautobot/extras/templates/extras/job_edit.html +52 -46
  323. nautobot/extras/templates/extras/job_list.html +29 -25
  324. nautobot/extras/templates/extras/marketplace.html +5 -9
  325. nautobot/extras/templates/extras/object_configcontext.html +1 -1
  326. nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
  327. nautobot/extras/templates/extras/objectchange_retrieve.html +19 -39
  328. nautobot/extras/templates/extras/plugin_detail.html +29 -24
  329. nautobot/extras/templates/extras/plugins_list.html +16 -26
  330. nautobot/extras/templates/extras/role_retrieve.html +64 -0
  331. nautobot/extras/templates/extras/scheduledjob.html +4 -2
  332. nautobot/extras/templates/extras/secret_create.html +1 -1
  333. nautobot/extras/templatetags/custom_links.py +12 -12
  334. nautobot/extras/templatetags/job_buttons.py +14 -12
  335. nautobot/extras/test_jobs/invalid_import.py +9 -0
  336. nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
  337. nautobot/extras/test_jobs/missing_import.py +11 -0
  338. nautobot/extras/tests/integration/test_computedfields.py +8 -9
  339. nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
  340. nautobot/extras/tests/integration/test_customfields.py +9 -10
  341. nautobot/extras/tests/integration/test_dynamicgroups.py +12 -9
  342. nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
  343. nautobot/extras/tests/integration/test_plugins.py +18 -6
  344. nautobot/extras/tests/integration/test_relationships.py +0 -2
  345. nautobot/extras/tests/test_api.py +90 -18
  346. nautobot/extras/tests/test_approvals.py +38 -38
  347. nautobot/extras/tests/test_changelog.py +59 -5
  348. nautobot/extras/tests/test_customfields.py +22 -13
  349. nautobot/extras/tests/test_customfields_filters.py +479 -0
  350. nautobot/extras/tests/test_dynamicgroups.py +39 -1
  351. nautobot/extras/tests/test_filters.py +57 -22
  352. nautobot/extras/tests/test_forms.py +18 -21
  353. nautobot/extras/tests/test_jobs.py +25 -4
  354. nautobot/extras/tests/test_migrations.py +1 -0
  355. nautobot/extras/tests/test_models.py +51 -33
  356. nautobot/extras/tests/test_plugins.py +36 -10
  357. nautobot/extras/tests/test_utils.py +3 -4
  358. nautobot/extras/tests/test_views.py +52 -112
  359. nautobot/extras/urls.py +0 -14
  360. nautobot/extras/views.py +164 -71
  361. nautobot/ipam/factory.py +7 -0
  362. nautobot/ipam/filter_mixins.py +38 -0
  363. nautobot/ipam/filters.py +53 -38
  364. nautobot/ipam/formfields.py +1 -1
  365. nautobot/ipam/forms.py +6 -3
  366. nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
  367. nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
  368. nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
  369. nautobot/ipam/models.py +29 -2
  370. nautobot/ipam/navigation.py +3 -2
  371. nautobot/ipam/signals.py +71 -0
  372. nautobot/ipam/tables.py +19 -6
  373. nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
  374. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
  375. nautobot/ipam/templates/ipam/ipaddress.html +14 -0
  376. nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
  377. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
  378. nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
  379. nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
  380. nautobot/ipam/templates/ipam/namespace_update.html +15 -0
  381. nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
  382. nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
  383. nautobot/ipam/templates/ipam/prefix_list.html +14 -13
  384. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  385. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  386. nautobot/ipam/tests/migration/test_migrations.py +89 -0
  387. nautobot/ipam/tests/test_api.py +13 -6
  388. nautobot/ipam/tests/test_filters.py +36 -1
  389. nautobot/ipam/tests/test_forms.py +1 -1
  390. nautobot/ipam/tests/test_models.py +44 -2
  391. nautobot/ipam/tests/test_tables.py +1 -2
  392. nautobot/ipam/tests/test_utils.py +1 -1
  393. nautobot/ipam/tests/test_views.py +13 -14
  394. nautobot/ipam/ui.py +0 -17
  395. nautobot/ipam/utils/migrations.py +16 -2
  396. nautobot/ipam/utils/testing.py +9 -3
  397. nautobot/ipam/views.py +53 -11
  398. nautobot/load_balancers/__init__.py +0 -0
  399. nautobot/load_balancers/api/__init__.py +1 -0
  400. nautobot/load_balancers/api/serializers.py +75 -0
  401. nautobot/load_balancers/api/urls.py +23 -0
  402. nautobot/load_balancers/api/views.py +61 -0
  403. nautobot/load_balancers/apps.py +17 -0
  404. nautobot/load_balancers/choices.py +167 -0
  405. nautobot/load_balancers/filters.py +225 -0
  406. nautobot/load_balancers/forms.py +532 -0
  407. nautobot/load_balancers/management/commands/__init__.py +0 -0
  408. nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
  409. nautobot/load_balancers/migrations/0001_initial.py +465 -0
  410. nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
  411. nautobot/load_balancers/migrations/__init__.py +0 -0
  412. nautobot/load_balancers/models.py +423 -0
  413. nautobot/load_balancers/navigation.py +80 -0
  414. nautobot/load_balancers/tables.py +255 -0
  415. nautobot/load_balancers/tests/__init__.py +474 -0
  416. nautobot/load_balancers/tests/test_api.py +353 -0
  417. nautobot/load_balancers/tests/test_filters.py +134 -0
  418. nautobot/load_balancers/tests/test_forms.py +266 -0
  419. nautobot/load_balancers/tests/test_models.py +195 -0
  420. nautobot/load_balancers/tests/test_views.py +229 -0
  421. nautobot/load_balancers/urls.py +17 -0
  422. nautobot/load_balancers/views.py +248 -0
  423. nautobot/project-static/dist/css/github-dark.min.css +10 -0
  424. nautobot/project-static/dist/css/github.min.css +10 -0
  425. nautobot/project-static/dist/css/nautobot.css +1 -11
  426. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  427. nautobot/project-static/dist/js/libraries.js +1 -1
  428. nautobot/project-static/dist/js/libraries.js.map +1 -1
  429. nautobot/project-static/dist/js/nautobot.js +1 -1
  430. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  431. nautobot/project-static/js/cabletrace.js +1 -1
  432. nautobot/project-static/js/forms.js +13 -0
  433. nautobot/project-static/js/interface_filtering.js +20 -16
  434. nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
  435. nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
  436. nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
  437. nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
  438. nautobot/project-static/nautobot-icons/cloud.svg +1 -1
  439. nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
  440. nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
  441. nautobot/project-static/nautobot-icons/elements.svg +1 -1
  442. nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
  443. nautobot/project-static/nautobot-icons/hammer.svg +1 -1
  444. nautobot/project-static/nautobot-icons/organization.svg +3 -0
  445. nautobot/project-static/nautobot-icons/secrets.svg +1 -1
  446. nautobot/project-static/nautobot-icons/security.svg +3 -0
  447. nautobot/project-static/nautobot-icons/server.svg +1 -1
  448. nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
  449. nautobot/project-static/nautobot-icons/star.svg +1 -1
  450. nautobot/tenancy/api/serializers.py +1 -0
  451. nautobot/tenancy/api/views.py +2 -1
  452. nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
  453. nautobot/tenancy/navigation.py +3 -1
  454. nautobot/tenancy/tests/test_filters.py +0 -2
  455. nautobot/tenancy/views.py +2 -1
  456. nautobot/ui/package-lock.json +87 -4
  457. nautobot/ui/package.json +2 -1
  458. nautobot/ui/src/js/collapse.js +3 -3
  459. nautobot/ui/src/js/nautobot.js +16 -1
  460. nautobot/ui/src/js/select2.js +53 -2
  461. nautobot/ui/src/scss/colors.scss +1 -1
  462. nautobot/ui/src/scss/nautobot.scss +112 -30
  463. nautobot/ui/webpack.config.js +13 -0
  464. nautobot/users/templates/users/preferences.html +11 -2
  465. nautobot/users/templates/users/profile.html +45 -12
  466. nautobot/users/templates/users/sessionkey_delete.html +1 -1
  467. nautobot/users/tests/test_api.py +4 -0
  468. nautobot/users/views.py +4 -2
  469. nautobot/virtualization/filters.py +6 -1
  470. nautobot/virtualization/models.py +1 -68
  471. nautobot/virtualization/navigation.py +3 -2
  472. nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
  473. nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
  474. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
  475. nautobot/virtualization/tests/test_api.py +3 -0
  476. nautobot/virtualization/tests/test_filters.py +10 -1
  477. nautobot/virtualization/tests/test_models.py +45 -4
  478. nautobot/virtualization/views.py +4 -1
  479. nautobot/vpn/__init__.py +0 -0
  480. nautobot/vpn/api/serializers.py +113 -0
  481. nautobot/vpn/api/urls.py +19 -0
  482. nautobot/vpn/api/views.py +70 -0
  483. nautobot/vpn/apps.py +8 -0
  484. nautobot/vpn/choices.py +171 -0
  485. nautobot/vpn/factory.py +219 -0
  486. nautobot/vpn/filters.py +234 -0
  487. nautobot/vpn/forms.py +487 -0
  488. nautobot/vpn/homepage.py +19 -0
  489. nautobot/vpn/migrations/0001_initial.py +541 -0
  490. nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
  491. nautobot/vpn/migrations/__init__.py +0 -0
  492. nautobot/vpn/models.py +535 -0
  493. nautobot/vpn/navigation.py +98 -0
  494. nautobot/vpn/tables.py +383 -0
  495. nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
  496. nautobot/vpn/tests/__init__.py +0 -0
  497. nautobot/vpn/tests/test_api.py +336 -0
  498. nautobot/vpn/tests/test_filters.py +139 -0
  499. nautobot/vpn/tests/test_forms.py +293 -0
  500. nautobot/vpn/tests/test_models.py +147 -0
  501. nautobot/vpn/tests/test_views.py +300 -0
  502. nautobot/vpn/urls.py +16 -0
  503. nautobot/vpn/views.py +495 -0
  504. nautobot/wireless/navigation.py +3 -2
  505. nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
  506. nautobot/wireless/tests/test_api.py +1 -1
  507. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +15 -15
  508. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +514 -572
  509. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +1 -0
  510. nautobot/circuits/templates/circuits/circuit.html +0 -2
  511. nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
  512. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
  513. nautobot/circuits/templates/circuits/circuit_update.html +0 -1
  514. nautobot/circuits/templates/circuits/circuittermination.html +0 -2
  515. nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
  516. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
  517. nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
  518. nautobot/circuits/templates/circuits/circuittype.html +0 -2
  519. nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
  520. nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
  521. nautobot/circuits/templates/circuits/provider.html +0 -2
  522. nautobot/circuits/templates/circuits/provider_edit.html +0 -2
  523. nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
  524. nautobot/circuits/templates/circuits/provider_update.html +0 -1
  525. nautobot/circuits/templates/circuits/providernetwork.html +0 -2
  526. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
  527. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
  528. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
  529. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
  530. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
  531. nautobot/core/templates/buttons/import.html +0 -9
  532. nautobot/data_validation/template_content.py +0 -42
  533. nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
  534. nautobot/dcim/filters/mixins.py +0 -354
  535. nautobot/dcim/templates/dcim/controller/base.html +0 -2
  536. nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
  537. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
  538. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
  539. nautobot/dcim/templates/dcim/device/base.html +0 -2
  540. nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
  541. nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
  542. nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
  543. nautobot/dcim/templates/dcim/device/frontports.html +0 -2
  544. nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
  545. nautobot/dcim/templates/dcim/device/inventory.html +0 -2
  546. nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
  547. nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
  548. nautobot/dcim/templates/dcim/device/powerports.html +0 -2
  549. nautobot/dcim/templates/dcim/device/rearports.html +0 -2
  550. nautobot/dcim/templates/dcim/device/wireless.html +0 -2
  551. nautobot/dcim/templates/dcim/device_component.html +0 -2
  552. nautobot/dcim/templates/dcim/device_edit.html +0 -2
  553. nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
  554. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
  555. nautobot/dcim/templates/dcim/devicetype.html +0 -2
  556. nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
  557. nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
  558. nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
  559. nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
  560. nautobot/dcim/templates/dcim/location.html +0 -2
  561. nautobot/dcim/templates/dcim/location_edit.html +0 -2
  562. nautobot/dcim/templates/dcim/location_retrieve.html +0 -243
  563. nautobot/dcim/templates/dcim/locationtype.html +0 -2
  564. nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
  565. nautobot/dcim/templates/dcim/manufacturer.html +0 -2
  566. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
  567. nautobot/dcim/templates/dcim/platform.html +0 -2
  568. nautobot/dcim/templates/dcim/powerfeed.html +0 -2
  569. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
  570. nautobot/dcim/templates/dcim/powerpanel.html +0 -2
  571. nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
  572. nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
  573. nautobot/dcim/templates/dcim/rack.html +0 -2
  574. nautobot/dcim/templates/dcim/rack_edit.html +0 -2
  575. nautobot/dcim/templates/dcim/rackgroup.html +0 -2
  576. nautobot/dcim/templates/dcim/rackreservation.html +0 -2
  577. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
  578. nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
  579. nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
  580. nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
  581. nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
  582. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
  583. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
  584. nautobot/dcim/ui.py +0 -29
  585. nautobot/extras/templates/extras/computedfield.html +0 -2
  586. nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
  587. nautobot/extras/templates/extras/configcontext.html +0 -2
  588. nautobot/extras/templates/extras/configcontext_edit.html +0 -2
  589. nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
  590. nautobot/extras/templates/extras/configcontextschema.html +0 -2
  591. nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
  592. nautobot/extras/templates/extras/contact_retrieve.html +0 -2
  593. nautobot/extras/templates/extras/customfield.html +0 -2
  594. nautobot/extras/templates/extras/customfield_edit.html +0 -2
  595. nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
  596. nautobot/extras/templates/extras/customlink.html +0 -2
  597. nautobot/extras/templates/extras/dynamicgroup.html +0 -2
  598. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
  599. nautobot/extras/templates/extras/exporttemplate.html +0 -2
  600. nautobot/extras/templates/extras/gitrepository.html +0 -2
  601. nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
  602. nautobot/extras/templates/extras/graphqlquery.html +0 -2
  603. nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
  604. nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -97
  605. nautobot/extras/templates/extras/job_detail.html +0 -2
  606. nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
  607. nautobot/extras/templates/extras/jobhook.html +0 -2
  608. nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
  609. nautobot/extras/templates/extras/jobresult.html +0 -2
  610. nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
  611. nautobot/extras/templates/extras/note.html +0 -2
  612. nautobot/extras/templates/extras/note_retrieve.html +0 -1
  613. nautobot/extras/templates/extras/object_changelog.html +0 -2
  614. nautobot/extras/templates/extras/object_notes.html +0 -2
  615. nautobot/extras/templates/extras/objectchange.html +0 -2
  616. nautobot/extras/templates/extras/objectchange_list.html +0 -3
  617. nautobot/extras/templates/extras/relationship.html +0 -1
  618. nautobot/extras/templates/extras/secret.html +0 -1
  619. nautobot/extras/templates/extras/secret_edit.html +0 -1
  620. nautobot/extras/templates/extras/secretsgroup.html +0 -2
  621. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
  622. nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
  623. nautobot/extras/templates/extras/status.html +0 -2
  624. nautobot/extras/templates/extras/tag.html +0 -2
  625. nautobot/extras/templates/extras/tag_edit.html +0 -2
  626. nautobot/extras/templates/extras/tag_retrieve.html +0 -2
  627. nautobot/extras/templates/extras/team_retrieve.html +0 -2
  628. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
  629. nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
  630. nautobot/ipam/templates/ipam/prefix.html +0 -2
  631. nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
  632. nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
  633. nautobot/ipam/templates/ipam/rir.html +0 -2
  634. nautobot/ipam/templates/ipam/routetarget.html +0 -1
  635. nautobot/ipam/templates/ipam/service.html +0 -2
  636. nautobot/ipam/templates/ipam/service_edit.html +0 -2
  637. nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
  638. nautobot/ipam/templates/ipam/vlan.html +0 -2
  639. nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
  640. nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
  641. nautobot/ipam/templates/ipam/vlangroup.html +0 -2
  642. nautobot/ipam/templates/ipam/vrf.html +0 -1
  643. nautobot/tenancy/templates/tenancy/tenant.html +0 -2
  644. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
  645. nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
  646. nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
  647. nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
  648. nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
  649. nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
  650. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
  651. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
  652. nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
  653. nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
  654. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
  655. /nautobot/dcim/templates/dcim/{cable.html → cable_retrieve.html} +0 -0
  656. /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
  657. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
  658. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
  659. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
@@ -251,3 +251,31 @@ class SavedViewMixin(models.Model):
251
251
  abstract = True
252
252
 
253
253
  is_saved_view_model = True
254
+
255
+
256
+ class DataComplianceModelMixin:
257
+ """
258
+ Adds a `get_data_compliance_url` that can be applied to instances.
259
+ """
260
+
261
+ is_data_compliance_model = True
262
+
263
+ def get_data_compliance_url(self, api=False):
264
+ """Return the data compliance URL for a given instance."""
265
+ # If is_data_compliance_model overridden should allow to opt out
266
+ if not self.is_data_compliance_model:
267
+ return None
268
+ route = get_route_for_model(self, "data-compliance", api=api)
269
+
270
+ # Iterate the pk-like fields and try to get a URL, or return None.
271
+ fields = ["pk", "slug"]
272
+ for field in fields:
273
+ if not hasattr(self, field):
274
+ continue
275
+
276
+ try:
277
+ return reverse(route, kwargs={field: getattr(self, field)})
278
+ except NoReverseMatch:
279
+ continue
280
+
281
+ return None
@@ -31,7 +31,13 @@ from nautobot.extras.choices import (
31
31
  )
32
32
  from nautobot.extras.constants import HTTP_CONTENT_TYPE_JSON
33
33
  from nautobot.extras.models import ChangeLoggedModel
34
- from nautobot.extras.models.mixins import ContactMixin, DynamicGroupsModelMixin, NotesMixin, SavedViewMixin
34
+ from nautobot.extras.models.mixins import (
35
+ ContactMixin,
36
+ DataComplianceModelMixin,
37
+ DynamicGroupsModelMixin,
38
+ NotesMixin,
39
+ SavedViewMixin,
40
+ )
35
41
  from nautobot.extras.models.relationships import RelationshipModel
36
42
  from nautobot.extras.querysets import ConfigContextQuerySet, NotesQuerySet
37
43
  from nautobot.extras.utils import extras_features, FeatureQuery, image_upload
@@ -127,6 +133,7 @@ class ConfigContext(
127
133
  tenant_groups = models.ManyToManyField(to="tenancy.TenantGroup", related_name="+", blank=True)
128
134
  tenants = models.ManyToManyField(to="tenancy.Tenant", related_name="+", blank=True)
129
135
  tags = models.ManyToManyField(to="extras.Tag", related_name="+", blank=True)
136
+ device_families = models.ManyToManyField("dcim.DeviceFamily", related_name="+", blank=True)
130
137
 
131
138
  # Due to feature flag CONFIG_CONTEXT_DYNAMIC_GROUPS_ENABLED this field will remain empty unless set to True.
132
139
  dynamic_groups = models.ManyToManyField(
@@ -353,6 +360,14 @@ class CustomLink(
353
360
  )
354
361
  new_window = models.BooleanField(help_text="Force link to open in a new window")
355
362
 
363
+ is_data_compliance_model = False
364
+
365
+ @property
366
+ def button_class_css_class(self):
367
+ if self.button_class == ButtonClassChoices.CLASS_DEFAULT:
368
+ return "secondary"
369
+ return self.button_class
370
+
356
371
  class Meta:
357
372
  ordering = ["group_name", "weight", "name"]
358
373
 
@@ -577,6 +592,7 @@ class FileAttachment(BaseModel):
577
592
  mimetype = models.CharField(max_length=CHARFIELD_MAX_LENGTH)
578
593
 
579
594
  is_metadata_associable_model = False
595
+ is_data_compliance_model = False
580
596
 
581
597
  natural_key_field_names = ["pk"]
582
598
 
@@ -638,6 +654,8 @@ class FileProxy(BaseModel):
638
654
  uploaded_at = models.DateTimeField(auto_now_add=True)
639
655
  job_result = models.ForeignKey(to=JobResult, null=True, blank=True, on_delete=models.CASCADE, related_name="files")
640
656
 
657
+ is_data_compliance_model = False
658
+
641
659
  def __str__(self):
642
660
  return self.name
643
661
 
@@ -676,6 +694,7 @@ class FileProxy(BaseModel):
676
694
  class GraphQLQuery(
677
695
  ChangeLoggedModel,
678
696
  ContactMixin,
697
+ DataComplianceModelMixin,
679
698
  DynamicGroupsModelMixin,
680
699
  NotesMixin,
681
700
  SavedViewMixin,
@@ -816,7 +835,7 @@ class ImageAttachment(BaseModel):
816
835
 
817
836
 
818
837
  @extras_features("graphql", "webhooks")
819
- class Note(ChangeLoggedModel, BaseModel):
838
+ class Note(ChangeLoggedModel, DataComplianceModelMixin, BaseModel):
820
839
  """
821
840
  Notes allow anyone with proper permissions to add a note to an object.
822
841
  """
@@ -877,6 +896,7 @@ class SavedView(BaseModel, ChangeLoggedModel):
877
896
  is_shared = models.BooleanField(default=True)
878
897
 
879
898
  documentation_static_path = "docs/user-guide/platform-functionality/savedview.html"
899
+ is_data_compliance_model = False
880
900
 
881
901
  class Meta:
882
902
  ordering = ["owner", "view", "name"]
@@ -905,6 +925,7 @@ class UserSavedViewAssociation(BaseModel):
905
925
  user = models.ForeignKey("users.User", on_delete=models.CASCADE, related_name="saved_view_assignments")
906
926
  view_name = models.CharField(max_length=CHARFIELD_MAX_LENGTH)
907
927
  is_metadata_associable_model = False
928
+ is_data_compliance_model = False
908
929
 
909
930
  class Meta:
910
931
  unique_together = [["user", "view_name"]]
@@ -141,6 +141,7 @@ class SecretsGroupAssociation(BaseModel):
141
141
  natural_key_field_names = ["secrets_group", "access_type", "secret_type", "secret"]
142
142
 
143
143
  documentation_static_path = "docs/user-guide/platform-functionality/secret.html"
144
+ is_data_compliance_model = False
144
145
 
145
146
  class Meta:
146
147
  unique_together = (
@@ -7,7 +7,6 @@ from django.utils.hashable import make_hashable
7
7
 
8
8
  from nautobot.core.models.fields import ForeignKeyLimitedByContentTypes
9
9
  from nautobot.core.models.name_color_content_types import NameColorContentTypesModel
10
- from nautobot.core.utils.deprecation import class_deprecated
11
10
  from nautobot.extras.utils import extras_features, FeatureQuery
12
11
 
13
12
 
@@ -98,17 +97,3 @@ class StatusField(ForeignKeyLimitedByContentTypes):
98
97
  f"get_{self.name}_color",
99
98
  partialmethod(_get_FIELD_color, field=self),
100
99
  )
101
-
102
-
103
- @class_deprecated(message="please directly declare `status = StatusField(...)` on your model instead")
104
- class StatusModel(models.Model):
105
- """
106
- Deprecated abstract base class for any model which may have statuses.
107
-
108
- Just directly include a StatusField instead for any new models.
109
- """
110
-
111
- status = StatusField(null=True) # for backward compatibility
112
-
113
- class Meta:
114
- abstract = True
@@ -4,11 +4,13 @@ from nautobot.core.apps import (
4
4
  NavMenuItem,
5
5
  NavMenuTab,
6
6
  )
7
+ from nautobot.core.ui.choices import NavigationIconChoices, NavigationWeightChoices
7
8
 
8
9
  menu_items = (
9
10
  NavMenuTab(
10
11
  name="Approvals",
11
- icon="checkbox-circle",
12
+ icon=NavigationIconChoices.APPROVAL_WORKFLOWS,
13
+ weight=NavigationWeightChoices.APPROVAL_WORKFLOWS,
12
14
  groups=(
13
15
  NavMenuGroup(
14
16
  name="Approval Workflows",
@@ -38,7 +40,8 @@ menu_items = (
38
40
  ),
39
41
  NavMenuTab(
40
42
  name="Organization",
41
- weight=100,
43
+ icon=NavigationIconChoices.ORGANIZATION,
44
+ weight=NavigationWeightChoices.ORGANIZATION,
42
45
  groups=(
43
46
  NavMenuGroup(
44
47
  name="Contacts",
@@ -140,8 +143,8 @@ menu_items = (
140
143
  ),
141
144
  NavMenuTab(
142
145
  name="Secrets",
143
- icon="secrets",
144
- weight=700,
146
+ icon=NavigationIconChoices.SECRETS,
147
+ weight=NavigationWeightChoices.SECRETS,
145
148
  groups=(
146
149
  NavMenuGroup(
147
150
  name="Secrets",
@@ -169,8 +172,8 @@ menu_items = (
169
172
  ),
170
173
  NavMenuTab(
171
174
  name="Jobs",
172
- icon="share",
173
- weight=800,
175
+ icon=NavigationIconChoices.JOBS,
176
+ weight=NavigationWeightChoices.JOBS,
174
177
  groups=(
175
178
  NavMenuGroup(
176
179
  name="Jobs",
@@ -258,7 +261,8 @@ menu_items = (
258
261
  ),
259
262
  NavMenuTab(
260
263
  name="Extensibility",
261
- weight=900,
264
+ icon=NavigationIconChoices.EXTENSIBILITY,
265
+ weight=NavigationWeightChoices.EXTENSIBILITY,
262
266
  groups=(
263
267
  NavMenuGroup(
264
268
  name="Logging",
@@ -532,8 +536,8 @@ menu_items = (
532
536
  ),
533
537
  NavMenuTab(
534
538
  name="Apps",
535
- icon="elements",
536
- weight=5000,
539
+ icon=NavigationIconChoices.APPS,
540
+ weight=NavigationWeightChoices.APPS,
537
541
  groups=(
538
542
  NavMenuGroup(
539
543
  name="General",
@@ -5,9 +5,10 @@ import inspect
5
5
  from logging import getLogger
6
6
 
7
7
  from django.conf import settings
8
+ from django.conf.urls import include
8
9
  from django.core.exceptions import ValidationError
9
10
  from django.template.loader import get_template
10
- from django.urls import get_resolver, URLPattern
11
+ from django.urls import clear_url_caches, get_resolver, path, URLPattern
11
12
  from packaging import version
12
13
 
13
14
  from nautobot.core.apps import (
@@ -17,10 +18,9 @@ from nautobot.core.apps import (
17
18
  register_menu_items,
18
19
  )
19
20
  from nautobot.core.signals import nautobot_database_ready
20
- from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
21
+ from nautobot.core.utils.module_loading import import_string_optional
21
22
  from nautobot.extras.choices import BannerClassChoices
22
23
  from nautobot.extras.plugins.exceptions import PluginImproperlyConfigured
23
- from nautobot.extras.plugins.utils import import_object
24
24
  from nautobot.extras.registry import register_datasource_contents, registry
25
25
  from nautobot.extras.secrets import register_secrets_provider
26
26
 
@@ -105,9 +105,24 @@ class NautobotAppConfig(NautobotConfig):
105
105
  """Callback after plugin app is loaded."""
106
106
  # We don't call super().ready here because we don't need or use the on-ready behavior of a core Nautobot app
107
107
 
108
+ from nautobot.extras.plugins.urls import BASE_URL_TO_APP_LABEL, plugin_api_patterns, plugin_patterns
109
+
108
110
  # Introspect URL patterns and models to make available to the installed-plugins detail UI view.
109
- urlpatterns = import_object(f"{self.__module__}.urls.urlpatterns")
110
- api_urlpatterns = import_object(f"{self.__module__}.api.urls.urlpatterns")
111
+ urlpatterns = import_string_optional(f"{self.__module__}.urls.urlpatterns")
112
+ api_urlpatterns = import_string_optional(f"{self.__module__}.api.urls.urlpatterns")
113
+
114
+ base_url = self.base_url or self.label
115
+
116
+ if urlpatterns is not None:
117
+ plugin_patterns.append(path(f"{base_url}/", include((urlpatterns, self.label))))
118
+
119
+ if api_urlpatterns is not None:
120
+ plugin_api_patterns.append(path(f"{base_url}/", include((api_urlpatterns, f"{self.label}-api"))))
121
+
122
+ if any([urlpatterns, api_urlpatterns]):
123
+ clear_url_caches()
124
+
125
+ BASE_URL_TO_APP_LABEL[base_url] = self.label
111
126
 
112
127
  self.features = {
113
128
  "api_urlpatterns": sorted(
@@ -123,36 +138,31 @@ class NautobotAppConfig(NautobotConfig):
123
138
  }
124
139
 
125
140
  # Register banner function (if defined)
126
- banner_function = import_object(f"{self.__module__}.{self.banner_function}")
127
- if banner_function is not None:
141
+ if banner_function := import_string_optional(f"{self.__module__}.{self.banner_function}"):
128
142
  register_banner_function(banner_function)
129
143
  self.features["banner"] = True
130
144
 
131
145
  # Register model validators (if defined)
132
- validators = import_object(f"{self.__module__}.{self.custom_validators}")
133
- if validators is not None:
146
+ if validators := import_string_optional(f"{self.__module__}.{self.custom_validators}"):
134
147
  register_custom_validators(validators)
135
148
  self.features["custom_validators"] = sorted(set(validator.model for validator in validators))
136
149
 
137
150
  # Register datasource contents (if defined)
138
- datasource_contents = import_object(f"{self.__module__}.{self.datasource_contents}")
139
- if datasource_contents is not None:
151
+ if datasource_contents := import_string_optional(f"{self.__module__}.{self.datasource_contents}"):
140
152
  register_datasource_contents(datasource_contents)
141
153
  self.features["datasource_contents"] = datasource_contents
142
154
 
143
155
  # Register GraphQL types (if defined)
144
- graphql_types = import_object(f"{self.__module__}.{self.graphql_types}")
145
- if graphql_types is not None:
156
+ if graphql_types := import_string_optional(f"{self.__module__}.{self.graphql_types}"):
146
157
  register_graphql_types(graphql_types)
147
158
 
148
159
  # Import jobs (if present)
149
160
  # Note that we do *not* auto-call `register_jobs()` - the App is responsible for doing so when imported.
150
- jobs = import_object(f"{self.__module__}.{self.jobs}")
151
- if jobs is not None:
161
+ if jobs := import_string_optional(f"{self.__module__}.{self.jobs}"):
152
162
  self.features["jobs"] = jobs
153
163
 
154
164
  # Import metrics (if present)
155
- metrics = import_object(f"{self.__module__}.{self.metrics}")
165
+ metrics = import_string_optional(f"{self.__module__}.{self.metrics}")
156
166
  if metrics is not None and self.name not in settings.METRICS_DISABLED_APPS:
157
167
  register_metrics(metrics)
158
168
  self.features["metrics"] = [] # Initialize as empty, to be filled by the signal handler
@@ -161,19 +171,16 @@ class NautobotAppConfig(NautobotConfig):
161
171
  nautobot_database_ready.connect(signal_callback, sender=self)
162
172
 
163
173
  # Register plugin navigation menu items (if defined)
164
- menu_items = import_object(f"{self.__module__}.{self.menu_items}")
165
- if menu_items is not None:
174
+ if menu_items := import_string_optional(f"{self.__module__}.{self.menu_items}"):
166
175
  register_plugin_menu_items(self.verbose_name, menu_items)
167
176
  self.features["nav_menu"] = menu_items
168
177
 
169
- homepage_layout = import_object(f"{self.__module__}.{self.homepage_layout}")
170
- if homepage_layout is not None:
178
+ if homepage_layout := import_string_optional(f"{self.__module__}.{self.homepage_layout}"):
171
179
  register_homepage_panels(self.path, self.label, homepage_layout)
172
180
  self.features["home_page"] = homepage_layout
173
181
 
174
182
  # Register template content (if defined)
175
- template_extensions = import_object(f"{self.__module__}.{self.template_extensions}")
176
- if template_extensions is not None:
183
+ if template_extensions := import_string_optional(f"{self.__module__}.{self.template_extensions}"):
177
184
  register_template_extensions(template_extensions)
178
185
  self.features["template_extensions"] = sorted(set(extension.model for extension in template_extensions))
179
186
 
@@ -185,15 +192,13 @@ class NautobotAppConfig(NautobotConfig):
185
192
  pass
186
193
 
187
194
  # Register secrets providers (if any)
188
- secrets_providers = import_object(f"{self.__module__}.{self.secrets_providers}")
189
- if secrets_providers is not None:
195
+ if secrets_providers := import_string_optional(f"{self.__module__}.{self.secrets_providers}"):
190
196
  for secrets_provider in secrets_providers:
191
197
  register_secrets_provider(secrets_provider)
192
198
  self.features["secrets_providers"] = secrets_providers
193
199
 
194
200
  # Register custom filters (if any)
195
- filter_extensions = import_object(f"{self.__module__}.{self.filter_extensions}")
196
- if filter_extensions is not None:
201
+ if filter_extensions := import_string_optional(f"{self.__module__}.{self.filter_extensions}"):
197
202
  register_filter_extensions(filter_extensions, self.name)
198
203
  self.features["filter_extensions"] = {"filterset_fields": [], "filterform_fields": []}
199
204
  for filter_extension in filter_extensions:
@@ -207,8 +212,7 @@ class NautobotAppConfig(NautobotConfig):
207
212
  )
208
213
 
209
214
  # Register override view (if any)
210
- override_views = import_object(f"{self.__module__}.{self.override_views}")
211
- if override_views is not None:
215
+ if override_views := import_string_optional(f"{self.__module__}.{self.override_views}"):
212
216
  for qualified_view_name, view in override_views.items():
213
217
  view_class_name = view.view_class.__name__ if hasattr(view, "view_class") else view.cls.__name__
214
218
  self.features.setdefault("overridden_views", []).append(
@@ -272,17 +276,11 @@ class NautobotAppConfig(NautobotConfig):
272
276
 
273
277
  def _register_table_extensions(self):
274
278
  """Register tables extensions (if any)."""
275
- table_extensions = import_object(f"{self.__module__}.{self.table_extensions}")
276
- if table_extensions is not None:
279
+ if table_extensions := import_string_optional(f"{self.__module__}.{self.table_extensions}"):
277
280
  register_table_extensions(table_extensions, self.name)
278
281
  self.features["table_extensions"] = get_table_extension_features(table_extensions)
279
282
 
280
283
 
281
- @class_deprecated_in_favor_of(NautobotAppConfig)
282
- class PluginConfig(NautobotAppConfig):
283
- pass
284
-
285
-
286
284
  #
287
285
  # Template content injection
288
286
  #
@@ -413,11 +411,6 @@ class TemplateExtension:
413
411
  raise NotImplementedError
414
412
 
415
413
 
416
- @class_deprecated_in_favor_of(TemplateExtension)
417
- class PluginTemplateExtension(TemplateExtension):
418
- pass
419
-
420
-
421
414
  def register_template_extensions(class_list):
422
415
  """
423
416
  Register a list of TemplateExtension classes
@@ -444,11 +437,6 @@ class Banner:
444
437
  self.banner_class = banner_class
445
438
 
446
439
 
447
- @class_deprecated_in_favor_of(Banner)
448
- class PluginBanner(Banner):
449
- pass
450
-
451
-
452
440
  def register_banner_function(function):
453
441
  """
454
442
  Register a function that may return a Banner object.
@@ -494,11 +482,6 @@ class FilterExtension:
494
482
  filterform_fields = {}
495
483
 
496
484
 
497
- @class_deprecated_in_favor_of(FilterExtension)
498
- class PluginFilterExtension(FilterExtension):
499
- pass
500
-
501
-
502
485
  def register_filter_extensions(filter_extensions, plugin_name):
503
486
  """
504
487
  Register a list of FilterExtension classes
@@ -789,11 +772,6 @@ class CustomValidator:
789
772
  raise NotImplementedError
790
773
 
791
774
 
792
- @class_deprecated_in_favor_of(CustomValidator)
793
- class PluginCustomValidator(CustomValidator):
794
- pass
795
-
796
-
797
775
  def register_custom_validators(class_list):
798
776
  """
799
777
  Register a list of CustomValidator classes
@@ -1,5 +1,29 @@
1
1
  ---
2
2
  "apps":
3
+ - "name": "Ansible Automation"
4
+ "use_cases":
5
+ - "Network Automation"
6
+ - "Ansible Playbook Execution"
7
+ "requires": []
8
+ "docs": "https://docs.nautobot.com/projects/ansible-automation/en/stable/"
9
+ "headline": >-
10
+ Execute and monitor automation workflows from Redhat Ansible Automation Platform seamlessly without leaving
11
+ Nautobot.
12
+ "description": >-
13
+ Nautobot Ansible Automation seamlessly integrates automation workflows from Redhat Ansible Automation Platform
14
+ as Nautobot Jobs, allowing its users to run and visualize the output of such workflows directly within Nautobot.
15
+ The Nautobot App connects with Redhat AAP/AWX platforms in order to selectively import Job Templates and Workflows
16
+ so they can be triggered to run from Nautobot. Other features include: historical result synchronization, support
17
+ for input customization using surveys, and deeper integration using Nautobot inventory data.
18
+ "package_name": "nautobot_ansible_automation"
19
+ "author": "Network to Code (NTC)"
20
+ "display":
21
+ "cloud": true
22
+ "oss": true
23
+ "enterprise": true
24
+ "availability": "NTC License"
25
+ "icon": >-
26
+ data:image/png;base64,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
3
27
  - "name": "BGP Models"
4
28
  "use_cases":
5
29
  - "Routing"
@@ -112,7 +136,6 @@
112
136
  "availability": "Open Source"
113
137
  "icon": >-
114
138
  data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAHjAAAB4wGoU74kAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAC9tJREFUeJztmnt8VdWVx79rn3sveRMIAUIABwrRVFCBJCLQ8YGiFTra4uhHp+NYOxoHaxWmdnQqpAlaR6e+PyIiH6ztYPkolJkxVMABH0gH0CCVR1AxSDQJ4ZX3855z1vxxQm5uGzD35obM51N+f51z9t7rddZea++1t6gqf8kw/S1Af+OsAfpbgP7GWQP0twD9Dd+ZYCKFBLCZgeEylBxgIkoGgtXRxQGqgN0IHwJvY9iqBbT3uWx9mQalkCwc7gFuBtIA/BbYDiCQnuDxPtosAPgMBJ3O4ceAVSjP6mI+6zMZ+8IA8hCjEB5DuAkw2enK3GyXzBRl/nofQ+KVlXNtstM93nuPCN9f6+NYs/DLWTaHG4Q1pYb9xwTAQVmF8i/6MBUxlzXWBpBFzAMeA5KuGuty31SH3EylOQjTV/g53iIU3xLkgqHhfPceEa5d6SctQdn6wyDxPthRITz1vxabDhqABuCnWsTSmMobKwNIIQk4vALcMCZVeXyWw6XnuJ3tT2+z+MUWi8evtLltktstjRU7LR7YZLHoUocf5YXmwttfGH660eJQnQC8RiO36ZO0xETuWBhAHmQQAYpRpn032+WJWTZJgVB7XRvkLgswOF7Z8oMgfqt7OkHH85K6VuGDO9tJGRBqa2iHBRt8/Nd+A7CVIN/RR6nprey9ToNSSAJ+3kCZdu9Uh6Wzw5UHWL3PorYVFlzinFJ58ALkP09zqGmFtaXhoiUHYNkcmx9f7ABMJ8B6uZ/E3srfqzQogrCQV4Hp8y9xeHCGE9YedKG6SXhllyHBD/F+WF1qQKEp2IUOkOAHEUj0Q5wFr3xsMesbSnqi4jOd/Hjorx2MwNPbrDzi+Y0Ic1WJ2o17NQVkEQuAJ2483+Wpq212VBh2VAglVcK+I0JFo9DbGWYZyExWJgxV8jKV3BEuk4Yr97zpY41nzPt0Mc9ErUO0BpCFfANhzzmpGpeToWw6aKht9doCPvjmEGVsqlLbLmwuE34yzWHmGJe0BE+p5EA437o2wXHheDO8VWZ4epvFzDEuiQEoqxH2HxPsjtiZnqhcPU557wuhvE5asJigBZRFo0f0U0B4DogrrxPK64SLhinXjneZPtrlgqFKoINyfrEPI0J+jsPAAacmlxrnGWTsIBiX5vDsdotB8bBktg1Aiw27qoQt5YbiTw3/8UeDMYAQj8uTwPVRqRGNB0ghF2HzEQKzs1wemO5w7pDu6Ux60c/AAfDObcFu20+Fb73sp7kdSvK7H7ezSnhki8WWQ53BMkeLKImICdFmAZufYMASuGqsy7jB3Stf0woV9UJuZuRGzhuhfFkv1LZ13z4hXZk5poOuAMq9ETMhCg+QQlJwqJo0XBOqm6CyQRiTqtzwTZc5WS7Z6YpVV0bg4HqqG+GZ7RbXjneZMToyPlsOCW8eMNx7scOwJKFt/PewE4bxcbVQ/Knh9X2GqgYhK01J9MNHh6UJi+FaQGPfGqCAm1BWPXG1zXXnurxUYrFil+Fok7ehGRwPy+IXMLfuqYjofh2eHvQ8BQ3zqO/wiJHJSn6uy60XOry2x3D/Wz4QbtBC1kRCN5ogeDnA5X+lpAzwFi73TXXY9pVh4+fCB5WGqurul7q9waFa5fwMJS/T5dvjvFQons25bEznT7wc+toALrmZA5WRKSHPsQxMH+0yfTSAQ/xmB3ZFTPm0+PllDu2Tug+I5wxUMpKVqka5OFK6kRtAyBp/iqB3ElbHnyFhCFzxiPfc3gAHNkDZW977zF9ATRnsXA5xqXDlY7Dun7wxMx6E5BFQsR22PwduMETzFBifplQ1yLmRqhNRFpBCDJA0KqWHAwLJMOVOOPQuNB6GuSvhoh94bRNvgdkvwLALwZ/g9UNgzlJIzoDSNZB+PvjiesRqVDIAyR0y9hiReoABSB4QYVor/R3YrWC3QU4+7HrZ+35wE8x+Hl6/qUtngYR0r/+G+Z7n9ABdZDJAj4PQmS2KNlRCfFrofevjnpdM/mHoW3E+VO30ps59h2DohB6RNl8zRU45LqLee71dV1MwSm7jroHqj0Pvrg2//xF862ehbxlT4N0ieGEinDgAY67oEenG9k6ZIkpBEU0BfQ1HFtFW3chpVvXd4JZ1MHA0qAO//ZvwtvItsLtLbJh0O1z/KzjxGQwaC2X/0yMWh73lT6sW9KEBOlB24IRk96hnQyUsy/Ge2+qg9gvvrwO8+h2oPeg9v3kv7Hge1IXX/xaGToTEdM9bmo/1iNWBEwLKgQh1icoAH5XVSHZNCwyK775Diy3EAzhtUHWK/cmR3aHn9obwfl3bOmmeWqDjzXCwVkAiX31EHgSFd1yF98tDQ5uC8N+fGO5e5yPvJT+//vg0da8oUfSuxYwVfuav97Hhc0NbF4O8d8h4hRdhc6R0I/cAwzocnLX7jZUzQnl2u2HlHovWoFeyGj9YkVGX0F61HKM2tusdeFgRmtpx6RzrSIDUzIkEa2DlbsPK3YbkANw+2WFejsNar1BqY3gzUnWiqwcsYoPfx6wEy6v4Ts5Qbp7gcM04ZVhSiN6RJmHCEj935TgUXe6chuKf46HNFstKLPbMCzI0MUTzq3pvN/jbPYbSo8LwJOV4sxB0Wa9FfDtSXaKrCCnPBW1mNTjw6+/aXDOu+8CbnqAMioOdVQbv+K/nKKkyDI4PHZ+dxMgU5a4ch/wpDq/uMczf4DuZ+KKqC0a3EHqYdcARV+HJbRZ/+LL7dYEI5Ga67KqWsDn7dWixYXe1kJfpdu74usJVr2649AMLvLlfrotZH40qUXmAKiqLuBF454+HhetX+RmVoszJcpk2WsnJ8IqfAHmZysbP4aPDwtSRPZtuJZWGdoew/ocbhZJK4b1yw+8/E6obxasJguJyczR6QG/L4gUsRcmfOlKpbIDyutDvGpmijB3s1QyKPzHMHOty+ySXtHjtqAqHAmPQ8TJJ0IHjLcLynYZ3vzDMyXKpbfVy/OHGEO2sNGVoArzved4SLeLuqHXolQEWEE8i2yyLC5bNsRmfpuyoED6sNOw7IpTVCo29POFPjYOxg7xzgZwRLrmZyp5qIb/Yh+uyCx+XaAGtUevQ27NBKWQkDlsDPka/ONtmdlZ4QGxsh8fet3ixxOLRKx2yh7i4KrQ74HSw9hnwGzCi7Dtq+NdNFnfnOdw/zSHBH86v+FPDXet8tNtU4jJNH+ZQr+SPyeHoIs4HNlqGEQWXehG6a/D6sl6YutzP5AzljZtPXx6/fpWfDyuFbXcEGZkckk0VXvjQYvF7Fo5LJTBLi9jbW9ljsh3WIvZiMd1xKV30tsWta31UNoQsMCpF+bsLXLZ/JSfP+rvFxs8Nf/hSuPVCJ0z5inrh+7/z8fN3LBzX4xUL5SHGFyTkfhKJ43mEf0j0w7xchzumOKTGQXWjcPFyP8kDlLf+3mZ4Ujjfo03CVb/xUd8mbP/HIOmJSk0rvFRiseQDi+YgoLxMK/fov9MUM5n75IrMQmYhPAOclxSA6851+V62y4lmuHOdj9wRysq5wc6jsro2uGWN5/pLrrUZmgir9xne+NScDKKlKD/WxfRsbxyJrH11SUpuxCKbG1HmA7kAcX5Ii4eKei8NntdxR2j/UaGhHTJT4HgLtJ4ME8IOlKeweC3SfX6P5TwTl6WlkPOwuQ7hCmAKHTfGusFxoARlM/CfuphP+ly2/rgtLsvwU8lSXG7v+LSCau7SF4nsBDUG6JebononQVy67g7s/lAezl6VPWuAM3JXuBsEBg7A194R1wMGH+CHMz8N+iUIKrhB2w3b6fst0yBCTw/dYoZ+8QDbUan7kzLv4MSA/+sOQPsCZ8wAre3uwz5LzgNw3D/3OsfFr6qrAWzV0jifWXgm5Ir9ZemF3IBwB10CrCVIw8+cmY1tp6sLehd9AJIGWKQ8am2ynbALkC7KS7qY1bGUN/YeILwADPmTbz24yqnhT8rMbmhPhtgaIPZpUHkjVoT6jnYIfbMbfICBSLhx2xZruaua1JPxRmhMf8ScU98a5hau/ht1MRa1f9Lg/yf8xa8EzxqgvwXob5w1QH8L0N/4P9UKocwTi+5yAAAAAElFTkSuQmCC
115
- # TODO: remove data_validation_engine from marketplace repo so it doesn't get reintroduced here by accident
116
139
  - "name": "Design Builder"
117
140
  "use_cases":
118
141
  - "Network Design"
@@ -430,6 +453,31 @@
430
453
  "availability": "Open Source"
431
454
  "icon": >-
432
455
  data:image/png;base64,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
456
+ - "name": "Version Control"
457
+ "use_cases":
458
+ - "Enterprise Governance"
459
+ - "Branching"
460
+ - "Approvals"
461
+ - "Data Management"
462
+ "requires": []
463
+ "docs": "https://docs.nautobot.com/projects/version-control/en/latest/"
464
+ "headline": >-
465
+ Allows users to have change (workflow) management with approvals when managing data within Nautobot ensuring
466
+ Enterprise can maintain governance of their data.
467
+ "description": >-
468
+ Version Control allows users to stage changes and have them reviewed by others before committing them. It offers
469
+ Git semantics when working with Nautobot using branch, commit, merge, and pull request concepts allowing users
470
+ to stage changes in a branch and have it be later merged after someone approves it in the UI or API. This app
471
+ requires a Dolt database.
472
+ "package_name": "nautobot_version_control"
473
+ "author": "Network to Code (NTC)"
474
+ "display":
475
+ "cloud": true
476
+ "oss": false
477
+ "enterprise": false
478
+ "availability": "NTC License"
479
+ "icon": >-
480
+ data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABoVBMVEUAAACvr6+wsLCxsbEAff8AfP8Ae/+BosVYlti1sq61sa7geAbmeADgeAe8oYPIlFvheAbheAiysrKvr6+wsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCvr6+wsLAAfP8AfP8Aff8Aff8Abf+xsbEAff8Aff8Cfv5Xlti7s6oAfP8AfP8kh++orrQAfP8jh++xsLACfv68tKsAef+wsLCwsLCwsLCvr6+xsbGwsLCvsLCxsLCvsLGrr7LCtqgPgfgAdP8Aff8Aff8AfP/CtagAdf8Aff/BtagAff8Aff+rrrL0eADueADfeAfgeAfgeAfuaADgeAbgeAfgeAffeQnIlFqss7vgeAbhdwTWgymyrajhdwSwsLHfeQnJlFqttLzjdQCwsLCwsLCwsLDfeAbheAawsLCwsLCwsLDheQawsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLBnm9FLkjVQAAAAiXRSTlMAAAAAAAAAAAAAAAAAAAAAAAAAAgkCeMLBhg72xfOVDRL8ls+eFggMCIcCCXjCAgL2xcQSCRLC88W/88LFCQmIwHoDA5oNlf6aDJoM+5YNDAz8DAINmgIJAnjCAgn2xcQSCRLC88W/wsUSCQn+Hd0CAk3X+AKM8EGTyAsza/2Qx+/cA6qLTKlqMjF60isAAAL+SURBVFjD7ZfbWxJBGMZlliV1tSgOu8ECghoJ0kpm2ZEsFEMp07IkpDLtqGVaeOhg2XH9q5uZZQ+4s7tDV174Xu3Dw/vb+Wa+eWe2peVQB0YuYCkXFQAAn5+oACUB+II8ScLJEB0B+PmwaPaLETkaoyJAgNhlLqBLkOMJKgIE8H7zBPqF7h46ggIg/Cr0RqkIlgD+VDJBQ7AG+EGMhmAHoCLYAmgIdgCGhmA/AgCSTmthCai31+nenngUdrUDwM02yGNocKFblvsCTgCWTaU1pVjWuMUEISL4HQGp/oym/jNHAJAM+0LknQHpzEBWsWcHzg6egzafvi94KkB2SClg6PyFYTTyoFo3LSCTViYwffHS8GVeDGumpgGZK4NXjXU3XUI2c63VaKIF6JMIx9I8oGEZU/8BaGgkNwngsjgqLDrRBIB+H/GosCtBeaf6HMrlSEeF5STi3QjfCf8hijidZJm0sUnLiADXI3A3iiOwHcGNkZGbKFnyeVI0EBrJCJAgAEgSGIXJlCTGtBVAKUGCJTAw2UbHCuO32nDA7Q8XqxLUSWTQ+sUShb3ihKcVJPNyLgCa60Tsj48XJ0u3Pe0IIIGmOlHxJ+5Mle6WJqYT5nxUG2lGb6QZQyfW/bE29l5psngfTyJDAHAdsw80zXZwKsClnQxudqq4V4ia/HVA58O5sqa5R0f1/q/7AWifHi+Mmf3qCCrl+apir84/fvJUCRSjHz0R3m8AVBcqWAvPFpd4JdL2+RMkvw4oVzisyvPFpRcoVCWGxk8AlF++eq00YYjCTyihWn5zDLsCueV80slPmkQ4FtwBK29lBWDnVwHvjMu46sVX2PfhtXVktPWrgM7ViqYPHIcBH2sbm/omZmxD9biX0+Xl8AhCW1vJ7c31eowwTV40oT7VcmB7Y01ejtr5La66MAvB5y9f+3a+hb/nbP3kyzbKwt0fcry28/PXimTrJ1/3URYGgr///I3VU8n2gyNA+NqQUB/tYvcJh7syikyS0M8u5vCT8KDrH3bO2ltvf7BdAAAAAElFTkSuQmCC
433
481
  - "name": "Welcome Wizard"
434
482
  "use_cases":
435
483
  - "Onboarding"
@@ -20,21 +20,21 @@ class InstalledAppsTable(tables.Table):
20
20
  {% if record.home_url %}
21
21
  <a href="{% url record.home_url %}" class="btn btn-primary btn-xs" title="Home">
22
22
  {% else %}
23
- <a href="" class="btn btn-primary btn-xs disabled" title="No home link provided">
23
+ <a class="btn btn-primary btn-xs disabled" aria-disabled="true" title="No home link provided">
24
24
  {% endif %}
25
25
  <i class="mdi mdi-home"></i>
26
26
  </a>
27
27
  {% if record.config_url %}
28
28
  <a href="{% url record.config_url %}" class="btn btn-warning btn-xs" title="Configure">
29
29
  {% else %}
30
- <a href="" class="btn btn-warning btn-xs disabled" title="No configuration link provided">
30
+ <a class="btn btn-warning btn-xs disabled" aria-disabled="true" title="No configuration link provided">
31
31
  {% endif %}
32
32
  <i class="mdi mdi-cog"></i>
33
33
  </a>
34
34
  {% if record.docs_url %}
35
35
  <a href="{% url record.docs_url %}" class="btn btn-info btn-xs" title="Docs">
36
36
  {% else %}
37
- <a href="" class="btn btn-info btn-xs disabled" title="No docs provided">
37
+ <a class="btn btn-info btn-xs disabled" aria-disabled="true" title="No docs provided">
38
38
  {% endif %}
39
39
  <i class="mdi mdi-book-open-page-variant"></i>
40
40
  </a>
@@ -1,12 +1,9 @@
1
- from django.apps import apps
2
- from django.conf import settings
3
- from django.conf.urls import include
4
1
  from django.urls import path
5
2
 
6
- from nautobot.extras.plugins.utils import import_object
7
-
8
3
  from . import views
9
4
 
5
+ BASE_URL_TO_APP_LABEL = {}
6
+
10
7
  # Initialize URL base, API, and admin URL patterns for plugins
11
8
  apps_patterns = [
12
9
  path("installed-apps/", views.InstalledAppsView.as_view(), name="apps_list"),
@@ -30,19 +27,3 @@ plugin_api_patterns = [
30
27
  ),
31
28
  ]
32
29
  plugin_admin_patterns = []
33
-
34
- # Register base/API URL patterns for each plugin
35
- for plugin_path in settings.PLUGINS:
36
- plugin_name = plugin_path.split(".")[-1]
37
- app = apps.get_app_config(plugin_name)
38
- base_url = app.base_url or app.label
39
-
40
- # Check if the plugin specifies any base URLs
41
- urlpatterns = import_object(f"{plugin_path}.urls.urlpatterns")
42
- if urlpatterns is not None:
43
- plugin_patterns.append(path(f"{base_url}/", include((urlpatterns, app.label))))
44
-
45
- # Check if the plugin specifies any API URLs
46
- urlpatterns = import_object(f"{plugin_path}.api.urls.urlpatterns")
47
- if urlpatterns is not None:
48
- plugin_api_patterns.append(path(f"{base_url}/", include((urlpatterns, f"{app.label}-api"))))