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
@@ -1,5 +1,6 @@
1
1
  from django.conf import settings
2
2
  from django.contrib.contenttypes.models import ContentType
3
+ from django.db import transaction
3
4
  from django.db.models import ProtectedError
4
5
  from django.forms import ValidationError as FormsValidationError
5
6
  from django.http import FileResponse, Http404
@@ -897,55 +898,61 @@ class JobViewSetBase(
897
898
  if schedule_data is None:
898
899
  schedule_data = {"interval": JobExecutionType.TYPE_IMMEDIATELY, "start_time": timezone.now()}
899
900
 
900
- schedule = ScheduledJob.create_schedule(
901
- job_model,
902
- request.user,
903
- name=schedule_data.get("name"),
904
- start_time=schedule_data.get("start_time"),
905
- interval=schedule_data.get("interval"),
906
- crontab=schedule_data.get("crontab", ""),
907
- job_queue=job_queue,
908
- validated_save=False,
909
- **job_class.serialize_data(cleaned_data),
910
- )
911
-
912
- scheduled_job_has_approval_workflow = schedule.has_approval_workflow_definition()
913
- if job_model.has_sensitive_variables:
914
- if (
915
- "schedule" in request.data
916
- and "interval" in request.data["schedule"]
917
- and request.data["schedule"]["interval"] != JobExecutionType.TYPE_IMMEDIATELY
918
- ):
919
- raise ValidationError(
920
- {"schedule": {"interval": ["Unable to schedule job: Job may have sensitive input variables"]}}
921
- )
922
- # check approval_required pointer
923
- if scheduled_job_has_approval_workflow:
924
- raise ValidationError(
925
- "Unable to run or schedule job: "
926
- "This job is flagged as possibly having sensitive variables but also has an applicable approval workflow definition."
927
- "Modify or remove the approval workflow definition or modify the job to set `has_sensitive_variables` to False."
928
- )
929
-
930
- # Approval is not required for dryrun
931
- # TODO: remove this once we have the ability to configure an approval workflow to ignore jobs with specific parameters(including `dryrun`)
932
- dryrun = data.get("dryrun", False) if job_class.supports_dryrun else False
933
-
934
- if (not dryrun and scheduled_job_has_approval_workflow) or schedule_data[
935
- "interval"
936
- ] in JobExecutionType.SCHEDULE_CHOICES:
937
- schedule.validated_save()
938
- serializer = serializers.ScheduledJobSerializer(schedule, context={"request": request})
939
- return Response({"scheduled_job": serializer.data, "job_result": None}, status=status.HTTP_201_CREATED)
940
- else:
941
- job_result = JobResult.enqueue_job(
901
+ with transaction.atomic():
902
+ schedule = ScheduledJob.create_schedule(
942
903
  job_model,
943
904
  request.user,
905
+ name=schedule_data.get("name"),
906
+ start_time=schedule_data.get("start_time"),
907
+ interval=schedule_data.get("interval"),
908
+ crontab=schedule_data.get("crontab", ""),
944
909
  job_queue=job_queue,
945
910
  **job_class.serialize_data(cleaned_data),
946
911
  )
947
- serializer = serializers.JobResultSerializer(job_result, context={"request": request})
948
- return Response({"scheduled_job": None, "job_result": serializer.data}, status=status.HTTP_201_CREATED)
912
+
913
+ scheduled_job_has_approval_workflow = schedule.has_approval_workflow_definition()
914
+ if job_model.has_sensitive_variables:
915
+ if (
916
+ "schedule" in request.data
917
+ and "interval" in request.data["schedule"]
918
+ and request.data["schedule"]["interval"] != JobExecutionType.TYPE_IMMEDIATELY
919
+ ):
920
+ schedule.delete()
921
+ schedule = None
922
+ raise ValidationError(
923
+ {"schedule": {"interval": ["Unable to schedule job: Job may have sensitive input variables"]}}
924
+ )
925
+ # check approval_required pointer
926
+ if scheduled_job_has_approval_workflow:
927
+ schedule.delete()
928
+ schedule = None
929
+ raise ValidationError(
930
+ "Unable to run or schedule job: "
931
+ "This job is flagged as possibly having sensitive variables but also has an applicable approval workflow definition."
932
+ "Modify or remove the approval workflow definition or modify the job to set `has_sensitive_variables` to False."
933
+ )
934
+
935
+ # Approval is not required for dryrun
936
+ # TODO: remove this once we have the ability to configure an approval workflow to ignore jobs with specific parameters(including `dryrun`)
937
+ dryrun = data.get("dryrun", False) if job_class.supports_dryrun else False
938
+
939
+ if (not dryrun and scheduled_job_has_approval_workflow) or schedule_data[
940
+ "interval"
941
+ ] in JobExecutionType.SCHEDULE_CHOICES:
942
+ serializer = serializers.ScheduledJobSerializer(schedule, context={"request": request})
943
+ return Response({"scheduled_job": serializer.data, "job_result": None}, status=status.HTTP_201_CREATED)
944
+
945
+ schedule.delete()
946
+ schedule = None
947
+
948
+ job_result = JobResult.enqueue_job(
949
+ job_model,
950
+ request.user,
951
+ job_queue=job_queue,
952
+ **job_class.serialize_data(cleaned_data),
953
+ )
954
+ serializer = serializers.JobResultSerializer(job_result, context={"request": request})
955
+ return Response({"scheduled_job": None, "job_result": serializer.data}, status=status.HTTP_201_CREATED)
949
956
 
950
957
 
951
958
  class JobViewSet(
@@ -1093,7 +1100,15 @@ class JobButtonViewSet(NotesViewSetMixin, ModelViewSet):
1093
1100
  #
1094
1101
 
1095
1102
 
1096
- class ScheduledJobViewSet(ReadOnlyModelViewSet):
1103
+ class ScheduledJobViewSet(
1104
+ # DRF mixins:
1105
+ # note no CreateModelMixin or UpdateModelMixin
1106
+ mixins.DestroyModelMixin,
1107
+ # Nautobot mixins:
1108
+ BulkDestroyModelMixin,
1109
+ # Base class
1110
+ ReadOnlyModelViewSet,
1111
+ ):
1097
1112
  """
1098
1113
  Retrieve a list of scheduled jobs
1099
1114
  """
@@ -1,7 +1,6 @@
1
1
  from celery import states
2
2
 
3
3
  from nautobot.core.choices import ChoiceSet
4
- from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
5
4
 
6
5
  #
7
6
  # Approval Workflows
@@ -159,7 +158,7 @@ class CustomFieldTypeChoices(ChoiceSet):
159
158
 
160
159
 
161
160
  class ButtonClassChoices(ChoiceSet):
162
- CLASS_DEFAULT = "default"
161
+ CLASS_DEFAULT = "default" # maps to "secondary" in v3 UI, but kept for backwards compatibility
163
162
  CLASS_PRIMARY = "primary"
164
163
  CLASS_SUCCESS = "success"
165
164
  CLASS_INFO = "info"
@@ -171,23 +170,13 @@ class ButtonClassChoices(ChoiceSet):
171
170
  (CLASS_DEFAULT, "Default"),
172
171
  (CLASS_PRIMARY, "Primary (blue)"),
173
172
  (CLASS_SUCCESS, "Success (green)"),
174
- (CLASS_INFO, "Info (aqua)"),
173
+ (CLASS_INFO, "Info (blue)"),
175
174
  (CLASS_WARNING, "Warning (orange)"),
176
175
  (CLASS_DANGER, "Danger (red)"),
177
176
  (CLASS_LINK, "None (link)"),
178
177
  )
179
178
 
180
179
 
181
- #
182
- # CustomLinks
183
- #
184
-
185
-
186
- @class_deprecated_in_favor_of(ButtonClassChoices)
187
- class CustomLinkButtonClassChoices(ButtonClassChoices):
188
- pass
189
-
190
-
191
180
  #
192
181
  # Dynamic Groups
193
182
  #
@@ -18,7 +18,7 @@ import yaml
18
18
 
19
19
  from nautobot.core.utils.git import GitRepo
20
20
  from nautobot.core.utils.module_loading import check_name_safe_to_import_privately, import_modules_privately
21
- from nautobot.dcim.models import Device, DeviceRedundancyGroup, DeviceType, Location, Platform
21
+ from nautobot.dcim.models import Device, DeviceFamily, DeviceRedundancyGroup, DeviceType, Location, Platform
22
22
  from nautobot.extras.choices import (
23
23
  LogLevelChoices,
24
24
  SecretsGroupAccessTypeChoices,
@@ -274,6 +274,7 @@ def update_git_config_contexts(repository_record, job_result):
274
274
  for filter_type in (
275
275
  "locations",
276
276
  "device_types",
277
+ "device_families",
277
278
  "roles",
278
279
  "platforms",
279
280
  "cluster_groups",
@@ -408,6 +409,7 @@ def import_config_context(context_data, repository_record, job_result):
408
409
  for key, model_class in [
409
410
  ("locations", Location),
410
411
  ("device_types", DeviceType),
412
+ ("device_families", DeviceFamily),
411
413
  ("roles", Role),
412
414
  ("platforms", Platform),
413
415
  ("cluster_groups", ClusterGroup),
@@ -21,7 +21,7 @@ from nautobot.extras.choices import (
21
21
  CustomFieldTypeChoices,
22
22
  RelationshipSideChoices,
23
23
  )
24
- from nautobot.extras.filters.customfields import (
24
+ from nautobot.extras.filter_mixins_customfields import (
25
25
  CustomFieldBooleanFilter,
26
26
  CustomFieldCharFilter,
27
27
  CustomFieldDateFilter,
@@ -1,3 +1,6 @@
1
+ from functools import reduce
2
+ import operator
3
+
1
4
  from django.db.models import Q
2
5
  from django.forms import IntegerField
3
6
  import django_filters
@@ -36,19 +39,52 @@ class CustomFieldFilterMixin:
36
39
  super().__init__(*args, **kwargs)
37
40
  self.field_name = f"_custom_field_data__{self.field_name}"
38
41
 
42
+ def generate_query(self, value):
43
+ # This method may be called from extras.models.DynamicGroup._generate_query_for_filter method
44
+ # to generate proper query for given field. But at this point, we don't know if the field will be negated or not
45
+ # That's why we're preparing query that works both: for positional filtering and negated one.
46
+ # For positional filtering, when we're expecting some value, the field must exists (key in custom field data JSONB)
47
+ # and value can't be None (null in db)
48
+ # For negated filtering we're expecting field without some value, but key may be missing or value can be None (null in db)
49
+ # Please refer to the filter method below, for more context.
50
+ if value == "null" or value == ["null"]:
51
+ return Q(**{f"{self.field_name}__exact": None}) & Q(**{f"{self.field_name}__isnull": False})
52
+
53
+ if isinstance(value, (list, tuple)):
54
+ value_match = reduce(operator.or_, [Q(**{f"{self.field_name}__{self.lookup_expr}": v}) for v in value])
55
+ else:
56
+ value_match = Q(**{f"{self.field_name}__{self.lookup_expr}": value})
57
+ value_is_not_none = ~Q(**{f"{self.field_name}__exact": None})
58
+ key_is_present_in_jsonb = Q(
59
+ **{f"{self.field_name}__isnull": False}
60
+ ) # __isnull and __has_key has same output in case of JSONB fields
61
+
62
+ return value_match & value_is_not_none & key_is_present_in_jsonb
63
+
39
64
  def filter(self, qs, value):
40
65
  if value in django_filters.constants.EMPTY_VALUES:
41
66
  return qs
42
67
 
43
- if value == "null":
68
+ if value == "null" or value == ["null"]:
44
69
  return self.get_method(qs)(
45
- Q(**{f"{self.field_name}__exact": None}) | Q(**{f"{self.field_name}__isnull": True})
70
+ Q(**{f"{self.field_name}__exact": None}) & Q(**{f"{self.field_name}__isnull": False})
46
71
  )
47
72
 
48
73
  # Custom fields require special handling for exclude filtering.
49
- # Return custom fields that don't match the value and null custom fields
74
+ # Return custom fields that don't match the value, key is missing or value is set to null
75
+ # For JSONB fields, like `_custom_field_data`:
76
+ # __isnull and __has_key returns those records which has key
77
+ # __isnull is not checking the actual value in JSONB!
78
+ # to check for null value, we need to use exact
79
+ # With exclude filtering we need to take into account all cases:
80
+ # - no key - handled by __isnull check
81
+ # - key is present with null - handled by __exact=None
82
+ # - key is present with some value - handled by filter
83
+ # - key is present with empty str - handled by filter
50
84
  if self.exclude:
51
- qs_null_custom_fields = qs.filter(**{f"{self.field_name}__isnull": True}).distinct()
85
+ qs_null_custom_fields = qs.filter(
86
+ Q(**{f"{self.field_name}__isnull": True}) | Q(**{f"{self.field_name}__exact": None})
87
+ ).distinct()
52
88
  return super().filter(qs, value).distinct() | qs_null_custom_fields
53
89
 
54
90
  return super().filter(qs, value)
@@ -58,7 +94,7 @@ class CustomFieldBooleanFilter(CustomFieldFilterMixin, django_filters.BooleanFil
58
94
  """Custom field single value filter for backwards compatibility"""
59
95
 
60
96
 
61
- class CustomFieldCharFilter(CustomFieldFilterMixin, django_filters.Filter):
97
+ class CustomFieldCharFilter(CustomFieldFilterMixin, django_filters.CharFilter):
62
98
  """Custom field single value filter for backwards compatibility"""
63
99
 
64
100
 
@@ -92,7 +128,7 @@ class CustomFieldMultiSelectFilter(CustomFieldSelectFilter):
92
128
  super().__init__(*args, **kwargs)
93
129
 
94
130
 
95
- class CustomFieldNumberFilter(CustomFieldFilterMixin, django_filters.Filter):
131
+ class CustomFieldNumberFilter(CustomFieldFilterMixin, django_filters.NumberFilter):
96
132
  """Custom field single value filter for backwards compatibility"""
97
133
 
98
134
  field_class = IntegerField
@@ -23,9 +23,9 @@ from nautobot.core.filters import (
23
23
  RelatedMembershipBooleanFilter,
24
24
  SearchFilter,
25
25
  )
26
- from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
27
- from nautobot.dcim.models import DeviceRedundancyGroup, DeviceType, Location, Platform
26
+ from nautobot.dcim.models import DeviceFamily, DeviceRedundancyGroup, DeviceType, Location, Platform
28
27
  from nautobot.extras.choices import (
28
+ ApprovalWorkflowStateChoices,
29
29
  JobQueueTypeChoices,
30
30
  JobResultStatusChoices,
31
31
  MetadataTypeDataTypeChoices,
@@ -33,7 +33,18 @@ from nautobot.extras.choices import (
33
33
  SecretsGroupAccessTypeChoices,
34
34
  SecretsGroupSecretTypeChoices,
35
35
  )
36
- from nautobot.extras.filters.customfields import (
36
+ from nautobot.extras.filter_mixins import (
37
+ ConfigContextRoleFilter,
38
+ CreatedUpdatedModelFilterSetMixin,
39
+ CustomFieldModelFilterSetMixin,
40
+ LocalContextModelFilterSetMixin,
41
+ RelationshipFilter,
42
+ RelationshipModelFilterSetMixin,
43
+ RoleModelFilterSetMixin,
44
+ StatusFilter,
45
+ StatusModelFilterSetMixin,
46
+ )
47
+ from nautobot.extras.filter_mixins_customfields import (
37
48
  CustomFieldBooleanFilter,
38
49
  CustomFieldCharFilter,
39
50
  CustomFieldDateFilter,
@@ -45,17 +56,6 @@ from nautobot.extras.filters.customfields import (
45
56
  CustomFieldMultiValueNumberFilter,
46
57
  CustomFieldNumberFilter,
47
58
  )
48
- from nautobot.extras.filters.mixins import (
49
- ConfigContextRoleFilter,
50
- CreatedUpdatedModelFilterSetMixin,
51
- CustomFieldModelFilterSetMixin,
52
- LocalContextModelFilterSetMixin,
53
- RelationshipFilter,
54
- RelationshipModelFilterSetMixin,
55
- RoleModelFilterSetMixin,
56
- StatusFilter,
57
- StatusModelFilterSetMixin,
58
- )
59
59
  from nautobot.extras.models import (
60
60
  ApprovalWorkflow,
61
61
  ApprovalWorkflowDefinition,
@@ -126,14 +126,12 @@ __all__ = (
126
126
  "ContactFilterSet",
127
127
  "ContentTypeFilterSet",
128
128
  "ContentTypeMultipleChoiceFilter",
129
- "CreatedUpdatedFilterSet",
130
129
  "CreatedUpdatedModelFilterSetMixin",
131
130
  "CustomFieldBooleanFilter",
132
131
  "CustomFieldCharFilter",
133
132
  "CustomFieldDateFilter",
134
133
  "CustomFieldFilterMixin",
135
134
  "CustomFieldJSONFilter",
136
- "CustomFieldModelFilterSet",
137
135
  "CustomFieldModelFilterSetMixin",
138
136
  "CustomFieldMultiSelectFilter",
139
137
  "CustomFieldMultiValueCharFilter",
@@ -153,7 +151,6 @@ __all__ = (
153
151
  "JobQueueAssignmentFilterSet",
154
152
  "JobQueueFilterSet",
155
153
  "JobResultFilterSet",
156
- "LocalContextFilterSet",
157
154
  "LocalContextModelFilterSetMixin",
158
155
  "MetadataChoiceFilterSet",
159
156
  "MetadataTypeFilterSet",
@@ -178,17 +175,6 @@ __all__ = (
178
175
  )
179
176
 
180
177
 
181
- # TODO: remove in 2.2
182
- @class_deprecated_in_favor_of(CreatedUpdatedModelFilterSetMixin)
183
- class CreatedUpdatedFilterSet(CreatedUpdatedModelFilterSetMixin):
184
- pass
185
-
186
-
187
- @class_deprecated_in_favor_of(RelationshipModelFilterSetMixin)
188
- class RelationshipModelFilterSet(RelationshipModelFilterSetMixin):
189
- pass
190
-
191
-
192
178
  #
193
179
  # Approval Workflows
194
180
  #
@@ -199,7 +185,7 @@ class ApprovalWorkflowStageDefinitionFilterSet(BaseFilterSet):
199
185
 
200
186
  q = SearchFilter(
201
187
  filter_predicates={
202
- "weight": {
188
+ "sequence": {
203
189
  "lookup_expr": "exact",
204
190
  "preprocessor": int,
205
191
  },
@@ -401,6 +387,12 @@ class ConfigContextFilterSet(BaseFilterSet):
401
387
  to_field_name="model",
402
388
  label="Device Type (model or ID)",
403
389
  )
390
+ device_family = NaturalKeyOrPKMultipleChoiceFilter(
391
+ field_name="device_families",
392
+ queryset=DeviceFamily.objects.all(),
393
+ to_field_name="name",
394
+ label="Device Family (name or ID)",
395
+ )
404
396
  platform_id = ModelMultipleChoiceFilter(
405
397
  field_name="platforms",
406
398
  queryset=Platform.objects.all(),
@@ -572,12 +564,6 @@ class ContentTypeFilterSet(BaseFilterSet):
572
564
  return queryset.filter(FeatureQuery(value).get_query())
573
565
 
574
566
 
575
- # TODO: remove in 2.2
576
- @class_deprecated_in_favor_of(CustomFieldModelFilterSetMixin)
577
- class CustomFieldModelFilterSet(CustomFieldModelFilterSetMixin):
578
- pass
579
-
580
-
581
567
  class CustomFieldFilterSet(BaseFilterSet):
582
568
  q = SearchFilter(
583
569
  filter_predicates={
@@ -787,7 +773,7 @@ class CustomLinkFilterSet(BaseFilterSet):
787
773
  #
788
774
 
789
775
  # Must be imported **after* NautobotFilterSet class is defined to avoid a circular import loop.
790
- from nautobot.tenancy.filters.mixins import TenancyModelFilterSetMixin # noqa: E402
776
+ from nautobot.tenancy.filter_mixins import TenancyModelFilterSetMixin # noqa: E402
791
777
 
792
778
 
793
779
  class DynamicGroupFilterSet(TenancyModelFilterSetMixin, NautobotFilterSet):
@@ -1229,10 +1215,15 @@ class ScheduledJobFilterSet(BaseFilterSet):
1229
1215
  label="Time zone",
1230
1216
  null_value="",
1231
1217
  )
1218
+ approval_state = django_filters.MultipleChoiceFilter(
1219
+ field_name="associated_approval_workflows__current_state",
1220
+ label="Approval state",
1221
+ choices=ApprovalWorkflowStateChoices,
1222
+ )
1232
1223
 
1233
1224
  class Meta:
1234
1225
  model = ScheduledJob
1235
- fields = ["id", "name", "total_run_count", "start_time", "last_run_at", "time_zone"]
1226
+ fields = ["id", "name", "enabled", "total_run_count", "start_time", "last_run_at", "time_zone"]
1236
1227
 
1237
1228
 
1238
1229
  #
@@ -1270,17 +1261,6 @@ class JobButtonFilterSet(BaseFilterSet):
1270
1261
  )
1271
1262
 
1272
1263
 
1273
- #
1274
- # Filter for Local Config Context Data
1275
- #
1276
-
1277
-
1278
- # TODO: remove in 2.2
1279
- @class_deprecated_in_favor_of(LocalContextModelFilterSetMixin)
1280
- class LocalContextFilterSet(LocalContextModelFilterSetMixin):
1281
- pass
1282
-
1283
-
1284
1264
  #
1285
1265
  # Metadata
1286
1266
  #
@@ -1503,6 +1483,11 @@ class SecretFilterSet(
1503
1483
  "name": "icontains",
1504
1484
  },
1505
1485
  )
1486
+ secrets_groups = NaturalKeyOrPKMultipleChoiceFilter(
1487
+ queryset=SecretsGroup.objects.all(),
1488
+ label="Groups (ID or name)",
1489
+ to_field_name="name",
1490
+ )
1506
1491
  # TODO(Glenn): dynamic choices needed. The issue being that secrets providers are Python
1507
1492
  # classes, not database models.
1508
1493
  # provider = django_filters.MultipleChoiceFilter(choices=..., null_value=None)
@@ -42,8 +42,7 @@ from nautobot.core.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
42
42
  from nautobot.core.forms.fields import MultiValueCharField
43
43
  from nautobot.core.forms.forms import ConfirmationForm
44
44
  from nautobot.core.forms.widgets import ClearableFileInput
45
- from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
46
- from nautobot.dcim.models import Device, DeviceRedundancyGroup, DeviceType, Location, Platform
45
+ from nautobot.dcim.models import Device, DeviceFamily, DeviceRedundancyGroup, DeviceType, Location, Platform
47
46
  from nautobot.extras.choices import (
48
47
  ApprovalWorkflowStateChoices,
49
48
  ButtonClassChoices,
@@ -153,7 +152,6 @@ __all__ = (
153
152
  "ConfigContextSchemaBulkEditForm",
154
153
  "ConfigContextSchemaFilterForm",
155
154
  "ConfigContextSchemaForm",
156
- "CustomFieldBulkCreateForm", # 2.0 TODO remove this deprecated class
157
155
  "CustomFieldBulkDeleteForm",
158
156
  "CustomFieldBulkEditForm",
159
157
  "CustomFieldChoiceFormSet",
@@ -338,7 +336,7 @@ ApprovalWorkflowStageDefinitionFormSet = inlineformset_factory(
338
336
  extra=5,
339
337
  widgets={
340
338
  "name": forms.TextInput(attrs={"class": "form-control"}),
341
- "weight": forms.NumberInput(attrs={"class": "form-control"}),
339
+ "sequence": forms.NumberInput(attrs={"class": "form-control"}),
342
340
  "min_approvers": forms.NumberInput(attrs={"class": "form-control"}),
343
341
  "denial_message": forms.TextInput(attrs={"class": "form-control"}),
344
342
  "approver_group": forms.Select(attrs={"class": "form-control"}),
@@ -352,8 +350,8 @@ class ApprovalWorkflowStageDefinitionBulkEditForm(TagsBulkEditFormMixin, Nautobo
352
350
  pk = forms.ModelMultipleChoiceField(
353
351
  queryset=ApprovalWorkflowStageDefinition.objects.all(), widget=forms.MultipleHiddenInput
354
352
  )
355
- weight = forms.IntegerField(required=False, label="Weight")
356
- min_approvers = forms.IntegerField(required=False, label="Min Approvers")
353
+ sequence = forms.IntegerField(required=False, label="Sequence")
354
+ min_approvers = forms.IntegerField(required=False, label="Minimum Approvers")
357
355
  denial_message = forms.CharField(required=False, label="Denial Message")
358
356
 
359
357
  class Meta:
@@ -374,8 +372,8 @@ class ApprovalWorkflowStageDefinitionFilterForm(NautobotFilterForm):
374
372
  required=False,
375
373
  label="Approval Workflow Definition",
376
374
  )
377
- weight = forms.IntegerField(required=False, label="Weight")
378
- min_approvers = forms.IntegerField(required=False, label="Min Approvers")
375
+ sequence = forms.IntegerField(required=False, label="Sequence")
376
+ min_approvers = forms.IntegerField(required=False, label="Minimum Approvers")
379
377
  approver_group = DynamicModelChoiceField(
380
378
  queryset=Group.objects.all(),
381
379
  required=False,
@@ -546,6 +544,7 @@ class ConfigContextForm(BootstrapMixin, NoteModelFormMixin, forms.ModelForm):
546
544
  required=False,
547
545
  )
548
546
  device_types = DynamicModelMultipleChoiceField(queryset=DeviceType.objects.all(), required=False)
547
+ device_families = DynamicModelMultipleChoiceField(queryset=DeviceFamily.objects.all(), required=False)
549
548
  platforms = DynamicModelMultipleChoiceField(queryset=Platform.objects.all(), required=False)
550
549
  cluster_groups = DynamicModelMultipleChoiceField(queryset=ClusterGroup.objects.all(), required=False)
551
550
  clusters = DynamicModelMultipleChoiceField(queryset=Cluster.objects.all(), required=False)
@@ -578,6 +577,7 @@ class ConfigContextForm(BootstrapMixin, NoteModelFormMixin, forms.ModelForm):
578
577
  "locations",
579
578
  "roles",
580
579
  "device_types",
580
+ "device_families",
581
581
  "platforms",
582
582
  "cluster_groups",
583
583
  "clusters",
@@ -611,7 +611,12 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
611
611
  role = DynamicModelMultipleChoiceField(
612
612
  queryset=Role.objects.get_for_models([Device, VirtualMachine]), to_field_name="name", required=False
613
613
  )
614
- type = DynamicModelMultipleChoiceField(queryset=DeviceType.objects.all(), to_field_name="model", required=False)
614
+ device_type = DynamicModelMultipleChoiceField(
615
+ queryset=DeviceType.objects.all(), to_field_name="model", required=False
616
+ )
617
+ device_family = DynamicModelMultipleChoiceField(
618
+ queryset=DeviceFamily.objects.all(), to_field_name="name", required=False
619
+ )
615
620
  platform = DynamicModelMultipleChoiceField(queryset=Platform.objects.all(), to_field_name="name", required=False)
616
621
  cluster_group = DynamicModelMultipleChoiceField(
617
622
  queryset=ClusterGroup.objects.all(), to_field_name="name", required=False
@@ -827,12 +832,6 @@ class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelFormMixin):
827
832
  self.custom_fields.append(field_name)
828
833
 
829
834
 
830
- # 2.0 TODO: remove this class
831
- @class_deprecated_in_favor_of(CustomFieldModelBulkEditFormMixin)
832
- class CustomFieldBulkCreateForm(CustomFieldModelBulkEditFormMixin):
833
- """No longer needed as a separate class - use CustomFieldModelBulkEditFormMixin instead."""
834
-
835
-
836
835
  class CustomFieldBulkDeleteForm(ConfirmationForm):
837
836
  def __init__(self, *args, delete_all=False, **kwargs):
838
837
  super().__init__(*args, **kwargs)
@@ -11,7 +11,6 @@ from nautobot.core.forms import (
11
11
  DynamicModelChoiceField,
12
12
  DynamicModelMultipleChoiceField,
13
13
  )
14
- from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
15
14
  from nautobot.extras.choices import (
16
15
  DynamicGroupTypeChoices,
17
16
  RelationshipSideChoices,
@@ -48,17 +47,10 @@ __all__ = ( # noqa:RUF022
48
47
  "StatusModelBulkEditFormMixin",
49
48
  "StatusModelFilterFormMixin",
50
49
  "TagsBulkEditFormMixin",
51
- # 2.0 TODO: remove the below deprecated aliases
52
- "AddRemoveTagsForm",
53
- "CustomFieldBulkEditForm",
54
- "CustomFieldModelForm",
55
- "RelationshipModelForm",
56
50
  "RoleModelBulkEditFormMixin",
57
51
  "RoleModelFilterFormMixin",
58
52
  "RoleNotRequiredModelFormMixin",
59
53
  "RoleRequiredModelFormMixin",
60
- "StatusBulkEditFormMixin",
61
- "StatusFilterFormMixin",
62
54
  )
63
55
 
64
56
 
@@ -854,36 +846,3 @@ class TagsBulkEditFormMixin(BulkEditForm):
854
846
  query_params={"content_types": self.model._meta.label_lower},
855
847
  required=False,
856
848
  )
857
-
858
-
859
- # 2.2 TODO: Names below are only for backward compatibility with Nautobot 1.3 and earlier. Remove in 2.2
860
-
861
-
862
- @class_deprecated_in_favor_of(TagsBulkEditFormMixin)
863
- class AddRemoveTagsForm(TagsBulkEditFormMixin):
864
- pass
865
-
866
-
867
- @class_deprecated_in_favor_of(CustomFieldModelBulkEditFormMixin)
868
- class CustomFieldBulkEditForm(CustomFieldModelBulkEditFormMixin):
869
- pass
870
-
871
-
872
- @class_deprecated_in_favor_of(CustomFieldModelFormMixin)
873
- class CustomFieldModelForm(CustomFieldModelFormMixin):
874
- pass
875
-
876
-
877
- @class_deprecated_in_favor_of(RelationshipModelFormMixin)
878
- class RelationshipModelForm(RelationshipModelFormMixin):
879
- pass
880
-
881
-
882
- @class_deprecated_in_favor_of(StatusModelBulkEditFormMixin)
883
- class StatusBulkEditFormMixin(StatusModelBulkEditFormMixin):
884
- pass
885
-
886
-
887
- @class_deprecated_in_favor_of(StatusModelFilterFormMixin)
888
- class StatusFilterFormMixin(StatusModelFilterFormMixin):
889
- pass
nautobot/extras/jobs.py CHANGED
@@ -468,6 +468,7 @@ class BaseJob:
468
468
  label="Profile job execution",
469
469
  help_text="Profiles the job execution using cProfile and outputs a report to /tmp/",
470
470
  )
471
+ form.fields["_profile"].widget.attrs["class"] = "form-check-input"
471
472
  # If the class already exists there may be overrides, so we have to check this.
472
473
  try:
473
474
  job_model = JobModel.objects.get(module_name=cls.__module__, job_class_name=cls.__name__)
@@ -484,6 +485,7 @@ class BaseJob:
484
485
  label="Ignore singleton lock",
485
486
  help_text="Allow this singleton job to run even when another instance is already running",
486
487
  )
488
+ form.fields["_ignore_singleton_lock"].widget.attrs["class"] = "form-check-input"
487
489
 
488
490
  if job_model is not None:
489
491
  job_queue_queryset = JobQueue.objects.filter(jobs=job_model)
@@ -165,10 +165,11 @@ class JobKeyValueOverrideValueTablePanel(KeyValueTablePanel):
165
165
  else:
166
166
  value_tag = format_html(
167
167
  """
168
- <span class="hover_copy">
168
+ <span>
169
169
  <span id="{unique_id}_value_{key}">{value}</span>
170
- <button class="btn btn-inline btn-default hover_copy_button" data-clipboard-target="#{unique_id}_value_{key}">
171
- <span class="mdi mdi-content-copy"></span>
170
+ <button class="btn btn-secondary nb-btn-inline-hover" data-clipboard-target="#{unique_id}_value_{key}">
171
+ <span aria-hidden="true" class="mdi mdi-content-copy"></span>
172
+ <span class="visually-hidden">Copy</span>
172
173
  </button>
173
174
  </span>
174
175
  """,