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
@@ -22,7 +22,7 @@ from nautobot.core.celery import app, register_jobs
22
22
  from nautobot.core.exceptions import AbortTransaction
23
23
  from nautobot.core.jobs.bulk_actions import BulkDeleteObjects, BulkEditObjects
24
24
  from nautobot.core.jobs.cleanup import LogsCleanup
25
- from nautobot.core.jobs.groups import RefreshDynamicGroupCaches
25
+ from nautobot.core.jobs.groups import RefreshDynamicGroupCacheJobButtonReceiver, RefreshDynamicGroupCaches
26
26
  from nautobot.core.utils.lookup import get_filterset_for_model
27
27
  from nautobot.core.utils.requests import get_filterable_params_from_filter_params
28
28
  from nautobot.data_validation import models
@@ -43,6 +43,7 @@ from nautobot.extras.jobs import (
43
43
  FileVar,
44
44
  Job,
45
45
  MultiChoiceVar,
46
+ MultiObjectVar,
46
47
  ObjectVar,
47
48
  RunJobTaskFailed,
48
49
  StringVar,
@@ -440,8 +441,10 @@ def clean_compliance_rules_results_for_instance(instance, excluded_pks):
440
441
  class RunRegisteredDataComplianceRules(Job):
441
442
  """Run the validate function on all registered DataComplianceRule classes and, optionally, the built-in data validation rules."""
442
443
 
443
- name = "Run Registered Data Compliance Rules"
444
- description = "Runs selected Data Compliance rule classes."
444
+ class Meta:
445
+ name = "Run Registered Data Compliance Rules"
446
+ description = "Runs selected Data Compliance rule classes."
447
+ has_sensitive_variables = False
445
448
 
446
449
  selected_data_compliance_rules = MultiChoiceVar(
447
450
  choices=get_data_compliance_choices,
@@ -527,6 +530,60 @@ class RunRegisteredDataComplianceRules(Job):
527
530
  clean_compliance_rules_results_for_instance(instance=validated_object, excluded_pks=[result.pk])
528
531
 
529
532
 
533
+ class ValidateModelData(Job):
534
+ """Clean and validate data in all records of a given content type(s)."""
535
+
536
+ class Meta:
537
+ name = "Validate Model Data"
538
+ description = "Run `full_clean()` against all records of a given type(s) to check for data validity."
539
+ has_sensitive_variables = False
540
+ read_only = True
541
+ # Validating large amounts of data may take substantial processing time
542
+ soft_time_limit = 1800
543
+ time_limit = 2000
544
+
545
+ content_types = MultiObjectVar(
546
+ model=ContentType,
547
+ description="Type(s) of objects to validate.",
548
+ label="Content Types",
549
+ query_params={"can_view": True},
550
+ required=True,
551
+ )
552
+ verbose = BooleanVar(default=False, label="Verbose output?")
553
+
554
+ def run(self, *, content_types, verbose=False): # pylint:disable=arguments-differ
555
+ for content_type in content_types:
556
+ model = content_type.model_class()
557
+ if model is None:
558
+ self.fail(
559
+ "Couldn't locate Python model for content-type %s.%s",
560
+ content_type.app_label,
561
+ content_type.model,
562
+ )
563
+ continue
564
+
565
+ try:
566
+ records = model.objects.restrict(self.user, "view")
567
+ except AttributeError: # Not a RestrictedQuerySet?
568
+ if self.user.is_superuser: # i.e., permissions exempt
569
+ records = model.objects.all()
570
+ else:
571
+ self.fail("Unable to apply access permissions to %s.%s", content_type.app_label, content_type.model)
572
+
573
+ if not records.exists():
574
+ self.logger.warning("No %s found", model._meta.verbose_name_plural)
575
+ continue
576
+
577
+ self.logger.info("Validating %d %s", records.count(), model._meta.verbose_name_plural)
578
+ for record in records.iterator():
579
+ try:
580
+ record.full_clean()
581
+ if verbose:
582
+ self.logger.success("Validated successfully", extra={"object": record})
583
+ except ValidationError as err:
584
+ self.fail("Validation error: `%s`", err, extra={"object": record})
585
+
586
+
530
587
  jobs = [
531
588
  BulkDeleteObjects,
532
589
  BulkEditObjects,
@@ -536,6 +593,8 @@ jobs = [
536
593
  ImportObjects,
537
594
  LogsCleanup,
538
595
  RefreshDynamicGroupCaches,
596
+ RefreshDynamicGroupCacheJobButtonReceiver,
539
597
  RunRegisteredDataComplianceRules,
598
+ ValidateModelData,
540
599
  ]
541
600
  register_jobs(*jobs)
@@ -1,5 +1,5 @@
1
1
  from nautobot.extras.choices import DynamicGroupTypeChoices
2
- from nautobot.extras.jobs import Job, ObjectVar
2
+ from nautobot.extras.jobs import Job, JobButtonReceiver, ObjectVar
3
3
  from nautobot.extras.models import DynamicGroup
4
4
 
5
5
  name = "System Jobs"
@@ -31,8 +31,38 @@ class RefreshDynamicGroupCaches(Job):
31
31
  if single_group is not None:
32
32
  groups = groups.filter(pk=single_group.pk)
33
33
 
34
+ if not groups.exists():
35
+ self.logger.info("No relevant dynamic groups were specified, nothing to do.")
36
+ return
37
+
38
+ self.logger.info("Re-calculating and re-caching group members. This may take some time.")
34
39
  for group in groups:
35
40
  group.update_cached_members()
36
41
  self.logger.info("Cache refreshed successfully, now with %d members", group.count, extra={"object": group})
37
42
 
38
43
  self.logger.info("Cache(s) refreshed")
44
+
45
+
46
+ class RefreshDynamicGroupCacheJobButtonReceiver(JobButtonReceiver):
47
+ """
48
+ System Job Button Receiver to re-calculate and re-cache the members of a given Dynamic Group.
49
+ """
50
+
51
+ class Meta:
52
+ name = "Refresh Dynamic Group Cache (Job Button Receiver)"
53
+ description = "Re-calculate and re-cache the membership list of a given Dynamic Group."
54
+
55
+ def receive_job_button(self, obj):
56
+ if not isinstance(obj, DynamicGroup):
57
+ self.fail("This job button should only be used with Dynamic Group records.")
58
+ elif obj.group_type == DynamicGroupTypeChoices.TYPE_STATIC:
59
+ self.fail(
60
+ "The members of this Dynamic Group are statically defined and do not need to be recalculated.",
61
+ extra={"object": obj},
62
+ )
63
+ else:
64
+ self.logger.info(
65
+ "Re-calculating and re-caching group members. This may take some time.", extra={"object": obj}
66
+ )
67
+ obj.update_cached_members()
68
+ self.logger.success("Cache refreshed successfully, now with %d members", obj.count, extra={"object": obj})
@@ -133,6 +133,14 @@ class Command(BaseCommand):
133
133
  )
134
134
  from nautobot.tenancy.factory import TenantFactory, TenantGroupFactory
135
135
  from nautobot.users.factory import UserFactory
136
+ from nautobot.vpn.factory import (
137
+ VPNFactory,
138
+ VPNPhase1PolicyFactory,
139
+ VPNPhase2PolicyFactory,
140
+ VPNProfileFactory,
141
+ VPNTunnelEndpointFactory,
142
+ VPNTunnelFactory,
143
+ )
136
144
  from nautobot.wireless.factory import (
137
145
  ControllerManagedDeviceGroupWithMembersFactory,
138
146
  RadioProfileFactory,
@@ -171,14 +179,12 @@ class Command(BaseCommand):
171
179
  )
172
180
  # ...and some tags that apply to a random subset of content-types
173
181
  _create_batch(TagFactory, 15, description="on some content-types")
174
- _create_batch(UserFactory, 5)
182
+ _create_batch(UserFactory, 10)
175
183
  _create_batch(SavedViewFactory, 20)
176
184
  _create_batch(ContactFactory, 20)
177
185
  _create_batch(TeamFactory, 20)
178
- _create_batch(TenantGroupFactory, 10, description="without parents", has_parent=False)
179
- _create_batch(TenantGroupFactory, 10, description="with parents", has_parent=True)
180
- _create_batch(TenantFactory, 10, description="without a parent group", has_tenant_group=False)
181
- _create_batch(TenantFactory, 10, description="with a parent group", has_tenant_group=True)
186
+ _create_batch(TenantGroupFactory, 30)
187
+ _create_batch(TenantFactory, 30)
182
188
  _create_batch(LocationTypeFactory, 7) # only 7 unique LocationTypes are hard-coded presently
183
189
  # First 7 locations must be created in specific order so subsequent objects have valid parents to reference
184
190
  _create_batch(LocationFactory, 7, description="as structure", has_parent=True)
@@ -192,7 +198,7 @@ class Command(BaseCommand):
192
198
  _create_batch(VRFFactory, 20)
193
199
  _create_batch(VLANGroupFactory, 20)
194
200
  _create_batch(VLANFactory, 20)
195
- for i in range(30):
201
+ for i in range(50):
196
202
  _create_batch(
197
203
  PrefixFactory,
198
204
  1,
@@ -207,7 +213,15 @@ class Command(BaseCommand):
207
213
  prefix=f"2001:db8:0:{i}::/64",
208
214
  type=PrefixTypeChoices.TYPE_CONTAINER,
209
215
  )
210
- _create_batch(NamespaceFactory, 5, description="without any Prefixes or IPAddresses")
216
+ _create_batch(
217
+ NamespaceFactory, 5, description="with a Tenant and without any Prefixes or IPAddresses", has_tenant=True
218
+ )
219
+ _create_batch(
220
+ NamespaceFactory,
221
+ 5,
222
+ description="without a Tenant and without any Prefixes or IPAddresses",
223
+ has_tenant=False,
224
+ )
211
225
  _create_batch(DeviceFamilyFactory, 20)
212
226
  _create_batch(ManufacturerFactory, 8) # First 8 hard-coded Manufacturers
213
227
  _create_batch(PlatformFactory, 20, description="with Manufacturers", has_manufacturer=True)
@@ -222,7 +236,7 @@ class Command(BaseCommand):
222
236
  _create_batch(ConsoleServerPortTemplateFactory, 30)
223
237
  _create_batch(RearPortTemplateFactory, 30)
224
238
  _create_batch(FrontPortTemplateFactory, 30)
225
- _create_batch(InterfaceTemplateFactory, 30)
239
+ _create_batch(InterfaceTemplateFactory, 50)
226
240
  _create_batch(PowerPortTemplateFactory, 30)
227
241
  _create_batch(PowerOutletTemplateFactory, 30)
228
242
  _create_batch(ModuleBayTemplateFactory, 60, description="without module families", has_module_family=False)
@@ -324,11 +338,16 @@ class Command(BaseCommand):
324
338
  _create_batch(WirelessNetworksWithMembersFactory, 5, description="with members")
325
339
  # make sure we have some supported data rates that have null relationships to make filter tests happy
326
340
  _create_batch(SupportedDataRateFactory, 10, description="without any associated objects")
341
+ _create_batch(VPNPhase1PolicyFactory, 20)
342
+ _create_batch(VPNPhase2PolicyFactory, 20)
343
+ _create_batch(VPNProfileFactory, 30)
344
+ _create_batch(VPNFactory, 10)
345
+ _create_batch(VPNTunnelEndpointFactory, 20)
346
+ _create_batch(VPNTunnelFactory, 10)
327
347
  _create_batch(JobQueueFactory, 10)
328
348
  # make sure we have some tenants that have null relationships to make filter tests happy
329
349
  _create_batch(TenantFactory, 10, description="without any associated objects")
330
350
  # TODO: nautobot.tenancy.tests.test_filters currently calls the following additional factories:
331
- # _create_batch(UserFactory, 10)
332
351
  # _create_batch(RackFactory, 10)
333
352
  # _create_batch(RackReservationFactory, 10)
334
353
  # _create_batch(ClusterTypeFactory, 10)
@@ -102,6 +102,17 @@ class BaseModel(models.Model):
102
102
 
103
103
  raise AttributeError(f"Cannot find a URL for {self} ({self._meta.app_label}.{self._meta.model_name})")
104
104
 
105
+ @property
106
+ def page_title(self):
107
+ """
108
+ Property used by Title and Breadcrumbs to display link to the object or title at detail page.
109
+ """
110
+ if hasattr(self, "name"):
111
+ return self.name
112
+ if hasattr(self, "display"):
113
+ return self.display
114
+ return str(self)
115
+
105
116
  @property
106
117
  def present_in_database(self):
107
118
  """
@@ -4,13 +4,20 @@ from nautobot.core.models import BaseModel
4
4
  from nautobot.core.models.fields import TagsField
5
5
  from nautobot.extras.models.change_logging import ChangeLoggedModel
6
6
  from nautobot.extras.models.customfields import CustomFieldModel
7
- from nautobot.extras.models.mixins import ContactMixin, DynamicGroupsModelMixin, NotesMixin, SavedViewMixin
7
+ from nautobot.extras.models.mixins import (
8
+ ContactMixin,
9
+ DataComplianceModelMixin,
10
+ DynamicGroupsModelMixin,
11
+ NotesMixin,
12
+ SavedViewMixin,
13
+ )
8
14
  from nautobot.extras.models.relationships import RelationshipModel
9
15
 
10
16
  logger = logging.getLogger(__name__)
11
17
 
12
18
 
13
19
  class OrganizationalModel(
20
+ DataComplianceModelMixin,
14
21
  ChangeLoggedModel,
15
22
  ContactMixin,
16
23
  CustomFieldModel,
@@ -35,6 +42,7 @@ class OrganizationalModel(
35
42
 
36
43
 
37
44
  class PrimaryModel(
45
+ DataComplianceModelMixin,
38
46
  ChangeLoggedModel,
39
47
  ContactMixin,
40
48
  CustomFieldModel,
@@ -1,3 +1,5 @@
1
+ import uuid
2
+
1
3
  from django.core.cache import cache
2
4
  from django.db.models import Case, When
3
5
  from django.db.models.signals import post_delete, post_save
@@ -21,15 +23,18 @@ class TreeQuerySet(TreeQuerySet_, querysets.RestrictedQuerySet):
21
23
  Dynamically computes ancestors either through the tree or through the `parent` foreign key depending on whether
22
24
  tree fields are present on `of`.
23
25
  """
26
+
27
+ # If `of` is a UUID, i.e. pk, retrieve the corresponding model instance with tree fields disabled.
28
+ if isinstance(of, uuid.UUID):
29
+ of = self.model.objects.without_tree_fields().get(pk=of)
30
+
24
31
  # If `of` has `tree_depth` defined, i.e. if it was retrieved from the database on a queryset where tree fields
25
32
  # were enabled (see `TreeQuerySet.with_tree_fields` and `TreeQuerySet.without_tree_fields`), use the default
26
33
  # implementation from `tree_queries.query.TreeQuerySet`.
27
- # Furthermore, if `of` doesn't have a parent field we also have to defer to the tree-based implementation which
28
- # will then annotate the tree fields and proceed as usual.
29
- if hasattr(of, "tree_depth") or not hasattr(of, "parent"):
34
+ if hasattr(of, "tree_depth"):
30
35
  return super().ancestors(of, include_self=include_self)
36
+
31
37
  # In the other case, traverse the `parent` foreign key until the root.
32
- model_class = of._meta.concrete_model
33
38
  ancestor_pks = []
34
39
  if include_self:
35
40
  ancestor_pks.append(of.pk)
@@ -40,7 +45,7 @@ class TreeQuerySet(TreeQuerySet_, querysets.RestrictedQuerySet):
40
45
  # Reference:
41
46
  # https://stackoverflow.com/questions/4916851/django-get-a-queryset-from-array-of-ids-in-specific-order
42
47
  preserve_order = Case(*[When(pk=pk, then=position) for position, pk in enumerate(ancestor_pks)])
43
- return model_class.objects.without_tree_fields().filter(pk__in=ancestor_pks).order_by(preserve_order)
48
+ return self.model.objects.without_tree_fields().filter(pk__in=ancestor_pks).order_by(preserve_order)
44
49
 
45
50
  def max_tree_depth(self):
46
51
  r"""
@@ -147,7 +147,7 @@ def serialize_object_v2(obj):
147
147
  # Try serializing obj(model instance) using its API Serializer
148
148
  try:
149
149
  serializer_class = get_serializer_for_model(obj.__class__)
150
- data = serializer_class(obj, context={"request": None, "depth": 1}).data
150
+ data = serializer_class(obj, context={"request": None, "depth": 1, "exclude_m2m": False}).data
151
151
  except SerializerNotFound:
152
152
  # Fall back to generic JSON representation of obj
153
153
  data = serialize_object(obj)
nautobot/core/settings.py CHANGED
@@ -95,11 +95,12 @@ if "NAUTOBOT_DEPLOYMENT_ID" in os.environ and os.environ["NAUTOBOT_DEPLOYMENT_ID
95
95
  DEPLOYMENT_ID = os.environ["NAUTOBOT_DEPLOYMENT_ID"]
96
96
 
97
97
  # Device names are not guaranteed globally-unique by Nautobot but in practice they often are.
98
- # Set this to True to use the device name alone as the natural key for Device objects.
99
- # Set this to False to use the sequence (name, tenant, location) as the natural key instead.
100
- #
101
- if "NAUTOBOT_DEVICE_NAME_AS_NATURAL_KEY" in os.environ and os.environ["NAUTOBOT_DEVICE_NAME_AS_NATURAL_KEY"] != "":
102
- DEVICE_NAME_AS_NATURAL_KEY = is_truthy(os.environ["NAUTOBOT_DEVICE_NAME_AS_NATURAL_KEY"])
98
+ # Select how Devices are uniquely identified:
99
+ # - 'location_tenant_name': combination of Location + Tenant + Name
100
+ # - 'name': Device name must be globally unique
101
+ # - 'none': No enforced uniqueness (rely on other validation rules or custom validators)
102
+ if "NAUTOBOT_DEVICE_UNIQUENESS" in os.environ and os.environ["NAUTOBOT_DEVICE_UNIQUENESS"] != "":
103
+ DEVICE_UNIQUENESS = os.environ["NAUTOBOT_DEVICE_UNIQUENESS"]
103
104
 
104
105
  # Event Brokers
105
106
  EVENT_BROKERS = {}
@@ -412,10 +413,12 @@ SPECTACULAR_SETTINGS = {
412
413
  # enum naming encountered a non-optimally resolvable collision for fields named "protocol".
413
414
  "InterfaceRedundancyGroupProtocolChoices": "nautobot.dcim.choices.InterfaceRedundancyGroupProtocolChoices",
414
415
  "ServiceProtocolChoices": "nautobot.ipam.choices.ServiceProtocolChoices",
416
+ "VirtualServerProtocolChoices": "nautobot.load_balancers.choices.ProtocolChoices",
415
417
  # These choice enums need to be overridden because they get assigned to the `mode` field and
416
418
  # result in this error:
417
419
  # enum naming encountered a non-optimally resolvable collision for fields named "mode".
418
420
  "InterfaceModeChoices": "nautobot.dcim.choices.InterfaceModeChoices",
421
+ "VPNPhase2PolicyChoices": "nautobot.vpn.choices.DhGroupChoices",
419
422
  "WirelessNetworkModeChoices": "nautobot.wireless.choices.WirelessNetworkModeChoices",
420
423
  },
421
424
  # Create separate schema components for PATCH requests (fields generally are not `required` on PATCH)
@@ -564,11 +567,13 @@ INSTALLED_APPS = [
564
567
  "nautobot.cloud",
565
568
  "nautobot.data_validation",
566
569
  "nautobot.dcim",
567
- "nautobot.ipam",
568
570
  "nautobot.extras",
571
+ "nautobot.ipam",
572
+ "nautobot.load_balancers",
569
573
  "nautobot.tenancy",
570
574
  "nautobot.users",
571
575
  "nautobot.virtualization",
576
+ "nautobot.vpn",
572
577
  "nautobot.wireless",
573
578
  "drf_spectacular",
574
579
  "drf_spectacular_sidecar",
@@ -759,12 +764,15 @@ CONSTANCE_CONFIG = {
759
764
  help_text="Number of days to retain object changelog history.\nSet this to 0 to retain changes indefinitely.",
760
765
  field_type=int,
761
766
  ),
762
- "DEVICE_NAME_AS_NATURAL_KEY": ConstanceConfigItem(
763
- default=False,
764
- help_text="Device names are not guaranteed globally-unique by Nautobot but in practice they often are. "
765
- "Set this to True to use the device name alone as the natural key for Device objects. "
766
- "Set this to False to use the sequence (name, tenant, location) as the natural key instead.",
767
- field_type=bool,
767
+ "DEVICE_UNIQUENESS": ConstanceConfigItem(
768
+ default="location_tenant_name",
769
+ help_text=(
770
+ "Select how Devices are uniquely identified:\n"
771
+ "- 'location_tenant_name': combination of Location + Tenant + Name\n"
772
+ "- 'name': Device name must be globally unique\n"
773
+ "- 'none': No enforced uniqueness (rely on other validation rules or custom validators)"
774
+ ),
775
+ field_type=str,
768
776
  ),
769
777
  "DEPLOYMENT_ID": ConstanceConfigItem(
770
778
  default="",
@@ -830,6 +838,11 @@ CONSTANCE_CONFIG = {
830
838
  help_text="Whether to prefer IPv4 primary addresses over IPv6 primary addresses for devices.",
831
839
  field_type=bool,
832
840
  ),
841
+ "RACK_DEFAULT_U_HEIGHT": ConstanceConfigItem(
842
+ default=42,
843
+ help_text="Default height in rack units (U) for newly created racks. Must be between 1 and 500.",
844
+ field_type=int,
845
+ ),
833
846
  "RACK_ELEVATION_DEFAULT_UNIT_HEIGHT": ConstanceConfigItem(
834
847
  default=22, help_text="Default height (in pixels) of a rack unit in a rack elevation diagram", field_type=int
835
848
  ),
@@ -869,10 +882,11 @@ CONSTANCE_CONFIG_FIELDSETS = {
869
882
  "Change Logging": ["CHANGELOG_RETENTION"],
870
883
  "Device Connectivity": ["NETWORK_DRIVERS", "PREFER_IPV4"],
871
884
  "Installation Metrics": ["DEPLOYMENT_ID"],
872
- "Natural Keys": ["DEVICE_NAME_AS_NATURAL_KEY", "LOCATION_NAME_AS_NATURAL_KEY"],
885
+ "Natural Keys": ["DEVICE_UNIQUENESS", "LOCATION_NAME_AS_NATURAL_KEY"],
873
886
  "Pagination": ["PAGINATE_COUNT", "MAX_PAGE_SIZE", "PER_PAGE_DEFAULTS"],
874
887
  "Performance": ["JOB_CREATE_FILE_MAX_SIZE"],
875
888
  "Rack Elevation Rendering": [
889
+ "RACK_DEFAULT_U_HEIGHT",
876
890
  "RACK_ELEVATION_DEFAULT_UNIT_HEIGHT",
877
891
  "RACK_ELEVATION_DEFAULT_UNIT_WIDTH",
878
892
  "RACK_ELEVATION_UNIT_TWO_DIGIT_FORMAT",
@@ -1035,12 +1049,8 @@ BRANDING_FILEPATHS = {
1035
1049
  "icon_mask": os.getenv(
1036
1050
  "NAUTOBOT_BRANDING_FILEPATHS_ICON_MASK", None
1037
1051
  ), # mono-chrome icon used for the mask-icon header
1038
- "header_bullet": os.getenv(
1039
- "NAUTOBOT_BRANDING_FILEPATHS_HEADER_BULLET", None
1040
- ), # bullet image used for various view headers
1041
- "nav_bullet": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_NAV_BULLET", None), # bullet image used for nav menu headers
1042
- "css": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_CSS", None), # Custom global CSS
1043
- "javascript": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_JAVASCRIPT", None), # Custom global JavaScript
1052
+ # "css": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_CSS", None), # Custom global CSS
1053
+ # "javascript": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_JAVASCRIPT", None), # Custom global JavaScript
1044
1054
  }
1045
1055
 
1046
1056
  # Title to use in place of "Nautobot"
@@ -1147,6 +1157,12 @@ def silk_request_logging_intercept_logic(request):
1147
1157
 
1148
1158
  SILKY_INTERCEPT_FUNC = silk_request_logging_intercept_logic
1149
1159
 
1160
+ #
1161
+ # django-tables2
1162
+ #
1163
+
1164
+ DJANGO_TABLES2_TEMPLATE = "utilities/obj_table.html"
1165
+
1150
1166
  #
1151
1167
  # Kubernetes settings variables
1152
1168
  #
@@ -162,22 +162,17 @@ properties:
162
162
  type: "string"
163
163
  BRANDING_FILEPATHS:
164
164
  default:
165
- css: null
166
165
  favicon: null
167
- header_bullet: null
168
166
  icon_16: null
169
167
  icon_32: null
170
168
  icon_180: null
171
169
  icon_192: null
172
170
  icon_mask: null
173
- javascript: null
174
171
  logo: null
175
- nav_bullet: null
176
172
  description: >-
177
173
  A set of filepaths relative to the [`MEDIA_ROOT`](#media_root) which locate assets used for
178
174
  custom branding of your Nautobot instance.
179
- With the exception of `css` and `javascript`, which provide the option to add an _additional_ file to Nautobot
180
- page content, each of the other assets takes the place of the corresponding stock Nautobot asset.
175
+ Each of the assets takes the place of the corresponding stock Nautobot asset.
181
176
  This allows for, for instance, providing your own navbar logo and favicon.
182
177
  If a custom asset is not provided for any of the above options, the stock Nautobot asset is used.
183
178
  details: |-
@@ -186,22 +181,15 @@ properties:
186
181
 
187
182
  +++ 2.2.4
188
183
  The `css` and `javascript` assets were added as options.
184
+
185
+ --- 3.0.0
186
+ The `header_bullet`, `nav_bullet`, `javascript`, and `css` assets were removed.
189
187
  properties:
190
- css:
191
- "$ref": "#/definitions/relative_path"
192
- default: null
193
- description: "Custom global CSS file"
194
- environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_CSS"
195
188
  favicon:
196
189
  "$ref": "#/definitions/relative_path"
197
190
  default: null
198
191
  description: "Browser favicon"
199
192
  environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_FAVICON"
200
- header_bullet:
201
- "$ref": "#/definitions/relative_path"
202
- default: null
203
- description: "Bullet image used for various view headers"
204
- environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_HEADER_BULLET"
205
193
  icon_16:
206
194
  "$ref": "#/definitions/relative_path"
207
195
  default: null
@@ -227,21 +215,11 @@ properties:
227
215
  default: null
228
216
  description: "Mono-chrome icon used for the mask-icon header"
229
217
  environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_ICON_MASK"
230
- javascript:
231
- "$ref": "#/definitions/relative_path"
232
- default: null
233
- description: "Custom global JavaScript file"
234
- environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_JAVASCRIPT"
235
218
  logo:
236
219
  "$ref": "#/definitions/relative_path"
237
220
  default: null
238
221
  description: "Navbar logo"
239
222
  environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_LOGO"
240
- nav_bullet:
241
- "$ref": "#/definitions/relative_path"
242
- default: null
243
- description: "Bullet image used for nav menu headers"
244
- environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_NAV_BULLET"
245
223
  type: "object"
246
224
  version_added: "1.2.0"
247
225
  BRANDING_PREPENDED_FILENAME:
@@ -758,16 +736,22 @@ properties:
758
736
  "`nautobot-server send_installation_metrics`": "../tools/nautobot-server.md#send_installation_metrics"
759
737
  type: "string"
760
738
  version_added: "1.6.0"
761
- DEVICE_NAME_AS_NATURAL_KEY:
762
- default: false
739
+ DEVICE_UNIQUENESS:
740
+ default: "location_tenant_name"
763
741
  description: >-
764
742
  `Device` names are not guaranteed globally-unique by Nautobot but in practice they often are.
765
- Set this to `True` to use the device `name` alone as the natural key for `Device` objects.
766
- Set this to `False` to use the sequence `(name, tenant, location)` as the natural key instead.
767
- environment_variable: "NAUTOBOT_DEVICE_NAME_AS_NATURAL_KEY"
743
+ Select how Devices are uniquely identified:
744
+ - 'location_tenant_name': combination of Location + Tenant + Name
745
+ - 'name': Device name must be globally unique
746
+ - 'none': No enforced uniqueness (rely on other validation rules or custom validators)
747
+ The `DEVICE_UNIQUENESS` setting also determines which fields Nautobot treats as the natural key for a Device.
748
+ - If set to "location_tenant_name", the natural key is the combination of Name, Tenant, and Location.
749
+ - If set to "name", the natural key is the device name alone.
750
+ - If set to "none", Nautobot falls back to using the primary key for uniqueness.
751
+ environment_variable: "NAUTOBOT_DEVICE_UNIQUENESS"
768
752
  is_constance_config: true
769
- type: "boolean"
770
- version_added: "2.0.0"
753
+ type: "string"
754
+ version_added: "3.0.0"
771
755
  EVENT_BROKERS:
772
756
  default: {}
773
757
  description: >-
nautobot/core/signals.py CHANGED
@@ -67,10 +67,21 @@ def invalidate_max_depth_cache(sender, **kwargs):
67
67
 
68
68
  Note that this signal is connected in `TreeModel.__init_subclass__()` so as to only apply to those models.
69
69
  """
70
- from nautobot.core.models.tree_queries import TreeManager
70
+ from nautobot.core.models.tree_queries import TreeManager, TreeNode
71
71
 
72
72
  if not isinstance(sender.objects, TreeManager):
73
73
  return
74
74
 
75
+ # If the instance is a TreeNode, and it has siblings, skip invalidating the cache.
76
+ instance = kwargs.get("instance", None)
77
+ if isinstance(instance, TreeNode):
78
+ try:
79
+ parent = getattr(instance, "parent", None)
80
+ except instance.DoesNotExist:
81
+ parent = None
82
+ if parent and getattr(parent, "children", None) and parent.children.count() > 1:
83
+ # TreeNode has siblings, depth can't change
84
+ return
85
+
75
86
  with contextlib.suppress(redis.exceptions.ConnectionError):
76
87
  cache.delete(sender.objects.max_depth_cache_key)
nautobot/core/tables.py CHANGED
@@ -370,12 +370,17 @@ class ToggleColumn(django_tables2.CheckBoxColumn):
370
370
  default = kwargs.pop("default", "")
371
371
  visible = kwargs.pop("visible", False)
372
372
  if "attrs" not in kwargs:
373
- kwargs["attrs"] = {"td": {"class": "nb-w-0"}}
373
+ kwargs["attrs"] = {
374
+ "input": {"class": "form-check-input nb-form-check-input-sm mt-2"},
375
+ "td": {"class": "nb-w-0"},
376
+ }
374
377
  super().__init__(*args, default=default, visible=visible, **kwargs)
375
378
 
376
379
  @property
377
380
  def header(self):
378
- return mark_safe('<input type="checkbox" class="toggle" title="Toggle all" />')
381
+ return mark_safe(
382
+ '<input type="checkbox" class="toggle form-check-input nb-form-check-input-sm mt-2" title="Toggle all" />'
383
+ )
379
384
 
380
385
 
381
386
  class BooleanColumn(django_tables2.Column):
@@ -405,7 +410,8 @@ class ButtonsColumn(django_tables2.TemplateColumn):
405
410
  "th": {"class": "nb-actionable nb-w-0"},
406
411
  }
407
412
  # Note that braces are escaped to allow for string formatting prior to template rendering
408
- template_code = """
413
+ template_code = """\
414
+ {{% if record.present_in_database %}}
409
415
  <div class="dropdown">
410
416
  <button class="btn dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
411
417
  <span class="mdi mdi-dots-vertical" aria-hidden="true"></span>
@@ -439,6 +445,7 @@ class ButtonsColumn(django_tables2.TemplateColumn):
439
445
  {{% endif %}}
440
446
  </ul>
441
447
  </div>
448
+ {{% endif %}}
442
449
  """
443
450
 
444
451
  def __init__(
@@ -490,7 +497,7 @@ class ApprovalButtonsColumn(django_tables2.TemplateColumn):
490
497
  """
491
498
 
492
499
  buttons = ("detail", "changelog", "approve", "deny")
493
- attrs = {"td": {"class": "d-print-none text-right text-nowrap"}}
500
+ attrs = {"td": {"class": "d-print-none text-end text-nowrap"}}
494
501
  template_name = "extras/inc/approval_buttons_column.html"
495
502
 
496
503
  def __init__(
@@ -652,11 +659,11 @@ class LinkedCountColumn(django_tables2.Column):
652
659
  {k: (getattr(record, v) or settings.FILTERS_NULL_CHOICE_VALUE) for k, v in self.url_params.items()}
653
660
  )
654
661
  if value > 1:
655
- return format_html('<a href="{}" class="badge">{}</a>', url, value)
662
+ return format_html('<a href="{}" class="badge bg-primary">{}</a>', url, value)
656
663
  if related_record is not None:
657
664
  return helpers.hyperlinked_object(related_record, self.display_field)
658
665
  if value == 1:
659
- return format_html('<a href="{}" class="badge">{}</a>', url, value)
666
+ return format_html('<a href="{}" class="badge bg-primary">{}</a>', url, value)
660
667
  return helpers.placeholder(value)
661
668
 
662
669
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  {% block content %}
5
5
  <div class="row justify-content-center" style="margin-top: 9.375rem;">
6
- <div class="col-md-4 offset-md-4">
6
+ <div class="col-sm-12 col-md-9 col-lg-7 nb-lg-max-width">
7
7
  <div class="card">
8
8
  <div class="card-header">
9
9
  <strong class="hstack gap-4">{% block icon %}{% endblock %}{% block title %}{% endblock %}</strong>