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
nautobot/core/checks.py CHANGED
@@ -6,6 +6,9 @@ from django.core.exceptions import ValidationError
6
6
  from django.core.validators import URLValidator
7
7
  from django.db import connections
8
8
 
9
+ from nautobot.core.utils.config import get_settings_or_config
10
+ from nautobot.dcim.choices import DeviceUniquenessChoices
11
+
9
12
  E002 = Error(
10
13
  "'nautobot.core.authentication.ObjectPermissionBackend' must be included in AUTHENTICATION_BACKENDS",
11
14
  id="nautobot.core.E002",
@@ -42,6 +45,19 @@ W005 = Warning(
42
45
  obj=settings,
43
46
  )
44
47
 
48
+ W006 = Warning(
49
+ "The deprecated setting DEVICE_NAME_AS_NATURAL_KEY is still defined.",
50
+ hint="This setting has been superseded by DEVICE_UNIQUENESS (see Device Constraints).",
51
+ id="nautobot.core.W006",
52
+ obj=settings,
53
+ )
54
+
55
+ W007 = Warning(
56
+ "Invalid DEVICE_UNIQUENESS configuration value.",
57
+ hint=f"DEVICE_UNIQUENESS must be one of: {', '.join(DeviceUniquenessChoices.values())}.",
58
+ id="nautobot.core.W007",
59
+ )
60
+
45
61
  MIN_POSTGRESQL_MAJOR_VERSION = 12
46
62
  MIN_POSTGRESQL_MINOR_VERSION = 0
47
63
 
@@ -153,3 +169,33 @@ def check_data_validation_engine_installed(app_configs, **kwargs):
153
169
  if app_name in settings.PLUGINS or app_name in settings.PLUGINS_CONFIG:
154
170
  return [E006]
155
171
  return []
172
+
173
+
174
+ @register(Tags.compatibility)
175
+ def check_deprecated_device_name_as_natural_key(app_configs, **kwargs):
176
+ """
177
+ Warn if the deprecated DEVICE_NAME_AS_NATURAL_KEY setting is still defined.
178
+
179
+ This setting existed prior to 3.0 and has been replaced by the
180
+ DEVICE_UNIQUENESS Constance configuration.
181
+ """
182
+ try:
183
+ get_settings_or_config("DEVICE_NAME_AS_NATURAL_KEY")
184
+ return [W006]
185
+ except AttributeError:
186
+ pass
187
+ return []
188
+
189
+
190
+ @register(Tags.compatibility)
191
+ def check_valid_value_for_device_uniqueness(app_configs, **kwargs):
192
+ """
193
+ Warn if the invalid value for DEVICE_UNIQUENESS is set.
194
+ """
195
+ try:
196
+ device_uniqueness = get_settings_or_config("DEVICE_UNIQUENESS")
197
+ if device_uniqueness not in DeviceUniquenessChoices.values():
198
+ return [W007]
199
+ except AttributeError:
200
+ return [W007]
201
+ return []
nautobot/core/choices.py CHANGED
@@ -238,7 +238,7 @@ class ButtonActionIconChoices(ChoiceSet):
238
238
  LOCK = "mdi-lock"
239
239
  MAGNIFY = "mdi-magnify"
240
240
  NOTE = "mdi-note-text"
241
- RENAME = "mdi-pencil"
241
+ RENAME = "mdi-rename"
242
242
  SWAP = "mdi-swap-vertical"
243
243
  TRASH = "mdi-trash-can-outline"
244
244
 
@@ -3,6 +3,8 @@ import logging
3
3
  import os
4
4
  import re
5
5
 
6
+ from .migrate_deprecated_templates import replace_deprecated_templates
7
+
6
8
  logger = logging.getLogger(__name__)
7
9
 
8
10
 
@@ -135,6 +137,61 @@ def _fix_nav_tabs_items(html: str, stats: dict, file_path=None) -> str:
135
137
  return pattern.sub(ul_replacer, html)
136
138
 
137
139
 
140
+ def _fix_dropdown_lis(html: str, stats: dict, file_path=None) -> str:
141
+ """Adds 'dropdown-item' class to all <li><a>...</a></li> tags in the given HTML string."""
142
+
143
+ def a_replacer(match):
144
+ a_tag = match.group(0)
145
+ # If Django template logic is found, notify user and skip auto-fix
146
+ if re.search(r"{%.*%}", a_tag):
147
+ if "manual_nav_template_lines" not in stats:
148
+ stats["manual_nav_template_lines"] = []
149
+ # Get line number and character position of li_tag
150
+ html_lines = html.splitlines()
151
+ for i, line in enumerate(html_lines):
152
+ if a_tag in line:
153
+ # Append line number, character position and file path for easier identification
154
+ stats["manual_nav_template_lines"].append(
155
+ f"{file_path}:{i + 1}:{line.index(a_tag)} - Please review manually '{a_tag}'"
156
+ )
157
+ break
158
+ else:
159
+ stats["manual_nav_template_lines"].append(f"{file_path} - Please review manually '{a_tag}'")
160
+ return a_tag
161
+
162
+ # Add dropdown-item to <a>
163
+ class_attr_match = re.search(r"""class=(["'])(.*?)\1""", a_tag)
164
+ if class_attr_match:
165
+ classes = class_attr_match.group(2).split()
166
+ if not any("dropdown-item" in _class for _class in classes):
167
+ classes.append("dropdown-item")
168
+ stats["dropdown_items"] += 1
169
+ new_class_attr = f'class="{" ".join(classes)}"'
170
+ a_tag = re.sub(r"""class=(["'])(.*?)\1""", new_class_attr, a_tag, count=1)
171
+ else:
172
+ a_tag = re.sub(r"<a(\s|>)", r'<a class="dropdown-item"\1', a_tag, count=1)
173
+ stats["dropdown_items"] += 1
174
+
175
+ return a_tag
176
+
177
+ return re.sub("<a[^>]*>.*?</a>", a_replacer, html, flags=re.DOTALL)
178
+
179
+
180
+ def _fix_dropdown_items(html: str, stats: dict, file_path=None) -> str:
181
+ """Ensures that all <li> elements within <ul class="dropdown-menu"> have class="dropdown-item" as appropriate."""
182
+
183
+ def ul_replacer(ul_match):
184
+ ul_tag = ul_match.group(0)
185
+ ul_tag_new = _fix_dropdown_lis(ul_tag, stats, file_path=file_path)
186
+ return ul_tag_new
187
+
188
+ pattern = re.compile(
189
+ r"""<ul\s+class=(["'])(?:[^"']*\s)?dropdown-menu(?:\s[^"']*)?\1[^>]*>.*?</ul>""",
190
+ re.DOTALL | re.IGNORECASE,
191
+ )
192
+ return pattern.sub(ul_replacer, html)
193
+
194
+
138
195
  def _fix_extra_nav_tabs_block(html_string: str, stats: dict, file_path: str) -> str:
139
196
  """
140
197
  Finds {% block extra_nav_tabs %} blocks and adds nav-item/nav-link to <li> tags inside using regex.
@@ -505,6 +562,7 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
505
562
  "extra_breadcrumbs": 0,
506
563
  "breadcrumb_items": 0,
507
564
  "nav_items": 0,
565
+ "dropdown_items": 0,
508
566
  "panel_classes": 0,
509
567
  "manual_nav_template_lines": [],
510
568
  }
@@ -521,6 +579,7 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
521
579
  "checkbox-inline": "form-check-input",
522
580
  "close": "btn-close",
523
581
  "control-label": "col-form-label",
582
+ "dropdown-menu-right": "dropdown-menu-end",
524
583
  "form-control-static": "form-control-plaintext",
525
584
  "form-group": "mb-10 d-flex justify-content-center",
526
585
  "help-block": "form-text",
@@ -582,6 +641,7 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
582
641
  current_html = _convert_caret_in_span_to_mdi(current_html, stats)
583
642
  current_html = _convert_hover_copy_buttons(current_html, stats)
584
643
  current_html = _fix_nav_tabs_items(current_html, stats, file_path=file_path)
644
+ current_html = _fix_dropdown_items(current_html, stats, file_path=file_path)
585
645
 
586
646
  return current_html, stats
587
647
 
@@ -589,7 +649,7 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
589
649
  # --- File Processing ---
590
650
 
591
651
 
592
- def fix_html_files_in_directory(directory: str, resize=False) -> None:
652
+ def fix_html_files_in_directory(directory: str, resize=False, dry_run=False, skip_templates=False) -> None:
593
653
  """
594
654
  Recursively finds all .html files in the given directory, applies convert_bootstrap_classes,
595
655
  and overwrites each file with the fixed content. If resize is True, it will only change the
@@ -598,11 +658,22 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
598
658
 
599
659
  totals = {
600
660
  k: 0
601
- for k in ["replacements", "extra_breadcrumbs", "breadcrumb_items", "nav_items", "panel_classes", "resizing_xs"]
661
+ for k in [
662
+ "replacements",
663
+ "extra_breadcrumbs",
664
+ "breadcrumb_items",
665
+ "nav_items",
666
+ "dropdown_items",
667
+ "panel_classes",
668
+ "resizing_xs",
669
+ ]
602
670
  }
603
671
  # Breakpoints that are not xs do not count as failures in djlint, so we keep a separate counter
604
672
  resizing_other = 0
605
673
 
674
+ if not os.path.exists(directory):
675
+ raise FileNotFoundError(directory)
676
+
606
677
  if os.path.isfile(directory):
607
678
  only_filename = os.path.basename(directory)
608
679
  directory = os.path.dirname(directory)
@@ -649,9 +720,12 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
649
720
 
650
721
  fixed_content, stats = convert_bootstrap_classes(content, file_path=file_path)
651
722
 
652
- with open(file_path, "w", encoding="utf-8") as f:
653
- f.write(fixed_content)
654
- logger.info("Fixed: %s", file_path)
723
+ if dry_run:
724
+ logger.info("Would fix: %s", file_path)
725
+ else:
726
+ with open(file_path, "w", encoding="utf-8") as f:
727
+ f.write(fixed_content)
728
+ logger.info("Fixed: %s", file_path)
655
729
 
656
730
  if any(stats.values()):
657
731
  print(f"→ {os.path.relpath(file_path, directory)}: ", end="")
@@ -663,6 +737,8 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
663
737
  print(f"{stats['breadcrumb_items']} breadcrumb-items, ", end="")
664
738
  if stats["nav_items"]:
665
739
  print(f"{stats['nav_items']} nav-items, ", end="")
740
+ if stats["dropdown_items"]:
741
+ print(f"{stats['dropdown_items']} dropdown-items, ", end="")
666
742
  if stats["panel_classes"]:
667
743
  print(f"{stats['panel_classes']} panel replacements, ", end="")
668
744
  print()
@@ -675,18 +751,23 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
675
751
  if k in totals:
676
752
  totals[k] += v
677
753
 
754
+ templates_replaced = replace_deprecated_templates(directory, dry_run=dry_run) if not skip_templates else 0
755
+
678
756
  # Global summary
679
757
  total_issues = sum(totals.values())
680
758
  print("=== Global Summary ===")
681
759
  print(f"Total issues fixed: {total_issues}")
682
- print(f"- Class replacements: {totals['replacements']}")
683
- print(f"- Extra-breadcrumb fixes: {totals['extra_breadcrumbs']}")
684
- print(f"- <li> in <ol.breadcrumb>: {totals['breadcrumb_items']}")
685
- print(f"- <li> in <ul.nav-tabs>: {totals['nav_items']}")
686
- print(f"- Panel class replacements: {totals['panel_classes']}")
687
- print(f"- Resizing breakpoint xs: {totals['resizing_xs']}")
760
+ print(f"- Class replacements: {totals['replacements']}")
761
+ print(f"- Extra-breadcrumb fixes: {totals['extra_breadcrumbs']}")
762
+ print(f"- <li> in <ol.breadcrumb>: {totals['breadcrumb_items']}")
763
+ print(f"- <li> in <ul.nav-tabs>: {totals['nav_items']}")
764
+ print(f"- <a> in <ul.dropdown-menu>: {totals['dropdown_items']}")
765
+ print(f"- Panel class replacements: {totals['panel_classes']}")
766
+ print(f"- Resizing breakpoint xs: {totals['resizing_xs']}")
688
767
  print("-------------------------------------")
689
- print(f"- Resizing other breakpoints: {resizing_other}")
768
+ print(f"- Resizing other breakpoints: {resizing_other}")
769
+ print("-------------------------------------")
770
+ print(f"- Deprecated templates replaced: {templates_replaced}")
690
771
 
691
772
 
692
773
  def main():
@@ -697,10 +778,21 @@ def main():
697
778
  action="store_true",
698
779
  help="Change column breakpoints to be one level higher, such as 'col-xs-*' to 'col-sm-*'",
699
780
  )
781
+ parser.add_argument(
782
+ "-d",
783
+ "--dry-run",
784
+ action="store_true",
785
+ help="Show which files would be modified without making any changes.",
786
+ )
700
787
  parser.add_argument("path", type=str, help="Path to directory in which to recursively fix all .html files.")
788
+ parser.add_argument(
789
+ "-st", "--skip-template-replacement", action="store_true", help="Skip replacing deprecated templates."
790
+ )
701
791
  args = parser.parse_args()
702
792
 
703
- fix_html_files_in_directory(args.path, resize=args.resize)
793
+ fix_html_files_in_directory(
794
+ args.path, resize=args.resize, dry_run=args.dry_run, skip_templates=args.skip_template_replacement
795
+ )
704
796
 
705
797
 
706
798
  if __name__ == "__main__":
@@ -0,0 +1,227 @@
1
+ import argparse
2
+ import os
3
+ import re
4
+
5
+ TEMPLATE_REPLACEMENTS = {
6
+ # Format: new_template: [old_template1, old_template2, ...]
7
+ "circuits/circuit_create.html": ["circuits/circuit_edit.html", "circuits/circuit_update.html"],
8
+ "circuits/circuittermination_create.html": [
9
+ "circuits/circuittermination_edit.html",
10
+ "circuits/circuittermination_update.html",
11
+ ],
12
+ "circuits/provider_create.html": ["circuits/provider_edit.html", "circuits/provider_update.html"],
13
+ "dcim/cable_retrieve.html": ["dcim/cable.html"],
14
+ "dcim/cable_update.html": ["dcim/cable_edit.html"],
15
+ "dcim/device_create.html": ["dcim/device_edit.html"],
16
+ "dcim/devicetype_update.html": ["dcim/devicetype_edit.html"],
17
+ "dcim/location_update.html": ["dcim/location_edit.html"],
18
+ "dcim/rack_retrieve.html": ["dcim/rack.html"],
19
+ "dcim/rack_update.html": ["dcim/rack_edit.html"],
20
+ "dcim/rackreservation_retrieve.html": ["dcim/rackreservation.html"],
21
+ "dcim/virtualchassis_create.html": ["dcim/virtualchassis_add.html"],
22
+ "extras/configcontext_update.html": ["extras/configcontext_edit.html"],
23
+ "extras/configcontextschema_retrieve.html": ["extras/configcontextschema.html"],
24
+ "extras/configcontextschema_update.html": ["extras/configcontextschema_edit.html"],
25
+ "extras/customfield_update.html": ["extras/customfield_edit.html"],
26
+ "extras/dynamicgroup_retrieve.html": ["extras/dynamicgroup.html"],
27
+ "extras/dynamicgroup_update.html": ["extras/dynamicgroup_edit.html"],
28
+ "extras/gitrepository_retrieve.html": ["extras/gitrepository.html"],
29
+ "extras/gitrepository_update.html": ["extras/gitrepository_object_edit.html"],
30
+ "extras/jobresult_retrieve.html": ["extras/jobresult.html"],
31
+ "extras/objectchange_retrieve.html": ["extras/objectchange.html"],
32
+ "extras/secret_create.html": ["extras/secret_edit.html"],
33
+ "extras/secretsgroup_update.html": ["extras/secretsgroup_edit.html"],
34
+ "extras/tag_update.html": ["extras/tag_edit.html"],
35
+ "generic/object_bulk_create.html": ["generic/object_bulk_import.html"],
36
+ "generic/object_bulk_destroy.html": ["generic/object_bulk_delete.html"],
37
+ "generic/object_bulk_update.html": ["generic/object_bulk_edit.html"],
38
+ "generic/object_changelog.html": ["extras/object_changelog.html"],
39
+ "generic/object_create.html": ["dcim/powerpanel_edit.html", "generic/object_edit.html", "ipam/service_edit.html"],
40
+ "generic/object_destroy.html": ["generic/object_delete.html"],
41
+ "generic/object_list.html": ["extras/graphqlquery_list.html", "extras/objectchange_list.html"],
42
+ "generic/object_notes.html": ["extras/object_notes.html"],
43
+ "generic/object_retrieve.html": [
44
+ "circuits/circuit.html",
45
+ "circuits/circuit_retrieve.html",
46
+ "circuits/circuittermination.html",
47
+ "circuits/circuittermination_retrieve.html",
48
+ "circuits/circuittype.html",
49
+ "circuits/circuittype_retrieve.html",
50
+ "circuits/provider.html",
51
+ "circuits/provider_retrieve.html",
52
+ "circuits/providernetwork.html",
53
+ "circuits/providernetwork_retrieve.html",
54
+ "cloud/cloudaccount_retrieve.html",
55
+ "cloud/cloudnetwork_retrieve.html",
56
+ "cloud/cloudresourcetype_retrieve.html",
57
+ "cloud/cloudservice_retrieve.html",
58
+ "dcim/controller/base.html",
59
+ "dcim/controller_retrieve.html",
60
+ "dcim/controller_wirelessnetworks.html",
61
+ "dcim/controllermanageddevicegroup_retrieve.html",
62
+ "dcim/device/base.html",
63
+ "dcim/device/consoleports.html",
64
+ "dcim/device/consoleserverports.html",
65
+ "dcim/device/devicebays.html",
66
+ "dcim/device/frontports.html",
67
+ "dcim/device/interfaces.html",
68
+ "dcim/device/inventory.html",
69
+ "dcim/device/modulebays.html",
70
+ "dcim/device/poweroutlets.html",
71
+ "dcim/device/powerports.html",
72
+ "dcim/device/rearports.html",
73
+ "dcim/device/wireless.html",
74
+ "dcim/device_component.html",
75
+ "dcim/devicefamily_retrieve.html",
76
+ "dcim/deviceredundancygroup_retrieve.html",
77
+ "dcim/devicetype.html",
78
+ "dcim/devicetype_retrieve.html",
79
+ "dcim/interfaceredundancygroup_retrieve.html",
80
+ "dcim/location.html",
81
+ "dcim/location_retrieve.html",
82
+ "dcim/locationtype.html",
83
+ "dcim/locationtype_retrieve.html",
84
+ "dcim/manufacturer.html",
85
+ "dcim/modulebay_retrieve.html",
86
+ "dcim/platform.html",
87
+ "dcim/powerfeed.html",
88
+ "dcim/powerfeed_retrieve.html",
89
+ "dcim/powerpanel.html",
90
+ "dcim/powerpanel_retrieve.html",
91
+ "dcim/rackgroup.html",
92
+ "dcim/softwareimagefile_retrieve.html",
93
+ "dcim/softwareversion_retrieve.html",
94
+ "dcim/virtualchassis.html",
95
+ "dcim/virtualchassis_retrieve.html",
96
+ "dcim/virtualdevicecontext_retrieve.html",
97
+ "extras/computedfield.html",
98
+ "extras/computedfield_retrieve.html",
99
+ "extras/configcontext.html",
100
+ "extras/configcontext_retrieve.html",
101
+ "extras/contact_retrieve.html",
102
+ "extras/customfield.html",
103
+ "extras/customfield_retrieve.html",
104
+ "extras/customlink.html",
105
+ "extras/exporttemplate.html",
106
+ "extras/graphqlquery.html",
107
+ "extras/graphqlquery_retrieve.html",
108
+ "extras/job_detail.html",
109
+ "extras/jobbutton_retrieve.html",
110
+ "extras/jobhook.html",
111
+ "extras/jobqueue_retrieve.html",
112
+ "extras/metadatatype_retrieve.html",
113
+ "extras/note.html",
114
+ "extras/note_retrieve.html",
115
+ "extras/relationship.html",
116
+ "extras/secret.html",
117
+ "extras/secretsgroup.html",
118
+ "extras/secretsgroup_retrieve.html",
119
+ "extras/status.html",
120
+ "extras/tag.html",
121
+ "extras/tag_retrieve.html",
122
+ "extras/team_retrieve.html",
123
+ "generic/object_detail.html",
124
+ "ipam/namespace_retrieve.html",
125
+ "ipam/prefix.html",
126
+ "ipam/prefix_retrieve.html",
127
+ "ipam/rir.html",
128
+ "ipam/routetarget.html",
129
+ "ipam/service.html",
130
+ "ipam/service_retrieve.html",
131
+ "ipam/vlan.html",
132
+ "ipam/vlan_retrieve.html",
133
+ "ipam/vlangroup.html",
134
+ "ipam/vrf.html",
135
+ "tenancy/tenant.html",
136
+ "tenancy/tenantgroup.html",
137
+ "tenancy/tenantgroup_retrieve.html",
138
+ "virtualization/clustergroup.html",
139
+ "virtualization/clustertype.html",
140
+ "virtualization/virtualmachine.html",
141
+ "virtualization/virtualmachine_retrieve.html",
142
+ "wireless/radioprofile_retrieve.html",
143
+ "wireless/supporteddatarate_retrieve.html",
144
+ "wireless/wirelessnetwork_retrieve.html",
145
+ ],
146
+ "ipam/prefix_create.html": ["ipam/prefix_edit.html"],
147
+ "ipam/vlan_update.html": ["ipam/vlan_edit.html"],
148
+ "tenancy/tenant_create.html": ["tenancy/tenant_edit.html"],
149
+ "virtualchassis_update.html": ["dcim/virtualchassis_edit.html"],
150
+ "virtualization/virtualmachine_update.html": ["virtualization/virtualmachine_edit.html"],
151
+ }
152
+
153
+
154
+ def replace_template_references(content: str) -> tuple[str, bool]:
155
+ """
156
+ Replaces references to deprecated templates with new ones.
157
+
158
+ Args:
159
+ content: The content of the file to replace references in.
160
+
161
+ Returns:
162
+ A tuple containing the updated content and a boolean indicating if any changes were made.
163
+ """
164
+ for new_template, old_templates in TEMPLATE_REPLACEMENTS.items():
165
+ for old_template in old_templates:
166
+ pattern = rf"(\{{%\s*extends\s*['\"]){re.escape(old_template)}(['\"]\s*%\}})"
167
+ new_content, count = re.subn(pattern, rf"\1{new_template}\2", content)
168
+ if count > 0:
169
+ # A django template can only have one extends statement, so we can return as soon as we find a match.
170
+ return new_content, True
171
+
172
+ return content, False
173
+
174
+
175
+ def replace_deprecated_templates(path: str, dry_run: bool = False):
176
+ """
177
+ Recursively finds all .html files in the given directory,
178
+ and replaces references to deprecated templates with new ones.
179
+ """
180
+
181
+ if os.path.isfile(path):
182
+ only_filename = os.path.basename(path)
183
+ path = os.path.dirname(path)
184
+ else:
185
+ only_filename = None
186
+ print("Finding deprecated templates to replace...")
187
+ count = 0
188
+
189
+ for root, _, files in os.walk(path):
190
+ for filename in files:
191
+ if only_filename and only_filename != filename:
192
+ continue
193
+ if filename.endswith((".html")):
194
+ file_path = os.path.join(root, filename)
195
+ with open(file_path, "r", encoding="utf-8") as f:
196
+ original_content = f.read()
197
+
198
+ content = original_content
199
+
200
+ fixed_content, was_updated = replace_template_references(content)
201
+
202
+ if was_updated:
203
+ count += 1
204
+ if dry_run:
205
+ print(f"Detected deprecated template reference in {file_path}")
206
+ continue
207
+ with open(file_path, "w", encoding="utf-8") as f:
208
+ f.write(fixed_content)
209
+ print(f"Updated: {file_path}")
210
+ if not count:
211
+ print("No deprecated templates found.")
212
+ else:
213
+ print(f"Found {count} deprecated templates.")
214
+ return count
215
+
216
+
217
+ def main():
218
+ parser = argparse.ArgumentParser(description="Replace deprecated templates with new ones")
219
+ parser.add_argument("path", type=str, help="Path to directory in which to recursively fix all .html files.")
220
+ parser.add_argument("--dry-run", action="store_true", help="Do not make any changes to the files.")
221
+ args = parser.parse_args()
222
+
223
+ replace_deprecated_templates(args.path, args.dry_run)
224
+
225
+
226
+ if __name__ == "__main__":
227
+ main()
@@ -210,3 +210,6 @@ GLOBAL_SEARCH_EXCLUDE_LIST = [
210
210
  "vrfprefixassignment",
211
211
  "webhook",
212
212
  ]
213
+
214
+ # M2M fields that are included by default in API responses if exclude_m2m is not provided
215
+ DEFAULT_M2M_FIELDS = ["tags", "content_types", "object_types"]
@@ -1,3 +1,5 @@
1
+ from urllib.parse import urlparse
2
+
1
3
  from django.conf import settings as django_settings
2
4
  from django.urls import NoReverseMatch, reverse
3
5
 
@@ -78,6 +80,12 @@ def nav_menu(request):
78
80
  pass
79
81
 
80
82
  nav_menu_object = {"tabs": {}}
83
+
84
+ if htmx_current_url := request.headers.get("HX-Current-URL"):
85
+ current_url = urlparse(htmx_current_url).path
86
+ else:
87
+ current_url = request.path
88
+
81
89
  for tab_name, tab_details in registry["nav_menu"]["tabs"].items():
82
90
  if not tab_details["permissions"] or has_one_or_more_perms(request.user, tab_details["permissions"]):
83
91
  nav_menu_object["tabs"][tab_name] = {"groups": {}, "icon": tab_details["icon"]}
@@ -93,7 +101,7 @@ def nav_menu(request):
93
101
  if has_identified_active_link:
94
102
  is_active = False
95
103
  else:
96
- is_active = item_link in [request.path, related_list_view_link]
104
+ is_active = item_link in [current_url, related_list_view_link]
97
105
  if is_active:
98
106
  has_identified_active_link = True
99
107
 
nautobot/core/filters.py CHANGED
@@ -101,6 +101,10 @@ class MultiValueDateTimeFilter(django_filters.DateTimeFilter, django_filters.Mul
101
101
  class MultiValueNumberFilter(django_filters.NumberFilter, django_filters.MultipleChoiceFilter):
102
102
  field_class = multivalue_field_factory(django_forms.IntegerField)
103
103
 
104
+ def __init__(self, *args, choices=None, **kwargs):
105
+ super().__init__(*args, **kwargs)
106
+ self.choices = list(choices) if choices is not None else None
107
+
104
108
 
105
109
  class MultiValueBigNumberFilter(MultiValueNumberFilter):
106
110
  """Subclass of MultiValueNumberFilter used for BigInteger model fields."""
@@ -69,6 +69,7 @@ from nautobot.core.forms.widgets import (
69
69
  DatePicker,
70
70
  DateTimePicker,
71
71
  MultiValueCharInput,
72
+ NumberWithSelect,
72
73
  SelectWithDisabled,
73
74
  SelectWithPK,
74
75
  SlugWidget,
@@ -126,6 +127,7 @@ __all__ = (
126
127
  "MultiValueCharInput",
127
128
  "MultipleContentTypeField",
128
129
  "NullableDateField",
130
+ "NumberWithSelect",
129
131
  "NumericArrayField",
130
132
  "PrefixFieldMixin",
131
133
  "ReturnURLForm",
@@ -377,7 +377,7 @@ class DynamicFilterForm(BootstrapMixin, forms.Form):
377
377
 
378
378
  def _get_lookup_field_choices(self):
379
379
  """Get choices for lookup_fields i.e filterset parameters without a lookup expr"""
380
- from nautobot.extras.filters.mixins import RelationshipFilter # Avoid circular import
380
+ from nautobot.extras.filter_mixins import RelationshipFilter # Avoid circular import
381
381
 
382
382
  filterset_without_lookup = (
383
383
  (
@@ -6,7 +6,7 @@ from django import forms
6
6
  from django.forms.models import ModelChoiceIterator
7
7
  from django.urls import get_script_prefix
8
8
 
9
- from nautobot.core import choices
9
+ from nautobot.core import choices as core_choices
10
10
  from nautobot.core.forms import utils
11
11
 
12
12
  __all__ = (
@@ -19,6 +19,7 @@ __all__ = (
19
19
  "ContentTypeSelect",
20
20
  "DatePicker",
21
21
  "DateTimePicker",
22
+ "NumberWithSelect",
22
23
  "SelectWithDisabled",
23
24
  "SelectWithPK",
24
25
  "SlugWidget",
@@ -68,7 +69,7 @@ class ColorSelect(forms.Select):
68
69
  option_template_name = "widgets/colorselect_option.html"
69
70
 
70
71
  def __init__(self, *args, **kwargs):
71
- kwargs["choices"] = utils.add_blank_choice(choices.ColorChoices)
72
+ kwargs["choices"] = utils.add_blank_choice(core_choices.ColorChoices)
72
73
  super().__init__(*args, **kwargs)
73
74
  self.attrs["class"] = "nautobot-select2-color-picker"
74
75
 
@@ -283,3 +284,21 @@ class ClearableFileInput(forms.ClearableFileInput):
283
284
 
284
285
  class Media:
285
286
  js = ["bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js"]
287
+
288
+
289
+ class NumberWithSelect(forms.NumberInput):
290
+ template_name = "widgets/number_input_with_choices.html"
291
+
292
+ def __init__(self, choices=None, *args, **kwargs):
293
+ super().__init__(*args, **kwargs)
294
+ if choices is None:
295
+ self.choices = []
296
+ elif hasattr(choices, "CHOICES"):
297
+ self.choices = core_choices.unpack_grouped_choices(choices.CHOICES)
298
+ else:
299
+ self.choices = core_choices.unpack_grouped_choices(choices)
300
+
301
+ def get_context(self, name, value, attrs):
302
+ context = super().get_context(name, value, attrs)
303
+ context["widget"]["choices"] = self.choices
304
+ return context