nautobot 2.4.21__py3-none-any.whl → 3.0.0a3__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.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (919) hide show
  1. nautobot/apps/choices.py +2 -2
  2. nautobot/apps/filters.py +9 -9
  3. nautobot/apps/forms.py +2 -0
  4. nautobot/apps/models.py +7 -2
  5. nautobot/apps/ui.py +20 -1
  6. nautobot/apps/utils.py +2 -3
  7. nautobot/apps/views.py +7 -1
  8. nautobot/circuits/filters.py +8 -23
  9. nautobot/circuits/navigation.py +3 -1
  10. nautobot/circuits/templates/circuits/circuit_create.html +9 -9
  11. nautobot/circuits/templates/circuits/circuit_terminations_swap.html +2 -2
  12. nautobot/circuits/templates/circuits/circuittermination_create.html +24 -33
  13. nautobot/circuits/templates/circuits/inc/circuit_termination.html +10 -10
  14. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +13 -13
  15. nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +6 -6
  16. nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +3 -3
  17. nautobot/circuits/templates/circuits/inc/speed_widget.html +13 -13
  18. nautobot/circuits/templates/circuits/provider_create.html +9 -9
  19. nautobot/circuits/tests/integration/test_circuit.py +19 -19
  20. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +3 -0
  21. nautobot/circuits/tests/integration/test_relationships.py +4 -12
  22. nautobot/circuits/views.py +0 -2
  23. nautobot/cloud/filters.py +1 -13
  24. nautobot/cloud/navigation.py +3 -1
  25. nautobot/cloud/templates/cloud/cloudnetwork_update.html +9 -9
  26. nautobot/cloud/templates/cloud/cloudservice_update.html +6 -6
  27. nautobot/core/api/fields.py +30 -2
  28. nautobot/core/api/schema.py +1 -1
  29. nautobot/core/api/serializers.py +9 -2
  30. nautobot/core/api/urls.py +2 -0
  31. nautobot/core/api/views.py +58 -37
  32. nautobot/core/apps/__init__.py +6 -12
  33. nautobot/core/branching.py +83 -0
  34. nautobot/core/celery/__init__.py +11 -6
  35. nautobot/core/celery/backends.py +2 -0
  36. nautobot/core/celery/encoders.py +7 -0
  37. nautobot/core/celery/task.py +44 -0
  38. nautobot/core/checks.py +60 -0
  39. nautobot/core/cli/bootstrap_v3_to_v5.py +776 -0
  40. nautobot/core/constants.py +9 -0
  41. nautobot/core/context_processors.py +84 -0
  42. nautobot/core/filters.py +131 -2
  43. nautobot/core/forms/__init__.py +4 -2
  44. nautobot/core/forms/fields.py +10 -8
  45. nautobot/core/forms/forms.py +21 -9
  46. nautobot/core/forms/search.py +0 -15
  47. nautobot/core/forms/widgets.py +3 -2
  48. nautobot/core/graphql/__init__.py +8 -26
  49. nautobot/core/graphql/generators.py +16 -6
  50. nautobot/core/graphql/schema.py +1 -1
  51. nautobot/core/graphql/schema_init.py +1 -2
  52. nautobot/core/graphql/utils.py +7 -9
  53. nautobot/core/jobs/__init__.py +158 -0
  54. nautobot/core/management/commands/generate_test_data.py +28 -9
  55. nautobot/core/models/__init__.py +17 -2
  56. nautobot/core/models/fields.py +3 -2
  57. nautobot/core/models/generics.py +9 -1
  58. nautobot/core/models/name_color_content_types.py +1 -1
  59. nautobot/core/models/ordering.py +7 -5
  60. nautobot/core/models/querysets.py +77 -2
  61. nautobot/core/models/tree_queries.py +6 -4
  62. nautobot/core/settings.py +30 -16
  63. nautobot/core/settings.yaml +13 -7
  64. nautobot/core/tables.py +114 -44
  65. nautobot/core/templates/403.html +1 -1
  66. nautobot/core/templates/403_csrf_failure.html +1 -1
  67. nautobot/core/templates/404.html +1 -1
  68. nautobot/core/templates/40x.html +8 -8
  69. nautobot/core/templates/500.html +10 -10
  70. nautobot/core/templates/about.html +13 -12
  71. nautobot/core/templates/admin/actions.html +1 -1
  72. nautobot/core/templates/admin/app_index.html +3 -3
  73. nautobot/core/templates/admin/base.html +45 -52
  74. nautobot/core/templates/admin/base_site.html +0 -9
  75. nautobot/core/templates/admin/change_form.html +5 -5
  76. nautobot/core/templates/admin/change_list.html +8 -12
  77. nautobot/core/templates/admin/change_list_results.html +3 -3
  78. nautobot/core/templates/admin/config/config.html +24 -24
  79. nautobot/core/templates/admin/delete_confirmation.html +5 -5
  80. nautobot/core/templates/admin/edit_inline/stacked.html +5 -5
  81. nautobot/core/templates/admin/edit_inline/tabular.html +3 -3
  82. nautobot/core/templates/admin/includes/fieldset.html +15 -15
  83. nautobot/core/templates/admin/index.html +8 -8
  84. nautobot/core/templates/admin/submit_line.html +5 -5
  85. nautobot/core/templates/base_django.html +36 -32
  86. nautobot/core/templates/buttons/add.html +1 -1
  87. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +2 -2
  88. nautobot/core/templates/buttons/export.html +17 -18
  89. nautobot/core/templates/buttons/job_import.html +2 -2
  90. nautobot/core/templates/components/breadcrumbs.html +19 -17
  91. nautobot/core/templates/components/button/dropdown.html +7 -5
  92. nautobot/core/templates/components/echarts.html +2 -0
  93. nautobot/core/templates/components/layout/one_over_two.html +3 -3
  94. nautobot/core/templates/components/layout/two_over_one.html +3 -3
  95. nautobot/core/templates/components/panel/body_content_data_table.html +2 -2
  96. nautobot/core/templates/components/panel/body_content_tags.html +1 -1
  97. nautobot/core/templates/components/panel/body_wrapper_generic.html +4 -2
  98. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  99. nautobot/core/templates/components/panel/body_wrapper_key_value_table.html +5 -3
  100. nautobot/core/templates/components/panel/body_wrapper_table.html +4 -2
  101. nautobot/core/templates/components/panel/footer_contacts_table.html +4 -4
  102. nautobot/core/templates/components/panel/footer_content_table.html +3 -3
  103. nautobot/core/templates/components/panel/grouping_toggle.html +12 -11
  104. nautobot/core/templates/components/panel/header_extra_content_table.html +2 -11
  105. nautobot/core/templates/components/panel/panel.html +6 -3
  106. nautobot/core/templates/components/panel/stats_panel_body.html +9 -7
  107. nautobot/core/templates/components/tab/content_wrapper.html +29 -1
  108. nautobot/core/templates/components/tab/label_wrapper.html +10 -2
  109. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +11 -4
  110. nautobot/core/templates/echarts/echarts.html +20 -0
  111. nautobot/core/templates/exceptions/import_error.html +2 -2
  112. nautobot/core/templates/exceptions/permission_error.html +1 -1
  113. nautobot/core/templates/exceptions/programming_error.html +2 -2
  114. nautobot/core/templates/generic/object_bulk_add_component.html +29 -20
  115. nautobot/core/templates/generic/object_bulk_create.html +87 -75
  116. nautobot/core/templates/generic/object_bulk_destroy.html +35 -37
  117. nautobot/core/templates/generic/object_bulk_remove.html +30 -26
  118. nautobot/core/templates/generic/object_bulk_rename.html +53 -40
  119. nautobot/core/templates/generic/object_bulk_update.html +36 -29
  120. nautobot/core/templates/generic/object_create.html +40 -27
  121. nautobot/core/templates/generic/object_import.html +36 -24
  122. nautobot/core/templates/generic/object_list.html +279 -215
  123. nautobot/core/templates/generic/object_notes.html +21 -11
  124. nautobot/core/templates/generic/object_retrieve.html +161 -213
  125. nautobot/core/templates/graphene/graphiql.html +113 -60
  126. nautobot/core/templates/home.html +164 -87
  127. nautobot/core/templates/import_success.html +3 -2
  128. nautobot/core/templates/inc/ajax_loader.html +1 -1
  129. nautobot/core/templates/inc/computed_fields/panel_data.html +25 -13
  130. nautobot/core/templates/inc/created_updated.html +12 -7
  131. nautobot/core/templates/inc/custom_fields/panel_data.html +28 -16
  132. nautobot/core/templates/inc/custom_fields_panel.html +3 -3
  133. nautobot/core/templates/inc/dynamic_groups_panel.html +3 -3
  134. nautobot/core/templates/inc/extras_features_edit_form_fields.html +15 -15
  135. nautobot/core/templates/inc/footer.html +90 -40
  136. nautobot/core/templates/inc/form_static_field.html +6 -0
  137. nautobot/core/templates/inc/header.html +75 -0
  138. nautobot/core/templates/inc/header_banners.html +17 -0
  139. nautobot/core/templates/inc/header_messages.html +6 -0
  140. nautobot/core/templates/inc/image_attachments.html +9 -9
  141. nautobot/core/templates/inc/javascript.html +7 -24
  142. nautobot/core/templates/inc/media.html +4 -29
  143. nautobot/core/templates/inc/modal.html +2 -2
  144. nautobot/core/templates/inc/nav_favorites.html +27 -0
  145. nautobot/core/templates/inc/nav_menu.html +150 -108
  146. nautobot/core/templates/inc/object_details_advanced_panel.html +84 -71
  147. nautobot/core/templates/inc/page_title.html +23 -0
  148. nautobot/core/templates/inc/paginator.html +39 -28
  149. nautobot/core/templates/inc/relationships/panel_override.html +3 -3
  150. nautobot/core/templates/inc/relationships_panel.html +3 -3
  151. nautobot/core/templates/inc/relationships_table_rows.html +1 -1
  152. nautobot/core/templates/inc/search_panel.html +22 -16
  153. nautobot/core/templates/inc/table.html +61 -36
  154. nautobot/core/templates/inc/tenancy_form_panel.html +3 -3
  155. nautobot/core/templates/login.html +17 -59
  156. nautobot/core/templates/modals/modal_theme.html +12 -23
  157. nautobot/core/templates/nautobot_config.py.j2 +6 -5
  158. nautobot/core/templates/panel_table.html +8 -12
  159. nautobot/core/templates/redoc_ui.html +80 -0
  160. nautobot/core/templates/rest_framework/api.html +43 -21
  161. nautobot/core/templates/search.html +12 -13
  162. nautobot/core/templates/swagger_ui.html +19 -4
  163. nautobot/core/templates/system_jobs/import_objects.html +70 -58
  164. nautobot/core/templates/template.css +0 -6
  165. nautobot/core/templates/utilities/comment_form.html +34 -0
  166. nautobot/core/templates/utilities/confirmation_form.html +17 -9
  167. nautobot/core/templates/utilities/obj_table.html +19 -11
  168. nautobot/core/templates/utilities/render_field.html +27 -21
  169. nautobot/core/templates/utilities/render_jinja2.html +22 -25
  170. nautobot/core/templates/utilities/templatetags/advanced_filter_indicator.html +8 -0
  171. nautobot/core/templates/utilities/templatetags/badge.html +1 -1
  172. nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +2 -3
  173. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +482 -0
  174. nautobot/core/templates/utilities/templatetags/modal_form_as_dialog.html +14 -18
  175. nautobot/core/templates/utilities/templatetags/saved_view_modal.html +11 -11
  176. nautobot/core/templates/utilities/templatetags/table_config_form.html +51 -24
  177. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  178. nautobot/core/templates/utilities/templatetags/utilization_graph.html +3 -3
  179. nautobot/core/templates/utilities/theme_preview.html +829 -566
  180. nautobot/core/templates/utilities/worker_status.html +42 -41
  181. nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
  182. nautobot/core/templates/widgets/sluginput.html +2 -2
  183. nautobot/core/templatetags/buttons.py +38 -40
  184. nautobot/core/templatetags/helpers.py +105 -28
  185. nautobot/core/templatetags/ui_framework.py +17 -0
  186. nautobot/core/testing/api.py +76 -12
  187. nautobot/core/testing/filters.py +11 -27
  188. nautobot/core/testing/integration.py +128 -10
  189. nautobot/core/testing/mixins.py +7 -4
  190. nautobot/core/testing/utils.py +28 -5
  191. nautobot/core/testing/views.py +125 -27
  192. nautobot/core/tests/integration/test_app_home.py +39 -35
  193. nautobot/core/tests/integration/test_app_navbar.py +60 -67
  194. nautobot/core/tests/integration/test_filters.py +123 -55
  195. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  196. nautobot/core/tests/integration/test_home.py +10 -18
  197. nautobot/core/tests/integration/test_import_objects_ui.py +2 -9
  198. nautobot/core/tests/integration/test_navbar.py +41 -16
  199. nautobot/core/tests/integration/test_swagger.py +1 -7
  200. nautobot/core/tests/integration/test_theme.py +3 -0
  201. nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
  202. nautobot/core/tests/runner.py +6 -1
  203. nautobot/core/tests/test_api.py +5 -3
  204. nautobot/core/tests/test_branching.py +154 -0
  205. nautobot/core/tests/test_breadcrumbs.py +7 -8
  206. nautobot/core/tests/test_checks.py +28 -0
  207. nautobot/core/tests/test_commands.py +0 -41
  208. nautobot/core/tests/test_config.py +2 -1
  209. nautobot/core/tests/test_csv.py +4 -7
  210. nautobot/core/tests/test_filters.py +326 -318
  211. nautobot/core/tests/test_forms.py +19 -30
  212. nautobot/core/tests/test_graphql.py +67 -57
  213. nautobot/core/tests/test_models.py +1 -1
  214. nautobot/core/tests/test_nautobot_server.py +2 -0
  215. nautobot/core/tests/test_navigations.py +78 -10
  216. nautobot/core/tests/test_tables.py +3 -1
  217. nautobot/core/tests/test_templatetags_helpers.py +61 -21
  218. nautobot/core/tests/test_templatetags_ui_framework.py +36 -18
  219. nautobot/core/tests/test_ui.py +207 -2
  220. nautobot/core/tests/test_utils.py +147 -2
  221. nautobot/core/tests/test_views.py +201 -64
  222. nautobot/core/tests/test_views_utils.py +1 -1
  223. nautobot/core/ui/breadcrumbs.py +2 -12
  224. nautobot/core/ui/choices.py +190 -0
  225. nautobot/core/ui/constants.py +86 -0
  226. nautobot/core/ui/echarts.py +474 -0
  227. nautobot/core/ui/nav.py +5 -1
  228. nautobot/core/ui/object_detail.py +180 -16
  229. nautobot/core/urls.py +13 -1
  230. nautobot/core/utils/cache.py +71 -0
  231. nautobot/core/utils/data.py +8 -5
  232. nautobot/core/utils/filtering.py +8 -2
  233. nautobot/core/utils/git.py +3 -3
  234. nautobot/core/utils/lookup.py +87 -13
  235. nautobot/core/utils/migrations.py +22 -0
  236. nautobot/core/utils/module_loading.py +26 -0
  237. nautobot/core/utils/permissions.py +9 -5
  238. nautobot/core/views/__init__.py +114 -63
  239. nautobot/core/views/generic.py +34 -27
  240. nautobot/core/views/mixins.py +49 -27
  241. nautobot/core/views/renderers.py +3 -5
  242. nautobot/core/views/utils.py +10 -5
  243. nautobot/core/views/viewsets.py +2 -1
  244. nautobot/data_validation/__init__.py +0 -0
  245. nautobot/data_validation/api/__init__.py +1 -0
  246. nautobot/data_validation/api/serializers.py +80 -0
  247. nautobot/data_validation/api/urls.py +20 -0
  248. nautobot/data_validation/api/views.py +44 -0
  249. nautobot/data_validation/apps.py +18 -0
  250. nautobot/data_validation/custom_validators.py +330 -0
  251. nautobot/data_validation/filters.py +133 -0
  252. nautobot/data_validation/form_mixin.py +25 -0
  253. nautobot/data_validation/forms.py +342 -0
  254. nautobot/data_validation/migrations/0001_initial.py +224 -0
  255. nautobot/data_validation/migrations/0002_data_migration_from_app.py +324 -0
  256. nautobot/data_validation/migrations/__init__.py +0 -0
  257. nautobot/data_validation/models.py +361 -0
  258. nautobot/data_validation/navigation.py +74 -0
  259. nautobot/data_validation/signals.py +30 -0
  260. nautobot/data_validation/tables.py +259 -0
  261. nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +1 -0
  262. nautobot/data_validation/templates/data_validation/datacompliance_tab.html +11 -0
  263. nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
  264. nautobot/data_validation/tests/__init__.py +20 -0
  265. nautobot/data_validation/tests/migrations/__init__.py +0 -0
  266. nautobot/data_validation/tests/migrations/test_migrations.py +489 -0
  267. nautobot/data_validation/tests/test_api.py +238 -0
  268. nautobot/data_validation/tests/test_custom_validators.py +423 -0
  269. nautobot/data_validation/tests/test_data_compliance_rules.py +85 -0
  270. nautobot/data_validation/tests/test_filters.py +240 -0
  271. nautobot/data_validation/tests/test_form_mixin.py +115 -0
  272. nautobot/data_validation/tests/test_models.py +393 -0
  273. nautobot/data_validation/tests/test_views.py +435 -0
  274. nautobot/data_validation/urls.py +21 -0
  275. nautobot/data_validation/views.py +227 -0
  276. nautobot/dcim/api/serializers.py +10 -13
  277. nautobot/dcim/api/urls.py +2 -0
  278. nautobot/dcim/api/views.py +7 -0
  279. nautobot/dcim/apps.py +4 -0
  280. nautobot/dcim/choices.py +16 -0
  281. nautobot/dcim/custom_validators.py +84 -0
  282. nautobot/dcim/filter_mixins.py +353 -4
  283. nautobot/dcim/{filters/__init__.py → filters.py} +70 -157
  284. nautobot/dcim/forms.py +12 -6
  285. nautobot/dcim/graphql/types.py +1 -0
  286. nautobot/dcim/migrations/0075_add_deviceclusterassignment.py +52 -0
  287. nautobot/dcim/migrations/0076_device_cluster_to_clusters_data_migration.py +40 -0
  288. nautobot/dcim/migrations/0077_remove_device_cluster.py +14 -0
  289. nautobot/dcim/migrations/0078_remove_device_location_tenant_name_uniqueness.py +16 -0
  290. nautobot/dcim/migrations/0079_device_name_data_migration.py +59 -0
  291. nautobot/dcim/models/__init__.py +2 -0
  292. nautobot/dcim/models/device_components.py +3 -1
  293. nautobot/dcim/models/devices.py +115 -51
  294. nautobot/dcim/navigation.py +7 -3
  295. nautobot/dcim/querysets.py +6 -0
  296. nautobot/dcim/signals.py +19 -0
  297. nautobot/dcim/tables/devices.py +9 -5
  298. nautobot/dcim/tables/template_code.py +191 -102
  299. nautobot/dcim/templates/dcim/cable.html +1 -1
  300. nautobot/dcim/templates/dcim/cable_connect.html +62 -146
  301. nautobot/dcim/templates/dcim/cable_retrieve.html +10 -10
  302. nautobot/dcim/templates/dcim/cable_trace.html +15 -17
  303. nautobot/dcim/templates/dcim/console_port_connection_list.html +2 -2
  304. nautobot/dcim/templates/dcim/consoleport.html +18 -17
  305. nautobot/dcim/templates/dcim/consoleserverport.html +18 -17
  306. nautobot/dcim/templates/dcim/controller_create.html +12 -8
  307. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +1 -1
  308. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +6 -6
  309. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +1 -1
  310. nautobot/dcim/templates/dcim/device/config.html +17 -19
  311. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +4 -4
  312. nautobot/dcim/templates/dcim/device/status.html +20 -20
  313. nautobot/dcim/templates/dcim/device_component_add.html +24 -15
  314. nautobot/dcim/templates/dcim/device_create.html +120 -120
  315. nautobot/dcim/templates/dcim/device_list.html +75 -12
  316. nautobot/dcim/templates/dcim/devicebay.html +7 -7
  317. nautobot/dcim/templates/dcim/devicebay_populate.html +29 -23
  318. nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +6 -6
  319. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  320. nautobot/dcim/templates/dcim/devicetype_component_add.html +25 -19
  321. nautobot/dcim/templates/dcim/devicetype_list.html +4 -4
  322. nautobot/dcim/templates/dcim/devicetype_update.html +9 -9
  323. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +3 -3
  324. nautobot/dcim/templates/dcim/frontport.html +21 -20
  325. nautobot/dcim/templates/dcim/inc/cable_form.html +7 -7
  326. nautobot/dcim/templates/dcim/inc/cable_termination.html +1 -1
  327. nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +18 -9
  328. nautobot/dcim/templates/dcim/inc/detail_softwareversion_softwareimagefile_rows.html +1 -1
  329. nautobot/dcim/templates/dcim/inc/device_interface_filter.html +1 -1
  330. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +10 -10
  331. nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
  332. nautobot/dcim/templates/dcim/inc/homepage_connections.html +2 -2
  333. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +10 -10
  334. nautobot/dcim/templates/dcim/inc/rack_elevation.html +2 -2
  335. nautobot/dcim/templates/dcim/interface.html +42 -22
  336. nautobot/dcim/templates/dcim/interface_connection_list.html +2 -2
  337. nautobot/dcim/templates/dcim/interface_edit.html +26 -11
  338. nautobot/dcim/templates/dcim/interfaceredundancygroupassociation_create.html +3 -3
  339. nautobot/dcim/templates/dcim/inventoryitem.html +3 -3
  340. nautobot/dcim/templates/dcim/inventoryitem_add.html +21 -10
  341. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  342. nautobot/dcim/templates/dcim/inventoryitem_edit.html +6 -4
  343. nautobot/dcim/templates/dcim/location.html +1 -1
  344. nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +24 -18
  345. nautobot/dcim/templates/dcim/location_retrieve.html +1 -1
  346. nautobot/dcim/templates/dcim/location_update.html +9 -9
  347. nautobot/dcim/templates/dcim/locationtype.html +0 -1
  348. nautobot/dcim/templates/dcim/module/base.html +67 -27
  349. nautobot/dcim/templates/dcim/module_consoleports.html +13 -15
  350. nautobot/dcim/templates/dcim/module_consoleserverports.html +13 -15
  351. nautobot/dcim/templates/dcim/module_frontports.html +13 -15
  352. nautobot/dcim/templates/dcim/module_interfaces.html +14 -16
  353. nautobot/dcim/templates/dcim/module_list.html +59 -10
  354. nautobot/dcim/templates/dcim/module_modulebays.html +12 -14
  355. nautobot/dcim/templates/dcim/module_poweroutlets.html +13 -15
  356. nautobot/dcim/templates/dcim/module_powerports.html +13 -15
  357. nautobot/dcim/templates/dcim/module_rearports.html +13 -15
  358. nautobot/dcim/templates/dcim/module_retrieve.html +3 -3
  359. nautobot/dcim/templates/dcim/module_update.html +15 -9
  360. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -93
  361. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +7 -7
  362. nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
  363. nautobot/dcim/templates/dcim/moduletype_retrieve.html +74 -35
  364. nautobot/dcim/templates/dcim/platform_create.html +9 -9
  365. nautobot/dcim/templates/dcim/power_port_connection_list.html +3 -3
  366. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  367. nautobot/dcim/templates/dcim/powerfeed_edit.html +15 -15
  368. nautobot/dcim/templates/dcim/poweroutlet.html +13 -13
  369. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  370. nautobot/dcim/templates/dcim/powerport.html +17 -16
  371. nautobot/dcim/templates/dcim/rack.html +1 -1
  372. nautobot/dcim/templates/dcim/rack_elevation.html +2 -2
  373. nautobot/dcim/templates/dcim/rack_elevation_list.html +21 -9
  374. nautobot/dcim/templates/dcim/rack_retrieve.html +75 -57
  375. nautobot/dcim/templates/dcim/rack_update.html +14 -14
  376. nautobot/dcim/templates/dcim/rackreservation.html +1 -1
  377. nautobot/dcim/templates/dcim/rackreservation_edit.html +6 -6
  378. nautobot/dcim/templates/dcim/rearport.html +19 -18
  379. nautobot/dcim/templates/dcim/trace/cable.html +1 -1
  380. nautobot/dcim/templates/dcim/trace/circuit.html +1 -1
  381. nautobot/dcim/templates/dcim/trace/device.html +1 -1
  382. nautobot/dcim/templates/dcim/trace/powerpanel.html +1 -1
  383. nautobot/dcim/templates/dcim/trace/termination.html +1 -1
  384. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  385. nautobot/dcim/templates/dcim/virtualchassis_add_member.html +25 -16
  386. nautobot/dcim/templates/dcim/virtualchassis_create.html +6 -6
  387. nautobot/dcim/templates/dcim/virtualchassis_edit.html +1 -1
  388. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -1
  389. nautobot/dcim/templates/dcim/virtualchassis_update.html +36 -22
  390. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +9 -9
  391. nautobot/dcim/tests/integration/test_controller.py +6 -6
  392. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +7 -7
  393. nautobot/dcim/tests/integration/test_create_device.py +9 -9
  394. nautobot/dcim/tests/integration/test_device_bulk_operations.py +7 -2
  395. nautobot/dcim/tests/integration/test_fileinputpicker.py +5 -7
  396. nautobot/dcim/tests/integration/test_location_bulk_operations.py +2 -0
  397. nautobot/dcim/tests/integration/test_module_bay_position.py +4 -1
  398. nautobot/dcim/tests/test_api.py +86 -6
  399. nautobot/dcim/tests/test_custom_validators.py +229 -0
  400. nautobot/dcim/tests/test_filters.py +159 -110
  401. nautobot/dcim/tests/test_graphql.py +32 -36
  402. nautobot/dcim/tests/test_jobs.py +1 -1
  403. nautobot/dcim/tests/test_models.py +229 -1
  404. nautobot/dcim/tests/test_views.py +31 -20
  405. nautobot/dcim/utils.py +3 -3
  406. nautobot/dcim/views.py +77 -41
  407. nautobot/extras/api/serializers.py +83 -19
  408. nautobot/extras/api/urls.py +7 -0
  409. nautobot/extras/api/views.py +243 -140
  410. nautobot/extras/choices.py +34 -13
  411. nautobot/extras/constants.py +1 -1
  412. nautobot/extras/context_managers.py +26 -26
  413. nautobot/extras/datasources/git.py +22 -0
  414. nautobot/extras/datasources/registry.py +3 -0
  415. nautobot/extras/exceptions.py +5 -0
  416. nautobot/extras/factory.py +11 -1
  417. nautobot/extras/{filters/mixins.py → filter_mixins.py} +4 -3
  418. nautobot/extras/{filters/__init__.py → filters.py} +203 -58
  419. nautobot/extras/forms/base.py +2 -1
  420. nautobot/extras/forms/forms.py +225 -20
  421. nautobot/extras/forms/mixins.py +0 -41
  422. nautobot/extras/homepage.py +21 -2
  423. nautobot/extras/jobs.py +2 -8
  424. nautobot/extras/jobs_ui.py +2 -2
  425. nautobot/extras/management/__init__.py +9 -0
  426. nautobot/extras/managers.py +31 -22
  427. nautobot/extras/migrations/0126_approval_workflow_pre_check.py +58 -0
  428. nautobot/extras/migrations/0127_approval_workflow_models.py +266 -0
  429. nautobot/extras/migrations/0128_remove_job_approval_required_and_more.py +29 -0
  430. nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
  431. nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
  432. nautobot/extras/models/__init__.py +14 -3
  433. nautobot/extras/models/approvals.py +556 -0
  434. nautobot/extras/models/change_logging.py +1 -0
  435. nautobot/extras/models/contacts.py +2 -0
  436. nautobot/extras/models/customfields.py +57 -22
  437. nautobot/extras/models/datasources.py +21 -0
  438. nautobot/extras/models/groups.py +2 -0
  439. nautobot/extras/models/jobs.py +122 -39
  440. nautobot/extras/models/metadata.py +2 -3
  441. nautobot/extras/models/mixins.py +129 -1
  442. nautobot/extras/models/models.py +22 -14
  443. nautobot/extras/models/relationships.py +47 -10
  444. nautobot/extras/models/secrets.py +1 -0
  445. nautobot/extras/models/statuses.py +0 -15
  446. nautobot/extras/models/tags.py +1 -1
  447. nautobot/extras/navigation.py +42 -15
  448. nautobot/extras/plugins/__init__.py +33 -55
  449. nautobot/extras/plugins/marketplace_manifest.yml +1 -23
  450. nautobot/extras/plugins/tables.py +8 -6
  451. nautobot/extras/plugins/urls.py +2 -21
  452. nautobot/extras/plugins/utils.py +1 -33
  453. nautobot/extras/plugins/validators.py +10 -10
  454. nautobot/extras/plugins/views.py +1 -5
  455. nautobot/extras/querysets.py +17 -21
  456. nautobot/extras/signals.py +23 -8
  457. nautobot/extras/tables.py +420 -99
  458. nautobot/extras/templates/extras/approval_dashboard.html +15 -0
  459. nautobot/extras/templates/extras/approval_workflow/approve.html +11 -0
  460. nautobot/extras/templates/extras/approval_workflow/comment.html +9 -0
  461. nautobot/extras/templates/extras/approval_workflow/deny.html +10 -0
  462. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +77 -0
  463. nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +29 -0
  464. nautobot/extras/templates/extras/configcontext_update.html +12 -12
  465. nautobot/extras/templates/extras/configcontextschema.html +1 -1
  466. nautobot/extras/templates/extras/configcontextschema_retrieve.html +9 -9
  467. nautobot/extras/templates/extras/configcontextschema_update.html +6 -6
  468. nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
  469. nautobot/extras/templates/extras/customfield_update.html +12 -12
  470. nautobot/extras/templates/extras/dynamicgroup.html +1 -1
  471. nautobot/extras/templates/extras/dynamicgroup_edit.html +1 -1
  472. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +17 -17
  473. nautobot/extras/templates/extras/dynamicgroup_update.html +24 -24
  474. nautobot/extras/templates/extras/externalintegration_update.html +6 -6
  475. nautobot/extras/templates/extras/gitrepository.html +1 -1
  476. nautobot/extras/templates/extras/gitrepository_object_edit.html +1 -1
  477. nautobot/extras/templates/extras/gitrepository_result.html +1 -1
  478. nautobot/extras/templates/extras/gitrepository_retrieve.html +12 -12
  479. nautobot/extras/templates/extras/gitrepository_update.html +25 -7
  480. nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -1
  481. nautobot/extras/templates/extras/inc/approval_buttons_column.html +38 -0
  482. nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +14 -13
  483. nautobot/extras/templates/extras/inc/configcontext_format.html +11 -4
  484. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +7 -7
  485. nautobot/extras/templates/extras/inc/job_label.html +5 -5
  486. nautobot/extras/templates/extras/inc/job_table.html +23 -10
  487. nautobot/extras/templates/extras/inc/job_tiles.html +33 -21
  488. nautobot/extras/templates/extras/inc/jobresult.html +6 -6
  489. nautobot/extras/templates/extras/inc/json_format.html +11 -4
  490. nautobot/extras/templates/extras/inc/object_contact_header.html +6 -6
  491. nautobot/extras/templates/extras/inc/overridable_field.html +16 -15
  492. nautobot/extras/templates/extras/inc/panel_approvalworkflowstage.html +34 -0
  493. nautobot/extras/templates/extras/inc/panel_changelog.html +9 -9
  494. nautobot/extras/templates/extras/inc/panel_jobhistory.html +8 -6
  495. nautobot/extras/templates/extras/inc/tags_panel.html +3 -3
  496. nautobot/extras/templates/extras/job.html +154 -155
  497. nautobot/extras/templates/extras/job_approval_confirmation.html +4 -27
  498. nautobot/extras/templates/extras/job_bulk_edit.html +18 -1
  499. nautobot/extras/templates/extras/job_detail.html +1 -1
  500. nautobot/extras/templates/extras/job_edit.html +69 -64
  501. nautobot/extras/templates/extras/job_list.html +37 -60
  502. nautobot/extras/templates/extras/jobresult.html +1 -1
  503. nautobot/extras/templates/extras/jobresult_retrieve.html +17 -17
  504. nautobot/extras/templates/extras/marketplace.html +62 -71
  505. nautobot/extras/templates/extras/metadatatype_create.html +9 -9
  506. nautobot/extras/templates/extras/note.html +1 -1
  507. nautobot/extras/templates/extras/object_approvalworkflow.html +36 -0
  508. nautobot/extras/templates/extras/object_assign_contact_or_team.html +16 -7
  509. nautobot/extras/templates/extras/object_configcontext.html +20 -20
  510. nautobot/extras/templates/extras/object_new_contact.html +6 -6
  511. nautobot/extras/templates/extras/object_new_team.html +6 -6
  512. nautobot/extras/templates/extras/objectchange.html +1 -1
  513. nautobot/extras/templates/extras/objectchange_retrieve.html +37 -56
  514. nautobot/extras/templates/extras/plugin_detail.html +40 -41
  515. nautobot/extras/templates/extras/plugins_list.html +23 -38
  516. nautobot/extras/templates/extras/plugins_tiles.html +28 -28
  517. nautobot/extras/templates/extras/role_retrieve.html +112 -48
  518. nautobot/extras/templates/extras/scheduledjob.html +25 -28
  519. nautobot/extras/templates/extras/secret_create.html +11 -11
  520. nautobot/extras/templates/extras/secretsgroup_update.html +6 -6
  521. nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +3 -3
  522. nautobot/extras/templates/extras/status.html +1 -1
  523. nautobot/extras/templates/extras/tag.html +1 -1
  524. nautobot/extras/templates/extras/tag_update.html +3 -3
  525. nautobot/extras/templates/extras/templatetags/log_level.html +1 -1
  526. nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +2 -2
  527. nautobot/extras/templates/extras/webhook.html +12 -12
  528. nautobot/extras/templatetags/approvals.py +19 -0
  529. nautobot/extras/templatetags/custom_links.py +12 -12
  530. nautobot/extras/templatetags/job_buttons.py +14 -12
  531. nautobot/extras/test_jobs/invalid_import.py +9 -0
  532. nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
  533. nautobot/extras/test_jobs/missing_import.py +11 -0
  534. nautobot/extras/tests/integration/test_computedfields.py +5 -8
  535. nautobot/extras/tests/integration/test_configcontextschema.py +43 -48
  536. nautobot/extras/tests/integration/test_customfields.py +33 -33
  537. nautobot/extras/tests/integration/test_dynamicgroups.py +5 -10
  538. nautobot/extras/tests/integration/test_jobs.py +2 -4
  539. nautobot/extras/tests/integration/test_notes.py +3 -9
  540. nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
  541. nautobot/extras/tests/integration/test_plugins.py +35 -27
  542. nautobot/extras/tests/integration/test_relationships.py +7 -11
  543. nautobot/extras/tests/integration/test_tagfilter.py +3 -11
  544. nautobot/extras/tests/test_api.py +786 -242
  545. nautobot/extras/tests/test_approvals.py +715 -0
  546. nautobot/extras/tests/test_changelog.py +18 -14
  547. nautobot/extras/tests/test_customfields.py +14 -13
  548. nautobot/extras/tests/test_datasources.py +1 -1
  549. nautobot/extras/tests/test_dynamicgroups.py +9 -4
  550. nautobot/extras/tests/test_filters.py +443 -13
  551. nautobot/extras/tests/test_forms.py +18 -57
  552. nautobot/extras/tests/test_jobs.py +25 -4
  553. nautobot/extras/tests/test_migrations.py +81 -1
  554. nautobot/extras/tests/test_models.py +378 -47
  555. nautobot/extras/tests/test_plugins.py +47 -13
  556. nautobot/extras/tests/test_relationships.py +7 -2
  557. nautobot/extras/tests/test_utils.py +2 -0
  558. nautobot/extras/tests/test_views.py +780 -493
  559. nautobot/extras/urls.py +36 -12
  560. nautobot/extras/utils.py +58 -12
  561. nautobot/extras/views.py +668 -209
  562. nautobot/ipam/factory.py +7 -0
  563. nautobot/ipam/filter_mixins.py +38 -0
  564. nautobot/ipam/filters.py +35 -71
  565. nautobot/ipam/formfields.py +1 -1
  566. nautobot/ipam/forms.py +6 -3
  567. nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
  568. nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
  569. nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
  570. nautobot/ipam/models.py +29 -2
  571. nautobot/ipam/navigation.py +3 -1
  572. nautobot/ipam/querysets.py +1 -2
  573. nautobot/ipam/tables.py +26 -17
  574. nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +6 -6
  575. nautobot/ipam/templates/ipam/inc/service.html +8 -8
  576. nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
  577. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +3 -2
  578. nautobot/ipam/templates/ipam/ipaddress.html +27 -13
  579. nautobot/ipam/templates/ipam/ipaddress_assign.html +31 -24
  580. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +3 -3
  581. nautobot/ipam/templates/ipam/ipaddress_edit.html +9 -9
  582. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +7 -9
  583. nautobot/ipam/templates/ipam/ipaddress_merge.html +195 -186
  584. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +7 -9
  585. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +7 -5
  586. nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
  587. nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
  588. nautobot/ipam/templates/ipam/namespace_update.html +15 -0
  589. nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
  590. nautobot/ipam/templates/ipam/prefix_create.html +9 -9
  591. nautobot/ipam/templates/ipam/prefix_list.html +15 -14
  592. nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -1
  593. nautobot/ipam/templates/ipam/vlan.html +1 -1
  594. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  595. nautobot/ipam/templates/ipam/vlan_update.html +6 -6
  596. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  597. nautobot/ipam/templates/ipam/vrf_edit.html +15 -15
  598. nautobot/ipam/tests/integration/test_prefixes.py +5 -13
  599. nautobot/ipam/tests/migration/test_migrations.py +89 -0
  600. nautobot/ipam/tests/test_api.py +20 -7
  601. nautobot/ipam/tests/test_filters.py +10 -0
  602. nautobot/ipam/tests/test_forms.py +1 -1
  603. nautobot/ipam/tests/test_models.py +1 -1
  604. nautobot/ipam/tests/test_tables.py +1 -2
  605. nautobot/ipam/tests/test_utils.py +1 -1
  606. nautobot/ipam/tests/test_views.py +24 -21
  607. nautobot/ipam/ui.py +0 -17
  608. nautobot/ipam/utils/migrations.py +16 -2
  609. nautobot/ipam/utils/testing.py +9 -3
  610. nautobot/ipam/views.py +49 -7
  611. nautobot/project-static/dist/css/graphql-libraries.css +655 -0
  612. nautobot/project-static/dist/css/graphql-libraries.css.map +1 -0
  613. nautobot/project-static/dist/css/materialdesignicons.css +3 -0
  614. nautobot/project-static/dist/css/materialdesignicons.css.map +1 -0
  615. nautobot/project-static/dist/css/nautobot.css +13 -0
  616. nautobot/project-static/dist/css/nautobot.css.map +1 -0
  617. nautobot/project-static/dist/js/graphql-libraries.js +3 -0
  618. nautobot/project-static/dist/js/graphql-libraries.js.LICENSE.txt +62 -0
  619. nautobot/project-static/dist/js/graphql-libraries.js.map +1 -0
  620. nautobot/project-static/dist/js/libraries.js +3 -0
  621. nautobot/project-static/dist/js/libraries.js.LICENSE.txt +65 -0
  622. nautobot/project-static/dist/js/libraries.js.map +1 -0
  623. nautobot/project-static/dist/js/materialdesignicons.js +0 -0
  624. nautobot/project-static/dist/js/nautobot-graphiql.js +2 -0
  625. nautobot/project-static/dist/js/nautobot-graphiql.js.map +1 -0
  626. nautobot/project-static/dist/js/nautobot.js +2 -0
  627. nautobot/project-static/dist/js/nautobot.js.map +1 -0
  628. nautobot/project-static/fonts/Montserrat-v30-Bold.woff2 +0 -0
  629. nautobot/project-static/fonts/Montserrat-v30-Light.woff2 +0 -0
  630. nautobot/project-static/fonts/Montserrat-v30-Regular.woff2 +0 -0
  631. nautobot/project-static/fonts/Roboto-v48-Bold.woff2 +0 -0
  632. nautobot/project-static/fonts/Roboto-v48-Light.woff2 +0 -0
  633. nautobot/project-static/fonts/Roboto-v48-Regular.woff2 +0 -0
  634. nautobot/project-static/img/jinja_logo.svg +21 -92
  635. nautobot/project-static/js/cabletrace.js +1 -1
  636. nautobot/project-static/js/editor.js +4 -4
  637. nautobot/project-static/js/forms.js +67 -717
  638. nautobot/project-static/js/job_result.js +2 -2
  639. nautobot/project-static/nautobot-icons/360-degrees.svg +3 -0
  640. nautobot/project-static/nautobot-icons/arrow-decision.svg +3 -0
  641. nautobot/project-static/nautobot-icons/arrows-expand-rec.svg +3 -0
  642. nautobot/project-static/nautobot-icons/arrows-move-2-rec.svg +3 -0
  643. nautobot/project-static/nautobot-icons/arrows-move-rec.svg +3 -0
  644. nautobot/project-static/nautobot-icons/atom.svg +3 -0
  645. nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
  646. nautobot/project-static/nautobot-icons/branch.svg +3 -0
  647. nautobot/project-static/nautobot-icons/briefcase-2.svg +3 -0
  648. nautobot/project-static/nautobot-icons/cable-data-2.svg +3 -0
  649. nautobot/project-static/nautobot-icons/cable-data.svg +3 -0
  650. nautobot/project-static/nautobot-icons/cast.svg +3 -0
  651. nautobot/project-static/nautobot-icons/check-circle.svg +3 -0
  652. nautobot/project-static/nautobot-icons/checkbox-circle.svg +3 -0
  653. nautobot/project-static/nautobot-icons/checkbox-rec.svg +3 -0
  654. nautobot/project-static/nautobot-icons/cloud-check.svg +3 -0
  655. nautobot/project-static/nautobot-icons/cloud-lightning.svg +3 -0
  656. nautobot/project-static/nautobot-icons/cloud-upload.svg +3 -0
  657. nautobot/project-static/nautobot-icons/cloud.svg +3 -0
  658. nautobot/project-static/nautobot-icons/compass.svg +3 -0
  659. nautobot/project-static/nautobot-icons/control-panel.svg +3 -0
  660. nautobot/project-static/nautobot-icons/credit-card.svg +3 -0
  661. nautobot/project-static/nautobot-icons/device-lifecycle.svg +3 -0
  662. nautobot/project-static/nautobot-icons/direction.svg +3 -0
  663. nautobot/project-static/nautobot-icons/elements.svg +3 -0
  664. nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
  665. nautobot/project-static/nautobot-icons/globe-2.svg +3 -0
  666. nautobot/project-static/nautobot-icons/globe.svg +3 -0
  667. nautobot/project-static/nautobot-icons/hammer.svg +3 -0
  668. nautobot/project-static/nautobot-icons/history.svg +3 -0
  669. nautobot/project-static/nautobot-icons/ip.svg +3 -0
  670. nautobot/project-static/nautobot-icons/laptop.svg +3 -0
  671. nautobot/project-static/nautobot-icons/lightning.svg +3 -0
  672. nautobot/project-static/nautobot-icons/list-unordered.svg +3 -0
  673. nautobot/project-static/nautobot-icons/map-view.svg +3 -0
  674. nautobot/project-static/nautobot-icons/organization.svg +3 -0
  675. nautobot/project-static/nautobot-icons/pin-2.svg +3 -0
  676. nautobot/project-static/nautobot-icons/pin-3.svg +3 -0
  677. nautobot/project-static/nautobot-icons/plug.svg +3 -0
  678. nautobot/project-static/nautobot-icons/refresh-cw.svg +3 -0
  679. nautobot/project-static/nautobot-icons/rocket-2.svg +3 -0
  680. nautobot/project-static/nautobot-icons/rotate-cw.svg +3 -0
  681. nautobot/project-static/nautobot-icons/route.svg +3 -0
  682. nautobot/project-static/nautobot-icons/secrets.svg +3 -0
  683. nautobot/project-static/nautobot-icons/security.svg +3 -0
  684. nautobot/project-static/nautobot-icons/server-2.svg +3 -0
  685. nautobot/project-static/nautobot-icons/server.svg +3 -0
  686. nautobot/project-static/nautobot-icons/share.svg +3 -0
  687. nautobot/project-static/nautobot-icons/shield-check.svg +3 -0
  688. nautobot/project-static/nautobot-icons/sitemap-outline.svg +3 -0
  689. nautobot/project-static/nautobot-icons/sliders-vert-2.svg +3 -0
  690. nautobot/project-static/nautobot-icons/sliders-vert.svg +3 -0
  691. nautobot/project-static/nautobot-icons/star-filled.svg +3 -0
  692. nautobot/project-static/nautobot-icons/star.svg +3 -0
  693. nautobot/project-static/nautobot-icons/transform.svg +3 -0
  694. nautobot/project-static/nautobot-icons/wifi.svg +3 -0
  695. nautobot/tenancy/api/serializers.py +1 -0
  696. nautobot/tenancy/api/views.py +2 -1
  697. nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
  698. nautobot/tenancy/navigation.py +3 -1
  699. nautobot/tenancy/templates/tenancy/tenant_create.html +6 -6
  700. nautobot/tenancy/tests/test_filters.py +0 -2
  701. nautobot/tenancy/views.py +2 -1
  702. nautobot/ui/.gitignore +137 -0
  703. nautobot/ui/.node-version +1 -0
  704. nautobot/ui/.prettierignore +3 -0
  705. nautobot/ui/eslint.config.js +33 -0
  706. nautobot/ui/package-lock.json +6594 -0
  707. nautobot/ui/package.json +67 -0
  708. nautobot/ui/prettier.config.js +9 -0
  709. nautobot/ui/src/js/collapse.js +69 -0
  710. nautobot/ui/src/js/cookie.js +31 -0
  711. nautobot/ui/src/js/draggable.js +101 -0
  712. nautobot/ui/src/js/drawer.js +106 -0
  713. nautobot/ui/src/js/form.js +23 -0
  714. nautobot/ui/src/js/history.js +51 -0
  715. nautobot/ui/src/js/nautobot-graphiql.js +19 -0
  716. nautobot/ui/src/js/nautobot.js +128 -0
  717. nautobot/ui/src/js/search.js +274 -0
  718. nautobot/ui/src/js/select2.js +318 -0
  719. nautobot/ui/src/js/sidenav.js +87 -0
  720. nautobot/ui/src/js/tabs.js +139 -0
  721. nautobot/ui/src/js/theme.js +104 -0
  722. nautobot/ui/src/js/utils.js +54 -0
  723. nautobot/ui/src/scss/colors.scss +58 -0
  724. nautobot/ui/src/scss/nautobot.scss +2471 -0
  725. nautobot/ui/webpack.config.js +148 -0
  726. nautobot/users/apps.py +3 -0
  727. nautobot/users/filters.py +7 -11
  728. nautobot/users/forms.py +10 -0
  729. nautobot/users/models.py +8 -0
  730. nautobot/users/templates/users/advanced_settings_edit.html +31 -21
  731. nautobot/users/templates/users/api_tokens.html +61 -51
  732. nautobot/users/templates/users/base.html +23 -31
  733. nautobot/users/templates/users/change_password.html +29 -19
  734. nautobot/users/templates/users/preferences.html +55 -45
  735. nautobot/users/templates/users/profile.html +45 -14
  736. nautobot/users/tests/test_api.py +4 -0
  737. nautobot/users/urls.py +2 -0
  738. nautobot/users/views.py +70 -2
  739. nautobot/virtualization/api/views.py +1 -1
  740. nautobot/virtualization/filters.py +18 -32
  741. nautobot/virtualization/forms.py +22 -59
  742. nautobot/virtualization/models.py +1 -19
  743. nautobot/virtualization/navigation.py +3 -1
  744. nautobot/virtualization/tables.py +10 -6
  745. nautobot/virtualization/templates/virtualization/cluster.html +13 -13
  746. nautobot/virtualization/templates/virtualization/cluster_edit.html +6 -6
  747. nautobot/virtualization/templates/virtualization/inc/virtualmachine_vminterface_filter.html +1 -1
  748. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  749. nautobot/virtualization/templates/virtualization/virtualmachine_component_add.html +24 -16
  750. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +1 -1
  751. nautobot/virtualization/templates/virtualization/virtualmachine_list.html +4 -4
  752. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +27 -25
  753. nautobot/virtualization/templates/virtualization/vminterface.html +5 -5
  754. nautobot/virtualization/templates/virtualization/vminterface_edit.html +27 -11
  755. nautobot/virtualization/tests/test_api.py +3 -0
  756. nautobot/virtualization/tests/test_models.py +20 -5
  757. nautobot/virtualization/tests/test_views.py +3 -5
  758. nautobot/virtualization/urls.py +0 -11
  759. nautobot/virtualization/views.py +5 -122
  760. nautobot/vpn/__init__.py +0 -0
  761. nautobot/vpn/api/serializers.py +113 -0
  762. nautobot/vpn/api/urls.py +19 -0
  763. nautobot/vpn/api/views.py +70 -0
  764. nautobot/vpn/apps.py +8 -0
  765. nautobot/vpn/choices.py +171 -0
  766. nautobot/vpn/factory.py +209 -0
  767. nautobot/vpn/filters.py +233 -0
  768. nautobot/vpn/forms.py +486 -0
  769. nautobot/vpn/homepage.py +19 -0
  770. nautobot/vpn/migrations/0001_initial.py +541 -0
  771. nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
  772. nautobot/vpn/migrations/__init__.py +0 -0
  773. nautobot/vpn/models.py +527 -0
  774. nautobot/vpn/navigation.py +98 -0
  775. nautobot/vpn/tables.py +380 -0
  776. nautobot/vpn/templates/vpn/vpnprofile.html +2 -0
  777. nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
  778. nautobot/vpn/tests/__init__.py +0 -0
  779. nautobot/vpn/tests/test_api.py +341 -0
  780. nautobot/vpn/tests/test_filters.py +139 -0
  781. nautobot/vpn/tests/test_forms.py +294 -0
  782. nautobot/vpn/tests/test_models.py +97 -0
  783. nautobot/vpn/tests/test_views.py +281 -0
  784. nautobot/vpn/urls.py +16 -0
  785. nautobot/vpn/views.py +437 -0
  786. nautobot/wireless/filters.py +0 -8
  787. nautobot/wireless/navigation.py +3 -1
  788. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +6 -6
  789. nautobot/wireless/tests/integration/test_radio_profile.py +3 -7
  790. nautobot/wireless/tests/test_api.py +1 -1
  791. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/METADATA +5 -4
  792. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +802 -707
  793. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
  794. nautobot/core/management/commands/check_job_approval_status.py +0 -47
  795. nautobot/core/templates/search_form.html +0 -9
  796. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +0 -87
  797. nautobot/dcim/filters/mixins.py +0 -354
  798. nautobot/extras/templates/extras/job_approval_request.html +0 -134
  799. nautobot/extras/templates/extras/scheduled_jobs_approval_queue_list.html +0 -28
  800. nautobot/ipam/mixins.py +0 -32
  801. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
  802. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css +0 -587
  803. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +0 -1
  804. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css +0 -6
  805. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +0 -1
  806. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +0 -6865
  807. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +0 -1
  808. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +0 -6
  809. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +0 -1
  810. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.eot +0 -0
  811. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.svg +0 -288
  812. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.ttf +0 -0
  813. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff +0 -0
  814. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
  815. nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.js +0 -2580
  816. nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.min.js +0 -6
  817. nautobot/project-static/bootstrap-3.4.1-dist/js/npm.js +0 -13
  818. nautobot/project-static/clipboard.js-2.0.9/clipboard.min.js +0 -7
  819. nautobot/project-static/css/base.css +0 -1040
  820. nautobot/project-static/css/dark.css +0 -282
  821. nautobot/project-static/flatpickr-4.6.9/flatpickr.min.js +0 -2
  822. nautobot/project-static/flatpickr-4.6.9/themes/light.min.css +0 -1
  823. nautobot/project-static/graphiql-1.5.16/graphiql.min.css +0 -12
  824. nautobot/project-static/graphiql-1.5.16/graphiql.min.js +0 -11
  825. nautobot/project-static/highlight.js-11.9.0/github-dark.min.css +0 -10
  826. nautobot/project-static/highlight.js-11.9.0/github.min.css +0 -10
  827. nautobot/project-static/highlight.js-11.9.0/highlight.min.js +0 -378
  828. nautobot/project-static/jquery/jquery-3.7.1.min.js +0 -2
  829. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_444444_256x240.png +0 -0
  830. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_555555_256x240.png +0 -0
  831. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777620_256x240.png +0 -0
  832. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777777_256x240.png +0 -0
  833. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_cc0000_256x240.png +0 -0
  834. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_ffffff_256x240.png +0 -0
  835. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.css +0 -7
  836. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.js +0 -6
  837. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.structure.min.css +0 -5
  838. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.theme.min.css +0 -5
  839. nautobot/project-static/js/homepage_layout.js +0 -182
  840. nautobot/project-static/js/nav_menu.js +0 -250
  841. nautobot/project-static/js/theme.js +0 -133
  842. nautobot/project-static/materialdesignicons-7.4.47/LICENSE +0 -20
  843. nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +0 -3
  844. nautobot/project-static/react-16.14.0/react.production.min.js +0 -32
  845. nautobot/project-static/react-dom-16.14.0/react-dom.production.min.js +0 -239
  846. nautobot/project-static/select2-4.0.13/i18n/af.js +0 -3
  847. nautobot/project-static/select2-4.0.13/i18n/ar.js +0 -3
  848. nautobot/project-static/select2-4.0.13/i18n/az.js +0 -3
  849. nautobot/project-static/select2-4.0.13/i18n/bg.js +0 -3
  850. nautobot/project-static/select2-4.0.13/i18n/bn.js +0 -3
  851. nautobot/project-static/select2-4.0.13/i18n/bs.js +0 -3
  852. nautobot/project-static/select2-4.0.13/i18n/ca.js +0 -3
  853. nautobot/project-static/select2-4.0.13/i18n/cs.js +0 -3
  854. nautobot/project-static/select2-4.0.13/i18n/da.js +0 -3
  855. nautobot/project-static/select2-4.0.13/i18n/de.js +0 -3
  856. nautobot/project-static/select2-4.0.13/i18n/dsb.js +0 -3
  857. nautobot/project-static/select2-4.0.13/i18n/el.js +0 -3
  858. nautobot/project-static/select2-4.0.13/i18n/en.js +0 -3
  859. nautobot/project-static/select2-4.0.13/i18n/es.js +0 -3
  860. nautobot/project-static/select2-4.0.13/i18n/et.js +0 -3
  861. nautobot/project-static/select2-4.0.13/i18n/eu.js +0 -3
  862. nautobot/project-static/select2-4.0.13/i18n/fa.js +0 -3
  863. nautobot/project-static/select2-4.0.13/i18n/fi.js +0 -3
  864. nautobot/project-static/select2-4.0.13/i18n/fr.js +0 -3
  865. nautobot/project-static/select2-4.0.13/i18n/gl.js +0 -3
  866. nautobot/project-static/select2-4.0.13/i18n/he.js +0 -3
  867. nautobot/project-static/select2-4.0.13/i18n/hi.js +0 -3
  868. nautobot/project-static/select2-4.0.13/i18n/hr.js +0 -3
  869. nautobot/project-static/select2-4.0.13/i18n/hsb.js +0 -3
  870. nautobot/project-static/select2-4.0.13/i18n/hu.js +0 -3
  871. nautobot/project-static/select2-4.0.13/i18n/hy.js +0 -3
  872. nautobot/project-static/select2-4.0.13/i18n/id.js +0 -3
  873. nautobot/project-static/select2-4.0.13/i18n/is.js +0 -3
  874. nautobot/project-static/select2-4.0.13/i18n/it.js +0 -3
  875. nautobot/project-static/select2-4.0.13/i18n/ja.js +0 -3
  876. nautobot/project-static/select2-4.0.13/i18n/ka.js +0 -3
  877. nautobot/project-static/select2-4.0.13/i18n/km.js +0 -3
  878. nautobot/project-static/select2-4.0.13/i18n/ko.js +0 -3
  879. nautobot/project-static/select2-4.0.13/i18n/lt.js +0 -3
  880. nautobot/project-static/select2-4.0.13/i18n/lv.js +0 -3
  881. nautobot/project-static/select2-4.0.13/i18n/mk.js +0 -3
  882. nautobot/project-static/select2-4.0.13/i18n/ms.js +0 -3
  883. nautobot/project-static/select2-4.0.13/i18n/nb.js +0 -3
  884. nautobot/project-static/select2-4.0.13/i18n/ne.js +0 -3
  885. nautobot/project-static/select2-4.0.13/i18n/nl.js +0 -3
  886. nautobot/project-static/select2-4.0.13/i18n/pl.js +0 -3
  887. nautobot/project-static/select2-4.0.13/i18n/ps.js +0 -3
  888. nautobot/project-static/select2-4.0.13/i18n/pt-BR.js +0 -3
  889. nautobot/project-static/select2-4.0.13/i18n/pt.js +0 -3
  890. nautobot/project-static/select2-4.0.13/i18n/ro.js +0 -3
  891. nautobot/project-static/select2-4.0.13/i18n/ru.js +0 -3
  892. nautobot/project-static/select2-4.0.13/i18n/sk.js +0 -3
  893. nautobot/project-static/select2-4.0.13/i18n/sl.js +0 -3
  894. nautobot/project-static/select2-4.0.13/i18n/sq.js +0 -3
  895. nautobot/project-static/select2-4.0.13/i18n/sr-Cyrl.js +0 -3
  896. nautobot/project-static/select2-4.0.13/i18n/sr.js +0 -3
  897. nautobot/project-static/select2-4.0.13/i18n/sv.js +0 -3
  898. nautobot/project-static/select2-4.0.13/i18n/th.js +0 -3
  899. nautobot/project-static/select2-4.0.13/i18n/tk.js +0 -3
  900. nautobot/project-static/select2-4.0.13/i18n/tr.js +0 -3
  901. nautobot/project-static/select2-4.0.13/i18n/uk.js +0 -3
  902. nautobot/project-static/select2-4.0.13/i18n/vi.js +0 -3
  903. nautobot/project-static/select2-4.0.13/i18n/zh-CN.js +0 -3
  904. nautobot/project-static/select2-4.0.13/i18n/zh-TW.js +0 -3
  905. nautobot/project-static/select2-4.0.13/select2.min.css +0 -1
  906. nautobot/project-static/select2-4.0.13/select2.min.js +0 -2
  907. nautobot/project-static/select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css +0 -7
  908. nautobot/project-static/subscriptions-transport-ws-0.9.18/client.min.js +0 -8
  909. nautobot/project-static/whatwg-fetch-3.6.2/fetch.umd.min.js +0 -8
  910. nautobot/virtualization/templates/virtualization/cluster_add_devices.html +0 -37
  911. /nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +0 -0
  912. /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.ttf → dist/1fcc36272ea3e53d0031.ttf} +0 -0
  913. /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.eot → dist/2146c3c82b553977abc7.eot} +0 -0
  914. /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff → dist/e55a20c80650829ec5fd.woff} +0 -0
  915. /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 → dist/ec024da790d2972da002.woff2} +0 -0
  916. /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
  917. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
  918. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
  919. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
@@ -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
@@ -112,29 +112,7 @@
112
112
  "availability": "Open Source"
113
113
  "icon": >-
114
114
  data:image/png;base64,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
115
- - "name": "Data Validation Engine"
116
- "use_cases":
117
- - "Data Management"
118
- - "Data Hygiene"
119
- - "Enterprise Governance"
120
- "requires": []
121
- "docs": "https://docs.nautobot.com/projects/data-validation/en/latest/"
122
- "headline": >-
123
- Guarantee Enterprise Data Governance with proper data hygiene by defining the right business rules for your
124
- data.
125
- "description": >-
126
- Data Validation Engine offers a streamlined no-code user experience to create data validation rules using Regular
127
- Expressions and min/mix rules ensuring corporate standards are enforced. It also supports advanced and complex
128
- business rule logic by using the Data Validation APIs (with code).
129
- "package_name": "nautobot_data_validation_engine"
130
- "author": "Network to Code (NTC)"
131
- "display":
132
- "cloud": true
133
- "oss": true
134
- "enterprise": true
135
- "availability": "Open Source"
136
- "icon": >-
137
- data:image/png;base64,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
115
+ # TODO: remove data_validation_engine from marketplace repo so it doesn't get reintroduced here by accident
138
116
  - "name": "Design Builder"
139
117
  "use_cases":
140
118
  - "Network Design"
@@ -20,38 +20,40 @@ 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>
41
41
  """,
42
- attrs={"td": {"class": "text-right text-nowrap noprint"}},
42
+ attrs={"td": {"class": "d-print-none text-end text-nowrap nb-w-0"}, "th": {"class": "nb-actionable nb-w-0"}},
43
43
  verbose_name="",
44
44
  )
45
45
 
46
46
  class Meta:
47
47
  attrs = {
48
- "class": "table table-hover table-headings",
48
+ "class": "table table-hover nb-table-headings",
49
49
  }
50
50
  default_columns = ("name", "headline", "version", "actions")
51
51
 
52
- def __init__(self, *args, user=None, **kwargs):
52
+ def __init__(self, *args, user=None, configurable=False, **kwargs):
53
53
  super().__init__(*args, **kwargs)
54
54
 
55
+ self.configurable = configurable
56
+
55
57
  if self.empty_text is None:
56
58
  self.empty_text = "No installed Apps found"
57
59
 
@@ -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"))))
@@ -4,7 +4,6 @@ Plugin utilities.
4
4
 
5
5
  import importlib.util
6
6
  import logging
7
- import sys
8
7
 
9
8
  from django.core.exceptions import ImproperlyConfigured
10
9
  from django.utils.module_loading import import_string
@@ -17,37 +16,6 @@ from .exceptions import PluginImproperlyConfigured, PluginNotFound
17
16
  logger = logging.getLogger(__name__)
18
17
 
19
18
 
20
- def import_object(module_and_object):
21
- """
22
- Import a specific object from a specific module by name, such as "nautobot.extras.plugins.utils.import_object".
23
-
24
- Returns the imported object, or None if it doesn't exist.
25
- """
26
- target_module_name, object_name = module_and_object.rsplit(".", 1)
27
- module_hierarchy = target_module_name.split(".")
28
-
29
- # Iterate through the module hierarchy, checking for the existence of each successive submodule.
30
- # We have to do this rather than jumping directly to calling find_spec(target_module_name)
31
- # because find_spec will raise a ModuleNotFoundError if any parent module of target_module_name does not exist.
32
- module_name = ""
33
- for module_component in module_hierarchy:
34
- module_name = f"{module_name}.{module_component}" if module_name else module_component
35
- spec = importlib.util.find_spec(module_name)
36
- if spec is None:
37
- # No such module
38
- return None
39
-
40
- # Okay, target_module_name exists. Load it if not already loaded
41
- if target_module_name in sys.modules:
42
- module = sys.modules[target_module_name]
43
- else:
44
- module = importlib.util.module_from_spec(spec)
45
- sys.modules[target_module_name] = module
46
- spec.loader.exec_module(module)
47
-
48
- return getattr(module, object_name, None)
49
-
50
-
51
19
  def load_plugins(settings):
52
20
  """Process plugins and log errors if they can't be loaded."""
53
21
  for plugin_name in settings.PLUGINS:
@@ -77,7 +45,7 @@ def load_plugin(plugin_name, settings):
77
45
  except AttributeError as err:
78
46
  raise PluginImproperlyConfigured(
79
47
  f"Plugin {plugin_name} does not provide a 'config' variable. This should be defined in the plugin's "
80
- f"__init__.py file and point to the PluginConfig subclass."
48
+ f"__init__.py file and point to the NautobotAppConfig subclass."
81
49
  ) from err
82
50
 
83
51
  # Validate user-provided configuration settings and assign defaults. Plugin
@@ -7,26 +7,26 @@ from nautobot.extras.registry import registry
7
7
  from nautobot.extras.utils import FeatureQuery
8
8
 
9
9
 
10
- def custom_validator_clean(model_clean_func):
10
+ def custom_validator_clean(model_class):
11
11
  """
12
12
  Decorator that wraps a models existing clean method to also execute registered plugin custom validators
13
13
 
14
- :param model_clean_func: The original model clean method which is to be wrapped
14
+ Args:
15
+ model_class: The model class whose clean function is to be wrapped
15
16
  """
16
17
 
18
+ model_clean_func = model_class.clean
19
+ model_name = model_class._meta.label_lower
20
+
17
21
  @wraps(model_clean_func)
18
22
  def wrapper(model_instance):
19
23
  # Run original model clean method
20
24
  model_clean_func(model_instance)
21
25
 
22
- # Run registered plugin custom validators
23
- model_name = model_instance._meta.label_lower
24
-
25
- # Note this registry holds instances of PluginCustomValidator registered from plugins
26
+ # Run App registered custom validators
27
+ # Note this registry holds instances of CustomValidator registered from plugins
26
28
  # which is different than the `custom_validators` model features registry
27
- custom_validators = registry["plugin_custom_validators"].get(model_name, [])
28
-
29
- for custom_validator in custom_validators:
29
+ for custom_validator in registry["plugin_custom_validators"][model_name]:
30
30
  # If the class has not overridden the specified method, we can skip it (because we know it
31
31
  # will raise NotImplementedError).
32
32
  if getattr(custom_validator, "clean") == getattr(CustomValidator, "clean"):
@@ -43,4 +43,4 @@ def wrap_model_clean_methods():
43
43
  for app_label, models in FeatureQuery("custom_validators").as_dict():
44
44
  for model in models:
45
45
  model_class = apps.get_model(app_label=app_label, model_name=model)
46
- model_class.clean = custom_validator_clean(model_class.clean)
46
+ model_class.clean = custom_validator_clean(model_class)
@@ -86,7 +86,6 @@ class InstalledAppsView(GenericView):
86
86
  """
87
87
 
88
88
  table = InstalledAppsTable
89
- breadcrumbs = Breadcrumbs(items={"*": [ViewNameBreadcrumbItem(view_name="apps:apps_list", label="Installed Apps")]})
90
89
  view_titles = Titles(titles={"*": "Installed Apps"})
91
90
 
92
91
  def get(self, request):
@@ -96,7 +95,7 @@ class InstalledAppsView(GenericView):
96
95
  for app_config in app_configs:
97
96
  if app_data := extract_app_data(app_config, marketplace_data):
98
97
  data.append(app_data)
99
- table = self.table(data, user=request.user)
98
+ table = self.table(data, user=request.user, configurable=True)
100
99
 
101
100
  paginate = {
102
101
  "paginator_class": EnhancedPaginator,
@@ -250,9 +249,6 @@ class MarketplaceView(GenericView):
250
249
  View for listing all available Apps.
251
250
  """
252
251
 
253
- breadcrumbs = Breadcrumbs(
254
- items={"generic": [ViewNameBreadcrumbItem(view_name="apps:apps_marketplace", label="Apps Marketplace")]}
255
- )
256
252
  view_titles = Titles(titles={"generic": "Apps Marketplace"})
257
253
 
258
254
  def get(self, request):
@@ -19,10 +19,6 @@ class ConfigContextQuerySet(RestrictedQuerySet):
19
19
  # `device_type` for Device; `type` for VirtualMachine
20
20
  device_type = getattr(obj, "device_type", None)
21
21
 
22
- # Virtualization cluster for VirtualMachine
23
- cluster = getattr(obj, "cluster", None)
24
- cluster_group = getattr(cluster, "cluster_group", None)
25
-
26
22
  device_redundancy_group = getattr(obj, "device_redundancy_group", None)
27
23
 
28
24
  # Get the group of the assigned tenant, if any
@@ -45,8 +41,6 @@ class ConfigContextQuerySet(RestrictedQuerySet):
45
41
  Q(roles=role) | Q(roles=None),
46
42
  Q(device_types=device_type) | Q(device_types=None),
47
43
  Q(platforms=obj.platform) | Q(platforms=None),
48
- Q(cluster_groups=cluster_group) | Q(cluster_groups=None),
49
- Q(clusters=cluster) | Q(clusters=None),
50
44
  Q(device_redundancy_groups=device_redundancy_group) | Q(device_redundancy_groups=None),
51
45
  Q(tenant_groups__in=tenant_groups) | Q(tenant_groups=None),
52
46
  Q(tenants=tenant) | Q(tenants=None),
@@ -55,6 +49,16 @@ class ConfigContextQuerySet(RestrictedQuerySet):
55
49
  if settings.CONFIG_CONTEXT_DYNAMIC_GROUPS_ENABLED:
56
50
  query.append(Q(dynamic_groups__in=obj.dynamic_groups) | Q(dynamic_groups=None))
57
51
 
52
+ # `clusters` for Device, `cluster` for VirtualMachine
53
+ if obj._meta.model_name == "device":
54
+ query.append(Q(clusters__in=obj.clusters.all()) | Q(clusters=None))
55
+ query.append(
56
+ Q(cluster_groups__in=obj.clusters.values_list("cluster_group", flat=True)) | Q(cluster_groups=None)
57
+ )
58
+ else:
59
+ query.append(Q(clusters=obj.cluster) | Q(clusters=None))
60
+ query.append(Q(cluster_groups=obj.cluster.cluster_group) | Q(cluster_groups=None))
61
+
58
62
  queryset = (
59
63
  self.filter(
60
64
  *query,
@@ -119,8 +123,6 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
119
123
  }
120
124
  base_query = Q(
121
125
  Q(platforms=OuterRef("platform")) | Q(platforms=None),
122
- Q(cluster_groups=OuterRef("cluster__cluster_group")) | Q(cluster_groups=None),
123
- Q(clusters=OuterRef("cluster")) | Q(clusters=None),
124
126
  Q(tenants=OuterRef("tenant")) | Q(tenants=None),
125
127
  Q(tags__pk__in=Subquery(TaggedItem.objects.filter(**tag_query_filters).values_list("tag_id", flat=True)))
126
128
  | Q(tags=None),
@@ -138,8 +140,14 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
138
140
  (Q(device_redundancy_groups=OuterRef("device_redundancy_group")) | Q(device_redundancy_groups=None)),
139
141
  Q.AND,
140
142
  )
143
+ # For devices, handle clusters as ManyToMany relationship
144
+ base_query.add((Q(clusters=OuterRef("clusters")) | Q(clusters=None)), Q.AND)
145
+ base_query.add((Q(cluster_groups=OuterRef("clusters__cluster_group")) | Q(cluster_groups=None)), Q.AND)
141
146
  else:
142
147
  location_query_string = "cluster__location"
148
+ # For virtual machines, handle cluster as ForeignKey relationship
149
+ base_query.add((Q(clusters=OuterRef("cluster")) | Q(clusters=None)), Q.AND)
150
+ base_query.add((Q(cluster_groups=OuterRef("cluster__cluster_group")) | Q(cluster_groups=None)), Q.AND)
143
151
 
144
152
  location_query = Q(locations=None) | Q(locations=OuterRef(location_query_string))
145
153
  for _ in range(Location.objects.max_depth + 1):
@@ -254,17 +262,5 @@ class ScheduledJobExtendedQuerySet(RestrictedQuerySet):
254
262
  Return only ScheduledJob instances that are enabled and approved (if approval required)
255
263
  """
256
264
  return self.filter(
257
- Q(enabled=True) & Q(Q(approval_required=True, approved_at__isnull=False) | Q(approval_required=False))
265
+ Q(enabled=True) & Q(Q(approval_required=True, decision_date__isnull=False) | Q(approval_required=False))
258
266
  )
259
-
260
- def approved(self):
261
- """
262
- Return only ScheduledJob instances that require approval and are approved
263
- """
264
- return self.filter(approval_required=True, approved_at__isnull=False)
265
-
266
- def needs_approved(self):
267
- """
268
- Return only ScheduledJob instances that require approval and are not approved
269
- """
270
- return self.filter(approval_required=True, approved_at__isnull=True).order_by("start_time")
@@ -22,6 +22,7 @@ import redis.exceptions
22
22
 
23
23
  from nautobot.core.celery import app, import_jobs
24
24
  from nautobot.core.models import BaseModel
25
+ from nautobot.core.utils.cache import construct_cache_key
25
26
  from nautobot.core.utils.logging import sanitize
26
27
  from nautobot.extras.choices import ButtonClassChoices, JobResultStatusChoices, ObjectChangeActionChoices
27
28
  from nautobot.extras.constants import CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL
@@ -142,9 +143,11 @@ def invalidate_models_cache(sender, **kwargs):
142
143
 
143
144
  with contextlib.suppress(redis.exceptions.ConnectionError):
144
145
  # TODO: *maybe* target more narrowly, e.g. only clear the cache for specific related content-types?
145
- cache.delete_pattern(f"{manager.get_for_model.cache_key_prefix}.*")
146
+ cache_key = construct_cache_key(manager, method_name="get_for_model", branch_aware=True)
147
+ cache.delete_pattern(f"{cache_key}(*)")
146
148
  if hasattr(manager, "keys_for_model"):
147
- cache.delete_pattern(f"{manager.keys_for_model.cache_key_prefix}.*")
149
+ cache_key = construct_cache_key(manager, method_name="keys_for_model", branch_aware=True)
150
+ cache.delete_pattern(f"{cache_key}(*)")
148
151
 
149
152
 
150
153
  @receiver(post_delete, sender=CustomField)
@@ -155,9 +158,10 @@ def invalidate_choices_cache(sender, instance, **kwargs):
155
158
  """Invalidate the choices cache for CustomFields."""
156
159
  with contextlib.suppress(redis.exceptions.ConnectionError):
157
160
  if sender is CustomField:
158
- cache.delete(instance.choices_cache_key)
161
+ cache_key = construct_cache_key(instance, method_name="choices", branch_aware=True)
159
162
  else:
160
- cache.delete(instance.custom_field.choices_cache_key)
163
+ cache_key = construct_cache_key(instance.custom_field, method_name="choices", branch_aware=True)
164
+ cache.delete(cache_key)
161
165
 
162
166
 
163
167
  @receiver(post_save, sender=Relationship)
@@ -165,13 +169,14 @@ def invalidate_choices_cache(sender, instance, **kwargs):
165
169
  @receiver(post_delete, sender=Relationship)
166
170
  def invalidate_relationship_models_cache(sender, **kwargs):
167
171
  """Invalidate the related-models caches for Relationships."""
168
- for method in (
169
- Relationship.objects.get_for_model_source,
170
- Relationship.objects.get_for_model_destination,
172
+ for method_name in (
173
+ "get_for_model_source",
174
+ "get_for_model_destination",
171
175
  ):
172
176
  with contextlib.suppress(redis.exceptions.ConnectionError):
173
177
  # TODO: *maybe* target more narrowly, e.g. only clear the cache for specific related content-types?
174
- cache.delete_pattern(f"{method.cache_key_prefix}.*")
178
+ cache_key = construct_cache_key(Relationship.objects, method_name=method_name, branch_aware=True)
179
+ cache.delete_pattern(f"{cache_key}(*)")
175
180
 
176
181
 
177
182
  @receiver(post_save, sender=CustomField)
@@ -200,6 +205,16 @@ def _handle_changed_object_pre_save(sender, instance, raw=False, **kwargs):
200
205
  _cache_obj_data_in_change_context(ObjectChangeActionChoices.ACTION_UPDATE, sender, instance)
201
206
 
202
207
 
208
+ @receiver(post_save, sender=GitRepository)
209
+ @receiver(post_delete, sender=GitRepository)
210
+ def invalidate_gitrepository_provided_contents_cache(sender, **kwargs):
211
+ with contextlib.suppress(redis.exceptions.ConnectionError):
212
+ cache_key = construct_cache_key(
213
+ GitRepository.objects, method_name="get_for_provided_contents", branch_aware=True
214
+ )
215
+ cache.delete_pattern(f"{cache_key}(*)")
216
+
217
+
203
218
  @receiver(post_save)
204
219
  @receiver(m2m_changed)
205
220
  def _handle_changed_object(sender, instance, raw=False, **kwargs):