nautobot 2.4.18__py3-none-any.whl → 2.4.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (583) hide show
  1. nautobot/apps/ui.py +6 -0
  2. nautobot/circuits/tables.py +1 -1
  3. nautobot/circuits/templates/circuits/circuit_create.html +7 -7
  4. nautobot/circuits/templates/circuits/circuit_retrieve.html +1 -5
  5. nautobot/circuits/templates/circuits/circuittermination_create.html +26 -26
  6. nautobot/circuits/templates/circuits/inc/circuit_termination.html +20 -20
  7. nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +3 -3
  8. nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +7 -7
  9. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +1 -3
  10. nautobot/circuits/views.py +26 -9
  11. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +1 -4
  12. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +1 -7
  13. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +1 -3
  14. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +1 -5
  15. nautobot/cloud/views.py +45 -0
  16. nautobot/core/graphql/generators.py +5 -2
  17. nautobot/core/jobs/bulk_actions.py +48 -85
  18. nautobot/core/models/querysets.py +2 -1
  19. nautobot/core/templates/40x.html +15 -15
  20. nautobot/core/templates/500.html +21 -21
  21. nautobot/core/templates/admin/app_index.html +8 -8
  22. nautobot/core/templates/admin/base.html +104 -104
  23. nautobot/core/templates/admin/change_form.html +65 -65
  24. nautobot/core/templates/admin/change_list.html +60 -60
  25. nautobot/core/templates/admin/change_list_results.html +39 -39
  26. nautobot/core/templates/admin/config/config.html +47 -47
  27. nautobot/core/templates/admin/delete_confirmation.html +47 -47
  28. nautobot/core/templates/admin/edit_inline/stacked.html +124 -124
  29. nautobot/core/templates/admin/edit_inline/tabular.html +60 -60
  30. nautobot/core/templates/admin/includes/fieldset.html +4 -4
  31. nautobot/core/templates/admin/index.html +60 -60
  32. nautobot/core/templates/admin/prepopulated_fields_js.html +18 -18
  33. nautobot/core/templates/admin/submit_line.html +4 -4
  34. nautobot/core/templates/base_django.html +46 -46
  35. nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +8 -8
  36. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +8 -8
  37. nautobot/core/templates/buttons/export.html +3 -3
  38. nautobot/core/templates/components/breadcrumbs.html +5 -5
  39. nautobot/core/templates/components/button/default.html +3 -3
  40. nautobot/core/templates/components/button/dropdown.html +7 -7
  41. nautobot/core/templates/components/button/formbutton.html +4 -4
  42. nautobot/core/templates/components/panel/body_content_data_table.html +1 -1
  43. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +3 -0
  44. nautobot/core/templates/components/panel/footer_content_table.html +3 -1
  45. nautobot/core/templates/components/panel/header_extra_content_table.html +10 -1
  46. nautobot/core/templates/components/tab/content_wrapper.html +1 -1
  47. nautobot/core/templates/components/tab/label_wrapper.html +1 -1
  48. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +10 -3
  49. nautobot/core/templates/generic/object_bulk_add_component.html +40 -40
  50. nautobot/core/templates/generic/object_bulk_create.html +3 -3
  51. nautobot/core/templates/generic/object_bulk_destroy.html +6 -6
  52. nautobot/core/templates/generic/object_bulk_update.html +52 -52
  53. nautobot/core/templates/generic/object_import.html +33 -33
  54. nautobot/core/templates/generic/object_list.html +267 -267
  55. nautobot/core/templates/generic/object_retrieve.html +261 -261
  56. nautobot/core/templates/graphene/graphiql.html +127 -127
  57. nautobot/core/templates/home.html +62 -62
  58. nautobot/core/templates/inc/computed_fields/panel_data.html +13 -13
  59. nautobot/core/templates/inc/created_updated.html +8 -8
  60. nautobot/core/templates/inc/custom_fields/panel_data.html +13 -13
  61. nautobot/core/templates/inc/dynamic_groups_panel.html +11 -11
  62. nautobot/core/templates/inc/footer.html +19 -19
  63. nautobot/core/templates/inc/javascript.html +1 -1
  64. nautobot/core/templates/inc/media.html +46 -46
  65. nautobot/core/templates/inc/nav_menu.html +1 -1
  66. nautobot/core/templates/inc/relationships_table_rows.html +22 -22
  67. nautobot/core/templates/inc/tenant_table_row.html +1 -1
  68. nautobot/core/templates/login.html +77 -77
  69. nautobot/core/templates/media_failure.html +38 -38
  70. nautobot/core/templates/panel_table.html +1 -1
  71. nautobot/core/templates/rest_framework/api.html +3 -3
  72. nautobot/core/templates/search.html +1 -1
  73. nautobot/core/templates/swagger_ui.html +9 -9
  74. nautobot/core/templates/utilities/confirmation_form.html +18 -18
  75. nautobot/core/templates/utilities/render_field.html +1 -1
  76. nautobot/core/templates/utilities/render_jinja2.html +43 -43
  77. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +56 -56
  78. nautobot/core/templates/utilities/templatetags/utilization_graph.html +1 -1
  79. nautobot/core/templates/utilities/theme_preview.html +799 -799
  80. nautobot/core/templates/utilities/worker_status.html +122 -122
  81. nautobot/core/templates/widgets/clearable_file.html +3 -3
  82. nautobot/core/templates/widgets/sluginput.html +1 -1
  83. nautobot/core/templatetags/buttons.py +8 -2
  84. nautobot/core/templatetags/helpers.py +1 -1
  85. nautobot/core/testing/integration.py +7 -4
  86. nautobot/core/testing/views.py +24 -5
  87. nautobot/core/tests/test_breadcrumbs.py +78 -4
  88. nautobot/core/tests/test_commands.py +7 -4
  89. nautobot/core/tests/test_graphql.py +20 -5
  90. nautobot/core/tests/test_jobs.py +34 -21
  91. nautobot/core/tests/test_ui.py +175 -1
  92. nautobot/core/tests/test_utils.py +35 -0
  93. nautobot/core/tests/test_views_generic.py +43 -0
  94. nautobot/core/tests/test_views_utils.py +239 -5
  95. nautobot/core/ui/breadcrumbs.py +208 -21
  96. nautobot/core/ui/bulk_buttons.py +8 -0
  97. nautobot/core/ui/object_detail.py +138 -55
  98. nautobot/core/ui/titles.py +1 -0
  99. nautobot/core/utils/requests.py +27 -2
  100. nautobot/core/views/generic.py +41 -31
  101. nautobot/core/views/mixins.py +80 -110
  102. nautobot/core/views/utils.py +153 -1
  103. nautobot/dcim/api/serializers.py +8 -2
  104. nautobot/dcim/constants.py +1 -0
  105. nautobot/dcim/factory.py +4 -3
  106. nautobot/dcim/filters/mixins.py +1 -2
  107. nautobot/dcim/forms.py +5 -1
  108. nautobot/dcim/migrations/0074_alter_rack_u_height.py +21 -0
  109. nautobot/dcim/models/devices.py +26 -1
  110. nautobot/dcim/models/racks.py +2 -2
  111. nautobot/dcim/templates/dcim/cable.html +53 -53
  112. nautobot/dcim/templates/dcim/cable_connect.html +182 -182
  113. nautobot/dcim/templates/dcim/cable_trace.html +1 -1
  114. nautobot/dcim/templates/dcim/console_port_connection_list.html +5 -5
  115. nautobot/dcim/templates/dcim/consoleport.html +86 -86
  116. nautobot/dcim/templates/dcim/consoleserverport.html +86 -86
  117. nautobot/dcim/templates/dcim/controller_create.html +34 -34
  118. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +68 -68
  119. nautobot/dcim/templates/dcim/device/base.html +1 -104
  120. nautobot/dcim/templates/dcim/device/config.html +17 -17
  121. nautobot/dcim/templates/dcim/device/consoleports.html +1 -52
  122. nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -52
  123. nautobot/dcim/templates/dcim/device/devicebays.html +1 -48
  124. nautobot/dcim/templates/dcim/device/frontports.html +1 -52
  125. nautobot/dcim/templates/dcim/device/interfaces.html +1 -56
  126. nautobot/dcim/templates/dcim/device/inventory.html +1 -48
  127. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +64 -64
  128. nautobot/dcim/templates/dcim/device/modulebays.html +1 -48
  129. nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -52
  130. nautobot/dcim/templates/dcim/device/powerports.html +1 -52
  131. nautobot/dcim/templates/dcim/device/rearports.html +1 -52
  132. nautobot/dcim/templates/dcim/device/status.html +66 -66
  133. nautobot/dcim/templates/dcim/device/wireless.html +1 -72
  134. nautobot/dcim/templates/dcim/device.html +4 -422
  135. nautobot/dcim/templates/dcim/device_component_add.html +25 -25
  136. nautobot/dcim/templates/dcim/device_create.html +229 -0
  137. nautobot/dcim/templates/dcim/device_edit.html +2 -227
  138. nautobot/dcim/templates/dcim/devicebay.html +41 -41
  139. nautobot/dcim/templates/dcim/devicebay_populate.html +32 -32
  140. nautobot/dcim/templates/dcim/devicetype_component_add.html +28 -28
  141. nautobot/dcim/templates/dcim/devicetype_retrieve.html +1 -3
  142. nautobot/dcim/templates/dcim/frontport.html +84 -84
  143. nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +1 -1
  144. nautobot/dcim/templates/dcim/inc/device_interface_filter.html +8 -0
  145. nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +1 -15
  146. nautobot/dcim/templates/dcim/inc/location_hierarchy.html +22 -22
  147. nautobot/dcim/templates/dcim/interface.html +206 -206
  148. nautobot/dcim/templates/dcim/interface_connection_list.html +5 -5
  149. nautobot/dcim/templates/dcim/interfaceredundancygroupassociation_create.html +6 -6
  150. nautobot/dcim/templates/dcim/inventoryitem.html +44 -44
  151. nautobot/dcim/templates/dcim/inventoryitem_add.html +32 -32
  152. nautobot/dcim/templates/dcim/inventoryitem_edit.html +22 -22
  153. nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +46 -46
  154. nautobot/dcim/templates/dcim/location_retrieve.html +1 -7
  155. nautobot/dcim/templates/dcim/locationtype.html +1 -6
  156. nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -7
  157. nautobot/dcim/templates/dcim/module/base.html +85 -85
  158. nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
  159. nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
  160. nautobot/dcim/templates/dcim/module_retrieve.html +52 -52
  161. nautobot/dcim/templates/dcim/module_update.html +61 -61
  162. nautobot/dcim/templates/dcim/modulebay_destroy.html +1 -1
  163. nautobot/dcim/templates/dcim/modulebay_retrieve.html +85 -85
  164. nautobot/dcim/templates/dcim/modulebay_update.html +33 -33
  165. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  166. nautobot/dcim/templates/dcim/moduletype_retrieve.html +140 -144
  167. nautobot/dcim/templates/dcim/platform_create.html +38 -38
  168. nautobot/dcim/templates/dcim/power_port_connection_list.html +5 -5
  169. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -8
  170. nautobot/dcim/templates/dcim/poweroutlet.html +85 -85
  171. nautobot/dcim/templates/dcim/powerpanel_retrieve.html +1 -8
  172. nautobot/dcim/templates/dcim/powerport.html +91 -91
  173. nautobot/dcim/templates/dcim/rack_elevation_list.html +18 -18
  174. nautobot/dcim/templates/dcim/rack_retrieve.html +264 -274
  175. nautobot/dcim/templates/dcim/rackreservation_retrieve.html +0 -3
  176. nautobot/dcim/templates/dcim/rearport.html +78 -78
  177. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -5
  178. nautobot/dcim/tests/integration/test_device_bulk_operations.py +3 -2
  179. nautobot/dcim/tests/integration/test_location_bulk_operations.py +6 -2
  180. nautobot/dcim/tests/test_api.py +33 -1
  181. nautobot/dcim/tests/test_views.py +148 -4
  182. nautobot/dcim/ui.py +29 -0
  183. nautobot/dcim/urls.py +1 -109
  184. nautobot/dcim/utils.py +30 -0
  185. nautobot/dcim/views.py +1009 -531
  186. nautobot/extras/tables.py +20 -0
  187. nautobot/extras/templates/extras/computedfield_edit.html +4 -4
  188. nautobot/extras/templates/extras/configcontext_update.html +1 -1
  189. nautobot/extras/templates/extras/configcontextschema_retrieve.html +32 -32
  190. nautobot/extras/templates/extras/customfield_update.html +23 -23
  191. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +38 -38
  192. nautobot/extras/templates/extras/dynamicgroup_update.html +62 -62
  193. nautobot/extras/templates/extras/gitrepository_list.html +10 -10
  194. nautobot/extras/templates/extras/gitrepository_retrieve.html +68 -68
  195. nautobot/extras/templates/extras/graphqlquery_retrieve.html +73 -73
  196. nautobot/extras/templates/extras/inc/configcontext_format.html +2 -2
  197. nautobot/extras/templates/extras/inc/job_table.html +10 -10
  198. nautobot/extras/templates/extras/inc/jobresult.html +21 -21
  199. nautobot/extras/templates/extras/inc/jobresult_js.html +6 -6
  200. nautobot/extras/templates/extras/inc/tags_panel.html +10 -10
  201. nautobot/extras/templates/extras/job.html +64 -64
  202. nautobot/extras/templates/extras/job_approval_request.html +9 -9
  203. nautobot/extras/templates/extras/job_bulk_edit.html +13 -13
  204. nautobot/extras/templates/extras/job_edit.html +45 -45
  205. nautobot/extras/templates/extras/job_list.html +4 -4
  206. nautobot/extras/templates/extras/jobresult_retrieve.html +0 -25
  207. nautobot/extras/templates/extras/marketplace.html +101 -101
  208. nautobot/extras/templates/extras/metadatatype_create.html +20 -20
  209. nautobot/extras/templates/extras/object_assign_contact_or_team.html +18 -18
  210. nautobot/extras/templates/extras/object_configcontext.html +1 -3
  211. nautobot/extras/templates/extras/objectchange.html +2 -165
  212. nautobot/extras/templates/extras/objectchange_retrieve.html +165 -0
  213. nautobot/extras/templates/extras/plugin_detail.html +41 -41
  214. nautobot/extras/templates/extras/plugins_list.html +9 -9
  215. nautobot/extras/templates/extras/plugins_tiles.html +26 -26
  216. nautobot/extras/templates/extras/relationship_edit.html +4 -4
  217. nautobot/extras/templates/extras/role_retrieve.html +13 -13
  218. nautobot/extras/templates/extras/scheduled_jobs_approval_queue_list.html +21 -21
  219. nautobot/extras/templates/extras/scheduledjob.html +128 -128
  220. nautobot/extras/templates/extras/secret_create.html +53 -53
  221. nautobot/extras/templates/extras/secretsgroup_update.html +13 -13
  222. nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +3 -3
  223. nautobot/extras/templates/extras/webhook.html +79 -79
  224. nautobot/extras/tests/integration/test_relationships.py +6 -6
  225. nautobot/extras/tests/test_filters.py +1 -1
  226. nautobot/extras/tests/test_jobs.py +2 -0
  227. nautobot/extras/tests/test_views.py +3 -3
  228. nautobot/extras/urls.py +1 -3
  229. nautobot/extras/views.py +101 -27
  230. nautobot/ipam/filters.py +2 -2
  231. nautobot/ipam/migrations/0053_alter_vrfdeviceassignment_options_and_more.py +20 -0
  232. nautobot/ipam/models.py +34 -0
  233. nautobot/ipam/tables.py +3 -1
  234. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +4 -0
  235. nautobot/ipam/templates/ipam/inc/toggle_available.html +8 -8
  236. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +4 -4
  237. nautobot/ipam/templates/ipam/ipaddress.html +119 -123
  238. nautobot/ipam/templates/ipam/ipaddress_assign.html +10 -10
  239. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -1
  240. nautobot/ipam/templates/ipam/ipaddress_merge.html +180 -180
  241. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +48 -48
  242. nautobot/ipam/templates/ipam/prefix.html +2 -107
  243. nautobot/ipam/templates/ipam/prefix_create.html +34 -0
  244. nautobot/ipam/templates/ipam/prefix_edit.html +1 -34
  245. nautobot/ipam/templates/ipam/prefix_retrieve.html +3 -0
  246. nautobot/ipam/templates/ipam/service_retrieve.html +1 -6
  247. nautobot/ipam/templates/ipam/vlan_retrieve.html +1 -7
  248. nautobot/ipam/templates/ipam/vrf_edit.html +1 -1
  249. nautobot/ipam/tests/test_views.py +34 -0
  250. nautobot/ipam/ui.py +145 -0
  251. nautobot/ipam/urls.py +1 -46
  252. nautobot/ipam/utils/__init__.py +26 -0
  253. nautobot/ipam/views.py +219 -106
  254. nautobot/project-static/docs/404.html +11 -11
  255. nautobot/project-static/docs/apps/index.html +11 -11
  256. nautobot/project-static/docs/apps/nautobot-apps.html +11 -11
  257. nautobot/project-static/docs/assets/javascripts/{bundle.92b07e13.min.js → bundle.f55a23d4.min.js} +2 -2
  258. nautobot/project-static/docs/assets/javascripts/{bundle.92b07e13.min.js.map → bundle.f55a23d4.min.js.map} +2 -2
  259. nautobot/project-static/docs/assets/stylesheets/{main.7e37652d.min.css → main.e53b48f4.min.css} +1 -1
  260. nautobot/project-static/docs/assets/stylesheets/{main.7e37652d.min.css.map → main.e53b48f4.min.css.map} +1 -1
  261. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +11 -11
  262. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +11 -11
  263. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +11 -11
  264. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +11 -11
  265. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +11 -11
  266. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +11 -11
  267. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +11 -11
  268. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +11 -11
  269. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +11 -11
  270. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +11 -11
  271. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +11 -11
  272. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +11 -11
  273. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +11 -11
  274. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +11 -11
  275. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +11 -11
  276. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -11
  277. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +11 -11
  278. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +11 -11
  279. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -11
  280. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +11 -11
  281. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2174 -1226
  282. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +11 -11
  283. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +12 -12
  284. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +72 -27
  285. nautobot/project-static/docs/development/apps/api/configuration-view.html +11 -11
  286. nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -11
  287. nautobot/project-static/docs/development/apps/api/models/django-admin.html +11 -11
  288. nautobot/project-static/docs/development/apps/api/models/global-search.html +11 -11
  289. nautobot/project-static/docs/development/apps/api/models/graphql.html +11 -11
  290. nautobot/project-static/docs/development/apps/api/models/index.html +11 -11
  291. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +11 -11
  292. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +11 -11
  293. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +11 -11
  294. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +11 -11
  295. nautobot/project-static/docs/development/apps/api/platform-features/index.html +11 -11
  296. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -11
  297. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +11 -11
  298. nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +11 -11
  299. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +11 -11
  300. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +11 -11
  301. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +11 -11
  302. nautobot/project-static/docs/development/apps/api/prometheus.html +11 -11
  303. nautobot/project-static/docs/development/apps/api/setup.html +11 -11
  304. nautobot/project-static/docs/development/apps/api/testing.html +11 -11
  305. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +11 -11
  306. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +11 -11
  307. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +11 -11
  308. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +11 -11
  309. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +11 -11
  310. nautobot/project-static/docs/development/apps/api/views/base-template.html +11 -11
  311. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +11 -11
  312. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +11 -11
  313. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +11 -11
  314. nautobot/project-static/docs/development/apps/api/views/index.html +11 -11
  315. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +11 -11
  316. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +11 -11
  317. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +11 -11
  318. nautobot/project-static/docs/development/apps/api/views/notes.html +11 -11
  319. nautobot/project-static/docs/development/apps/api/views/rest-api.html +11 -11
  320. nautobot/project-static/docs/development/apps/api/views/urls.html +11 -11
  321. nautobot/project-static/docs/development/apps/index.html +11 -11
  322. nautobot/project-static/docs/development/apps/migration/code-updates.html +11 -11
  323. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +11 -11
  324. nautobot/project-static/docs/development/apps/migration/from-v1.html +11 -11
  325. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +11 -11
  326. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +11 -11
  327. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +11 -11
  328. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +11 -11
  329. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +11 -11
  330. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +11 -11
  331. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +11 -11
  332. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +11 -11
  333. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +11 -11
  334. nautobot/project-static/docs/development/apps/porting-from-netbox.html +11 -11
  335. nautobot/project-static/docs/development/core/application-registry.html +11 -11
  336. nautobot/project-static/docs/development/core/best-practices.html +11 -11
  337. nautobot/project-static/docs/development/core/bootstrap-ui.html +11 -11
  338. nautobot/project-static/docs/development/core/caching.html +11 -11
  339. nautobot/project-static/docs/development/core/controllers.html +11 -11
  340. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +11 -11
  341. nautobot/project-static/docs/development/core/generic-views.html +11 -11
  342. nautobot/project-static/docs/development/core/getting-started.html +50 -48
  343. nautobot/project-static/docs/development/core/homepage.html +11 -11
  344. nautobot/project-static/docs/development/core/index.html +11 -11
  345. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +11 -11
  346. nautobot/project-static/docs/development/core/model-checklist.html +11 -11
  347. nautobot/project-static/docs/development/core/model-features.html +11 -11
  348. nautobot/project-static/docs/development/core/natural-keys.html +11 -11
  349. nautobot/project-static/docs/development/core/navigation-menu.html +11 -11
  350. nautobot/project-static/docs/development/core/release-checklist.html +11 -11
  351. nautobot/project-static/docs/development/core/role-internals.html +11 -11
  352. nautobot/project-static/docs/development/core/settings.html +11 -11
  353. nautobot/project-static/docs/development/core/style-guide.html +15 -11
  354. nautobot/project-static/docs/development/core/templates.html +11 -11
  355. nautobot/project-static/docs/development/core/testing.html +11 -11
  356. nautobot/project-static/docs/development/core/ui-component-framework.html +11 -11
  357. nautobot/project-static/docs/development/core/user-preferences.html +11 -11
  358. nautobot/project-static/docs/development/index.html +11 -11
  359. nautobot/project-static/docs/development/jobs/getting-started.html +11 -11
  360. nautobot/project-static/docs/development/jobs/index.html +11 -11
  361. nautobot/project-static/docs/development/jobs/installation.html +11 -11
  362. nautobot/project-static/docs/development/jobs/job-extensions.html +11 -11
  363. nautobot/project-static/docs/development/jobs/job-logging.html +11 -11
  364. nautobot/project-static/docs/development/jobs/job-patterns.html +11 -11
  365. nautobot/project-static/docs/development/jobs/job-structure.html +11 -11
  366. nautobot/project-static/docs/development/jobs/migration/from-v1.html +11 -11
  367. nautobot/project-static/docs/development/jobs/testing.html +11 -11
  368. nautobot/project-static/docs/index.html +11 -11
  369. nautobot/project-static/docs/objects.inv +0 -0
  370. nautobot/project-static/docs/overview/application_stack.html +11 -11
  371. nautobot/project-static/docs/overview/design_philosophy.html +11 -11
  372. nautobot/project-static/docs/release-notes/index.html +11 -11
  373. nautobot/project-static/docs/release-notes/version-1.0.html +11 -11
  374. nautobot/project-static/docs/release-notes/version-1.1.html +11 -11
  375. nautobot/project-static/docs/release-notes/version-1.2.html +11 -11
  376. nautobot/project-static/docs/release-notes/version-1.3.html +11 -11
  377. nautobot/project-static/docs/release-notes/version-1.4.html +11 -11
  378. nautobot/project-static/docs/release-notes/version-1.5.html +11 -11
  379. nautobot/project-static/docs/release-notes/version-1.6.html +11 -11
  380. nautobot/project-static/docs/release-notes/version-2.0.html +11 -11
  381. nautobot/project-static/docs/release-notes/version-2.1.html +11 -11
  382. nautobot/project-static/docs/release-notes/version-2.2.html +11 -11
  383. nautobot/project-static/docs/release-notes/version-2.3.html +11 -11
  384. nautobot/project-static/docs/release-notes/version-2.4.html +196 -11
  385. nautobot/project-static/docs/search/search_index.json +1 -1
  386. nautobot/project-static/docs/sitemap.xml +300 -300
  387. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  388. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +11 -11
  389. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +11 -11
  390. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +11 -11
  391. nautobot/project-static/docs/user-guide/administration/configuration/index.html +11 -11
  392. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +11 -11
  393. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +11 -11
  394. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +11 -11
  395. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +89 -14
  396. nautobot/project-static/docs/user-guide/administration/guides/docker.html +11 -11
  397. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +11 -11
  398. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +11 -11
  399. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +11 -11
  400. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +11 -11
  401. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +11 -11
  402. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +11 -11
  403. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +11 -11
  404. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +11 -11
  405. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +11 -11
  406. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +11 -11
  407. nautobot/project-static/docs/user-guide/administration/installation/index.html +11 -11
  408. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +11 -11
  409. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +11 -11
  410. nautobot/project-static/docs/user-guide/administration/installation/services.html +11 -11
  411. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +11 -11
  412. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +11 -11
  413. nautobot/project-static/docs/user-guide/administration/security/index.html +11 -11
  414. nautobot/project-static/docs/user-guide/administration/security/notices.html +11 -11
  415. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +11 -11
  416. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +11 -11
  417. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +11 -11
  418. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +11 -11
  419. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +11 -11
  420. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +11 -11
  421. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +11 -11
  422. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +11 -11
  423. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +11 -11
  424. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +11 -11
  425. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +11 -11
  426. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +11 -11
  427. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +11 -11
  428. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +11 -11
  429. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +11 -11
  430. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +11 -11
  431. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +11 -11
  432. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +11 -11
  433. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +11 -11
  434. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +11 -11
  435. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +11 -11
  436. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +11 -11
  437. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +11 -11
  438. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +11 -11
  439. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +11 -11
  440. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +11 -11
  441. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +11 -11
  442. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +11 -11
  443. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +11 -11
  444. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +11 -11
  445. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +11 -11
  446. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +11 -11
  447. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +11 -11
  448. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +11 -11
  449. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +11 -11
  450. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +11 -11
  451. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +11 -11
  452. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +11 -11
  453. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +11 -11
  454. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +11 -11
  455. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +11 -11
  456. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +11 -11
  457. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +11 -11
  458. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +11 -11
  459. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +11 -11
  460. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +11 -11
  461. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +11 -11
  462. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +11 -11
  463. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +11 -11
  464. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +11 -11
  465. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +11 -11
  466. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +11 -11
  467. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +11 -11
  468. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +11 -11
  469. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +11 -11
  470. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +11 -11
  471. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +11 -11
  472. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +11 -11
  473. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +11 -11
  474. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +11 -11
  475. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +11 -11
  476. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +11 -11
  477. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +11 -11
  478. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +11 -11
  479. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +11 -11
  480. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +11 -11
  481. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +11 -11
  482. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +11 -11
  483. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -11
  484. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -11
  485. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +11 -11
  486. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +11 -11
  487. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +11 -11
  488. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +11 -11
  489. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +11 -11
  490. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +11 -11
  491. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +11 -11
  492. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +11 -11
  493. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +11 -11
  494. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +11 -11
  495. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +11 -11
  496. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +11 -11
  497. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +11 -11
  498. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +11 -11
  499. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +11 -11
  500. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +11 -11
  501. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +11 -11
  502. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +11 -11
  503. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +11 -11
  504. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +11 -11
  505. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +11 -11
  506. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -11
  507. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +11 -11
  508. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +11 -11
  509. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +11 -11
  510. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +11 -11
  511. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +11 -11
  512. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +11 -11
  513. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +11 -11
  514. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +11 -11
  515. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +11 -11
  516. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +11 -11
  517. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +11 -11
  518. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +11 -11
  519. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +11 -11
  520. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +11 -11
  521. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +11 -11
  522. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +11 -11
  523. nautobot/project-static/docs/user-guide/index.html +11 -11
  524. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +11 -11
  525. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +11 -11
  526. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +11 -11
  527. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +11 -11
  528. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +11 -11
  529. nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -11
  530. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +11 -11
  531. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +11 -11
  532. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +11 -11
  533. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +11 -11
  534. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +11 -11
  535. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +11 -11
  536. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +11 -11
  537. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +11 -11
  538. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +11 -11
  539. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +11 -11
  540. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +11 -11
  541. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +11 -11
  542. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +11 -11
  543. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +11 -11
  544. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +11 -11
  545. nautobot/project-static/docs/user-guide/platform-functionality/note.html +11 -11
  546. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +11 -11
  547. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +11 -11
  548. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +11 -11
  549. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +11 -11
  550. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +11 -11
  551. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +11 -11
  552. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +11 -11
  553. nautobot/project-static/docs/user-guide/platform-functionality/role.html +11 -11
  554. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +11 -11
  555. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -11
  556. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +11 -11
  557. nautobot/project-static/docs/user-guide/platform-functionality/status.html +11 -11
  558. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +11 -11
  559. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +11 -11
  560. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +11 -11
  561. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +11 -11
  562. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +11 -11
  563. nautobot/tenancy/templates/tenancy/tenant.html +1 -7
  564. nautobot/tenancy/views.py +13 -0
  565. nautobot/users/templates/users/api_tokens.html +4 -4
  566. nautobot/users/templates/users/base.html +28 -28
  567. nautobot/virtualization/templates/virtualization/cluster.html +64 -64
  568. nautobot/virtualization/templates/virtualization/inc/virtualmachine_vminterface_filter.html +8 -0
  569. nautobot/virtualization/templates/virtualization/virtualmachine_component_add.html +25 -25
  570. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +1 -251
  571. nautobot/virtualization/templates/virtualization/vminterface.html +70 -70
  572. nautobot/virtualization/urls.py +0 -12
  573. nautobot/virtualization/views.py +158 -54
  574. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +13 -13
  575. nautobot/wireless/tests/integration/test_radio_profile.py +1 -1
  576. {nautobot-2.4.18.dist-info → nautobot-2.4.19.dist-info}/METADATA +1 -1
  577. {nautobot-2.4.18.dist-info → nautobot-2.4.19.dist-info}/RECORD +581 -570
  578. nautobot/ipam/templates/ipam/prefix_ipaddresses.html +0 -11
  579. nautobot/ipam/templates/ipam/prefix_prefixes.html +0 -11
  580. {nautobot-2.4.18.dist-info → nautobot-2.4.19.dist-info}/LICENSE.txt +0 -0
  581. {nautobot-2.4.18.dist-info → nautobot-2.4.19.dist-info}/NOTICE +0 -0
  582. {nautobot-2.4.18.dist-info → nautobot-2.4.19.dist-info}/WHEEL +0 -0
  583. {nautobot-2.4.18.dist-info → nautobot-2.4.19.dist-info}/entry_points.txt +0 -0
@@ -43,9 +43,11 @@ from nautobot.core.utils.lookup import get_filterset_for_model, get_route_for_mo
43
43
  from nautobot.core.utils.permissions import get_permission_for_model
44
44
  from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
45
45
  from nautobot.core.views.utils import get_obj_from_context
46
+ from nautobot.dcim.models import Rack
46
47
  from nautobot.extras.choices import CustomFieldTypeChoices
47
48
  from nautobot.extras.tables import AssociatedContactsTable, DynamicGroupTable, ObjectMetadataTable
48
49
  from nautobot.tenancy.models import Tenant
50
+ from nautobot.virtualization.models import Cluster
49
51
 
50
52
  logger = logging.getLogger(__name__)
51
53
 
@@ -123,16 +125,24 @@ class ObjectDetailContent:
123
125
  class Component:
124
126
  """Common base class for renderable components (tabs, panels, etc.)."""
125
127
 
126
- def __init__(self, *, weight):
128
+ def __init__(
129
+ self,
130
+ *,
131
+ weight,
132
+ required_permissions=None,
133
+ ):
127
134
  """Initialize common Component properties.
128
135
 
129
136
  Args:
130
137
  weight (int): A relative weighting of this Component relative to its peers. Typically lower weights will be
131
138
  rendered "first", usually towards the top left of the page.
139
+ required_permissions (list, optional): Permissions such as `["dcim.add_consoleport"]`.
140
+ The component will only be rendered if the user has these permissions.
132
141
  """
133
142
  self.weight = weight
143
+ self.required_permissions = required_permissions or []
134
144
 
135
- def should_render(self, context: dict):
145
+ def should_render(self, context: Context):
136
146
  """
137
147
  Check whether this component should be rendered at all.
138
148
 
@@ -140,9 +150,13 @@ class Component:
140
150
  In general most Components may also return an empty string when actually rendered, which is typically also a
141
151
  means to specify that they do not need to be rendered, but may be more expensive to derive.
142
152
 
153
+ The default implementation here checks for `self.required_permissions` if any.
154
+
143
155
  Returns:
144
156
  (bool): `True` (default) if this component should be rendered.
145
157
  """
158
+ if self.required_permissions:
159
+ return context["request"].user.has_perms(self.required_permissions)
146
160
  return True
147
161
 
148
162
  def render(self, context: Context):
@@ -178,12 +192,12 @@ class Button(Component):
178
192
  link_name=None,
179
193
  icon=None,
180
194
  template_path="components/button/default.html",
181
- required_permissions=None,
182
195
  javascript_template_path=None,
183
196
  attributes=None,
184
197
  size=None,
185
198
  link_includes_pk=True,
186
199
  context_object_key=None,
200
+ render_on_tab_id="main",
187
201
  **kwargs,
188
202
  ):
189
203
  """
@@ -199,30 +213,25 @@ class Button(Component):
199
213
  context_object_key (str, optional): The key in the render context that will contain the linked object.
200
214
  icon (str, optional): Material Design Icons icon, to include on the button, for example `"mdi-plus-bold"`.
201
215
  template_path (str): Template to render for this button.
202
- required_permissions (list, optional): Permissions such as `["dcim.add_consoleport"]`.
203
- The button will only be rendered if the user has these permissions.
204
216
  javascript_template_path (str, optional): JavaScript template to render and include with this button.
205
217
  Does not need to include the wrapping `<script>...</script>` tags as those will be added automatically.
206
218
  attributes (dict, optional): Additional HTML attributes and their values to attach to the button.
207
219
  size (str, optional): The size of the button (e.g. `xs` or `sm`), used to apply a Bootstrap-style sizing.
220
+ render_on_tab_id (str, optional): The (only) tab that this button should appear on.
208
221
  """
209
222
  self.label = label
210
223
  self.color = color
211
224
  self.link_name = link_name
212
225
  self.icon = icon
213
226
  self.template_path = template_path
214
- self.required_permissions = required_permissions or []
215
227
  self.javascript_template_path = javascript_template_path
216
228
  self.attributes = attributes
217
229
  self.size = size
218
230
  self.link_includes_pk = link_includes_pk
219
231
  self.context_object_key = context_object_key
232
+ self.render_on_tab_id = render_on_tab_id
220
233
  super().__init__(**kwargs)
221
234
 
222
- def should_render(self, context: Context):
223
- """Render if and only if the requesting user has appropriate permissions (if any)."""
224
- return context["request"].user.has_perms(self.required_permissions)
225
-
226
235
  def get_link(self, context: Context):
227
236
  """
228
237
  Get the hyperlink URL (if any) for this button.
@@ -248,6 +257,11 @@ class Button(Component):
248
257
  "size": self.size,
249
258
  }
250
259
 
260
+ def should_render(self, context: Context):
261
+ if not super().should_render(context):
262
+ return False
263
+ return context.get("active_tab", "main") == self.render_on_tab_id
264
+
251
265
  def render(self, context: Context):
252
266
  """Render this button to HTML, possibly including any associated JavaScript."""
253
267
  if not self.should_render(context):
@@ -399,9 +413,18 @@ class Tab(Component):
399
413
  """
400
414
  return self.label
401
415
 
416
+ def should_render_content(self, context: Context):
417
+ """
418
+ Only render a main-view Tab if the active request is for the main object view rather than a separate action.
419
+ """
420
+ request = context["request"]
421
+ obj = get_obj_from_context(context)
422
+ return request.path == obj.get_absolute_url()
423
+
402
424
  def render(self, context: Context):
403
425
  """Render the tab's contents (layout and panels) to HTML."""
404
- if not self.should_render(context):
426
+ # Check should_render_content first as it's generally a cheaper calculation than should_render checking perms
427
+ if not self.should_render_content(context) or not self.should_render(context):
405
428
  return ""
406
429
 
407
430
  with context.update(
@@ -427,6 +450,7 @@ class DistinctViewTab(Tab):
427
450
  url_name (str): The name of the URL pattern to link to, which will be reversed to generate the URL.
428
451
  label_wrapper_template_path (str, optional): Template path to render the tab label to HTML.
429
452
  related_object_attribute (str, optional): The name of the related object attribute to count for the tab label.
453
+ hide_if_empty (bool, optional): Do not render this tab if the related_object_attribute is an empty queryset.
430
454
  """
431
455
 
432
456
  def __init__(
@@ -435,39 +459,62 @@ class DistinctViewTab(Tab):
435
459
  url_name,
436
460
  label_wrapper_template_path="components/tab/label_wrapper_distinct_view.html",
437
461
  related_object_attribute="",
462
+ hide_if_empty=False,
438
463
  **kwargs,
439
464
  ):
440
465
  self.url_name = url_name
441
466
  self.related_object_attribute = related_object_attribute
467
+ self.hide_if_empty = hide_if_empty
442
468
  super().__init__(label_wrapper_template_path=label_wrapper_template_path, **kwargs)
443
469
 
444
470
  def get_extra_context(self, context: Context):
445
471
  return {"url": reverse(self.url_name, kwargs={"pk": get_obj_from_context(context).pk})}
446
472
 
447
- def render_label(self, context: Context):
473
+ def should_render(self, context: Context):
474
+ if not super().should_render(context):
475
+ return False
476
+
477
+ self.related_object_count = None
448
478
  if not self.related_object_attribute:
449
- return super().render_label(context)
479
+ return True
450
480
 
451
481
  obj = get_obj_from_context(context)
452
482
  if not hasattr(obj, self.related_object_attribute):
453
483
  logger.warning(
454
484
  f"{obj} does not have a related attribute {self.related_object_attribute} to count for tab label."
455
485
  )
456
- return super().render_label(context)
486
+ return True
457
487
 
458
488
  try:
459
- related_obj_count = getattr(obj, self.related_object_attribute).count()
460
- return format_html(
461
- "{} {}",
462
- self.label,
463
- render_to_string("utilities/templatetags/badge.html", badge(related_obj_count, True)),
464
- )
489
+ self.related_object_count = getattr(obj, self.related_object_attribute).count()
465
490
  except AttributeError:
466
- logger.warning(
491
+ # Not a warning log, as there are cases where this is expected.
492
+ logger.debug(
467
493
  f"{obj}'s attribute {self.related_object_attribute} is not a related manager to count for tab label."
468
494
  )
495
+
496
+ if self.hide_if_empty and not self.related_object_count:
497
+ return False
498
+ return True
499
+
500
+ def should_render_content(self, context: Context):
501
+ """
502
+ A DistinctViewTab should only render its content if the view in question is active.
503
+ """
504
+ with context.update(self.get_extra_context(context)):
505
+ request = context["request"]
506
+ return request.path == context["url"]
507
+
508
+ def render_label(self, context: Context):
509
+ if not self.related_object_attribute or not self.related_object_count:
469
510
  return super().render_label(context)
470
511
 
512
+ return format_html(
513
+ "{} {}",
514
+ self.label,
515
+ render_to_string("utilities/templatetags/badge.html", badge(self.related_object_count, True)),
516
+ )
517
+
471
518
 
472
519
  class Panel(Component):
473
520
  """Base class for defining an individual display panel within a Layout within a Tab."""
@@ -607,7 +654,7 @@ class DataTablePanel(Panel):
607
654
  **kwargs,
608
655
  ):
609
656
  """
610
- Instantiate a DataDictTablePanel.
657
+ Instantiate a DataTablePanel.
611
658
 
612
659
  Args:
613
660
  context_data_key (str): The key in the render context that stores the data used to populate the table.
@@ -684,6 +731,7 @@ class ObjectsTablePanel(Panel):
684
731
  table_class=None,
685
732
  table_filter=None,
686
733
  table_attribute=None,
734
+ distinct=False,
687
735
  select_related_fields=None,
688
736
  prefetch_related_fields=None,
689
737
  order_by_fields=None,
@@ -726,6 +774,7 @@ class ObjectsTablePanel(Panel):
726
774
  table_attribute (str, optional): The attribute of the detail view instance that contains the queryset to
727
775
  initialize the table class. e.g. `dynamic_groups`.
728
776
  Mutually exclusive with `table_filter`.
777
+ distinct (bool, optional): If True, apply `.distinct()` to the table queryset.
729
778
  select_related_fields (list, optional): list of fields to pass to table queryset's `select_related` method.
730
779
  prefetch_related_fields (list, optional): list of fields to pass to table queryset's `prefetch_related`
731
780
  method.
@@ -761,6 +810,7 @@ class ObjectsTablePanel(Panel):
761
810
  table_class,
762
811
  table_filter,
763
812
  table_attribute,
813
+ distinct,
764
814
  select_related_fields,
765
815
  prefetch_related_fields,
766
816
  order_by_fields,
@@ -769,8 +819,8 @@ class ObjectsTablePanel(Panel):
769
819
  ):
770
820
  raise ValueError(
771
821
  "context_table_key cannot be combined with any of the args that are used to dynamically construct the "
772
- "table (table_class, table_filter, table_attribute, select_related_fields, prefetch_related_fields, "
773
- "order_by_fields, hide_hierarchy_ui)."
822
+ "table (table_class, table_filter, table_attribute, distinct, select_related_fields, "
823
+ "prefetch_related_fields, order_by_fields, hide_hierarchy_ui)."
774
824
  )
775
825
  self.context_table_key = context_table_key
776
826
  self.table_class = table_class
@@ -782,6 +832,7 @@ class ObjectsTablePanel(Panel):
782
832
  raise ValueError("You must provide a `related_field_name` when specifying `table_attribute`")
783
833
  self.table_filter = table_filter
784
834
  self.table_attribute = table_attribute
835
+ self.distinct = distinct
785
836
  self.select_related_fields = select_related_fields
786
837
  self.prefetch_related_fields = prefetch_related_fields
787
838
  self.order_by_fields = order_by_fields
@@ -792,7 +843,7 @@ class ObjectsTablePanel(Panel):
792
843
  self.include_columns = include_columns
793
844
  self.exclude_columns = exclude_columns
794
845
  self.add_button_route = add_button_route
795
- self.add_permissions = add_permissions
846
+ self.add_permissions = add_permissions or []
796
847
  self.hide_hierarchy_ui = hide_hierarchy_ui
797
848
  self.related_field_name = related_field_name
798
849
  self.enable_bulk_actions = enable_bulk_actions
@@ -823,21 +874,26 @@ class ObjectsTablePanel(Panel):
823
874
  if self.tab_id:
824
875
  return_url += f"?tab={self.tab_id}"
825
876
 
826
- if self.add_button_route == "default":
827
- body_content_table_class = self.table_class or context[self.context_table_key].__class__
828
- body_content_table_model = body_content_table_class.Meta.model
829
- permission_name = get_permission_for_model(body_content_table_model, "add")
830
- if request.user.has_perms([permission_name]):
831
- try:
832
- add_route = reverse(get_route_for_model(body_content_table_model, "add"))
833
- body_content_table_add_url = f"{add_route}?{related_field_name}={obj.pk}&return_url={return_url}"
834
- except NoReverseMatch:
835
- logger.warning("add route for `body_content_table_model` not found")
877
+ if self.add_button_route is not None:
878
+ add_permissions = self.add_permissions
879
+ if not add_permissions:
880
+ body_content_table_class = self.table_class or context[self.context_table_key].__class__
881
+ body_content_table_model = body_content_table_class.Meta.model
882
+ add_permissions = [get_permission_for_model(body_content_table_model, "add")]
836
883
 
837
- elif self.add_button_route is not None:
838
- if request.user.has_perms(self.add_permissions or []):
839
- add_route = reverse(self.add_button_route)
840
- body_content_table_add_url = f"{add_route}?{related_field_name}={obj.pk}&return_url={return_url}"
884
+ if self.add_button_route == "default":
885
+ if request.user.has_perms(add_permissions):
886
+ try:
887
+ add_route = reverse(get_route_for_model(body_content_table_model, "add"))
888
+ body_content_table_add_url = (
889
+ f"{add_route}?{related_field_name}={obj.pk}&return_url={return_url}"
890
+ )
891
+ except NoReverseMatch:
892
+ logger.warning("add route for `body_content_table_model` not found")
893
+ else:
894
+ if request.user.has_perms(add_permissions):
895
+ add_route = reverse(self.add_button_route)
896
+ body_content_table_add_url = f"{add_route}?{related_field_name}={obj.pk}&return_url={return_url}"
841
897
 
842
898
  return body_content_table_add_url
843
899
 
@@ -880,7 +936,8 @@ class ObjectsTablePanel(Panel):
880
936
  )
881
937
  if self.order_by_fields:
882
938
  body_content_table_queryset = body_content_table_queryset.order_by(*self.order_by_fields)
883
- body_content_table_queryset = body_content_table_queryset.distinct()
939
+ if self.distinct:
940
+ body_content_table_queryset = body_content_table_queryset.distinct()
884
941
  body_content_table = body_content_table_class(
885
942
  body_content_table_queryset, hide_hierarchy_ui=self.hide_hierarchy_ui, user=request.user
886
943
  )
@@ -1004,6 +1061,8 @@ class KeyValueTablePanel(Panel):
1004
1061
  super().__init__(body_wrapper_template_path=body_wrapper_template_path, **kwargs)
1005
1062
 
1006
1063
  def should_render(self, context: Context):
1064
+ if not super().should_render(context):
1065
+ return False
1007
1066
  return bool(self.get_data(context))
1008
1067
 
1009
1068
  def get_data(self, context: Context):
@@ -1093,6 +1152,10 @@ class KeyValueTablePanel(Panel):
1093
1152
  elif isinstance(value, models.Model):
1094
1153
  if hasattr(value, "color"):
1095
1154
  display = hyperlinked_object_with_color(value)
1155
+ elif isinstance(value, Cluster) and value.cluster_group is not None:
1156
+ display = format_html("{} / {}", hyperlinked_object(value.cluster_group), hyperlinked_object(value))
1157
+ elif isinstance(value, Rack) and value.rack_group is not None:
1158
+ display = format_html("{} / {}", hyperlinked_object(value.rack_group), hyperlinked_object(value))
1096
1159
  elif isinstance(value, Tenant) and value.tenant_group is not None:
1097
1160
  display = format_html("{} / {}", hyperlinked_object(value.tenant_group), hyperlinked_object(value))
1098
1161
  # TODO: render location hierarchy for Location objects
@@ -1317,6 +1380,8 @@ class ObjectFieldsPanel(KeyValueTablePanel):
1317
1380
  for token in field_name.split(LOOKUP_SEP):
1318
1381
  try:
1319
1382
  field_value = getattr(field_value, token)
1383
+ if field_value is None:
1384
+ break
1320
1385
  except ObjectDoesNotExist:
1321
1386
  field_value = None
1322
1387
  break
@@ -1610,7 +1675,7 @@ class _ObjectCustomFieldsPanel(GroupedKeyValueTablePanel):
1610
1675
  if not hasattr(obj, "get_custom_field_groupings"):
1611
1676
  return False
1612
1677
  self.custom_field_data = obj.get_custom_field_groupings(advanced_ui=self.advanced_ui)
1613
- return bool(self.custom_field_data)
1678
+ return bool(self.custom_field_data) and super().should_render(context)
1614
1679
 
1615
1680
  def get_data(self, context: Context):
1616
1681
  """Remap the response from `get_custom_field_groupings()` to a nested dict as expected by the parent class."""
@@ -1686,7 +1751,7 @@ class _ObjectComputedFieldsPanel(GroupedKeyValueTablePanel):
1686
1751
  if not hasattr(obj, "get_computed_fields_grouping"):
1687
1752
  return False
1688
1753
  self.computed_fields_data = obj.get_computed_fields_grouping(advanced_ui=self.advanced_ui)
1689
- return bool(self.computed_fields_data)
1754
+ return bool(self.computed_fields_data) and super().should_render(context)
1690
1755
 
1691
1756
  def get_data(self, context: Context):
1692
1757
  """Remap `get_computed_fields_grouping()` to the nested dict format expected by the base class."""
@@ -1732,7 +1797,7 @@ class _ObjectRelationshipsPanel(KeyValueTablePanel):
1732
1797
  self.relationships_data["source"]
1733
1798
  or self.relationships_data["destination"]
1734
1799
  or self.relationships_data["peer"]
1735
- )
1800
+ ) and super().should_render(context)
1736
1801
 
1737
1802
  def get_data(self, context: Context):
1738
1803
  """Remap `get_relationships_with_related_objects()` to the flat dict format expected by the base class."""
@@ -1783,6 +1848,8 @@ class _ObjectTagsPanel(Panel):
1783
1848
  )
1784
1849
 
1785
1850
  def should_render(self, context: Context):
1851
+ if not super().should_render(context):
1852
+ return False
1786
1853
  return hasattr(get_obj_from_context(context), "tags")
1787
1854
 
1788
1855
  def get_extra_context(self, context: Context):
@@ -1814,6 +1881,8 @@ class _ObjectCommentPanel(ObjectTextPanel):
1814
1881
  )
1815
1882
 
1816
1883
  def should_render(self, context: Context):
1884
+ if not super().should_render(context):
1885
+ return False
1817
1886
  return hasattr(get_obj_from_context(context), "comments")
1818
1887
 
1819
1888
 
@@ -1947,6 +2016,8 @@ class _ObjectDetailContactsTab(Tab):
1947
2016
  super().__init__(tab_id=tab_id, label=label, weight=weight, panels=panels, **kwargs)
1948
2017
 
1949
2018
  def should_render(self, context: Context):
2019
+ if not super().should_render(context):
2020
+ return False
1950
2021
  return getattr(get_obj_from_context(context), "is_contact_associable_model", False)
1951
2022
 
1952
2023
  def render_label(self, context: Context):
@@ -1971,6 +2042,7 @@ class _ObjectDetailGroupsTab(Tab):
1971
2042
  label="Dynamic Groups",
1972
2043
  weight=Tab.WEIGHT_GROUPS_TAB,
1973
2044
  panels=None,
2045
+ required_permissions=("extras.view_dynamic_group",),
1974
2046
  **kwargs,
1975
2047
  ):
1976
2048
  if panels is None:
@@ -1984,15 +2056,20 @@ class _ObjectDetailGroupsTab(Tab):
1984
2056
  related_field_name="member_id",
1985
2057
  ),
1986
2058
  )
1987
- super().__init__(tab_id=tab_id, label=label, weight=weight, panels=panels, **kwargs)
2059
+ super().__init__(
2060
+ tab_id=tab_id,
2061
+ label=label,
2062
+ weight=weight,
2063
+ panels=panels,
2064
+ required_permissions=required_permissions,
2065
+ **kwargs,
2066
+ )
1988
2067
 
1989
2068
  def should_render(self, context: Context):
2069
+ if not super().should_render(context):
2070
+ return False
1990
2071
  obj = get_obj_from_context(context)
1991
- return (
1992
- getattr(obj, "is_dynamic_group_associable_model", False)
1993
- and context["request"].user.has_perm("extras.view_dynamicgroup")
1994
- and obj.dynamic_groups.exists()
1995
- )
2072
+ return getattr(obj, "is_dynamic_group_associable_model", False) and obj.dynamic_groups.exists()
1996
2073
 
1997
2074
  def render_label(self, context: Context):
1998
2075
  return format_html(
@@ -2015,6 +2092,7 @@ class _ObjectDetailMetadataTab(Tab):
2015
2092
  label="Object Metadata",
2016
2093
  weight=Tab.WEIGHT_METADATA_TAB,
2017
2094
  panels=None,
2095
+ required_permissions=("extras.view_objectmetadata",),
2018
2096
  **kwargs,
2019
2097
  ):
2020
2098
  if panels is None:
@@ -2030,15 +2108,20 @@ class _ObjectDetailMetadataTab(Tab):
2030
2108
  header_extra_content_template_path=None,
2031
2109
  ),
2032
2110
  )
2033
- super().__init__(tab_id=tab_id, label=label, weight=weight, panels=panels, **kwargs)
2111
+ super().__init__(
2112
+ tab_id=tab_id,
2113
+ label=label,
2114
+ weight=weight,
2115
+ panels=panels,
2116
+ required_permissions=required_permissions,
2117
+ **kwargs,
2118
+ )
2034
2119
 
2035
2120
  def should_render(self, context: Context):
2121
+ if not super().should_render(context):
2122
+ return False
2036
2123
  obj = get_obj_from_context(context)
2037
- return (
2038
- getattr(obj, "is_metadata_associable_model", False)
2039
- and context["request"].user.has_perm("extras.view_objectmetadata")
2040
- and obj.associated_object_metadata.exists()
2041
- )
2124
+ return getattr(obj, "is_metadata_associable_model", False) and obj.associated_object_metadata.exists()
2042
2125
 
2043
2126
  def render_label(self, context: Context):
2044
2127
  return format_html(
@@ -15,6 +15,7 @@ DEFAULT_TITLES: dict[str, str] = {
15
15
  "bulk_rename": "Renaming {{ selected_objects|length }} {{ verbose_name_plural|bettertitle }} on {{ parent_name }}",
16
16
  "bulk_update": "Editing {{ objs_count }} {{ verbose_name_plural|bettertitle }}",
17
17
  "changelog": "{{ object.display|default:object }} - Change Log",
18
+ "config_context": "{{ object.display|default:object }} - Config Context",
18
19
  "notes": "{{ object.display|default:object }} - Notes",
19
20
  "approve": "Approve {{ verbose_name|bettertitle }}?",
20
21
  "deny": "Deny {{ verbose_name|bettertitle }}?",
@@ -156,7 +156,7 @@ def normalize_querydict(querydict, form_class=None, filterset=None):
156
156
  does not inherently preserve multiple values as lists.
157
157
 
158
158
  Args:
159
- querydict (QueryDict): The QueryDict to be normalized.
159
+ querydict (QueryDict): The QueryDict or dictionary as produced by `convert_querydict_to_dict` to be normalized.
160
160
  form_class (forms.Form, optional): A form class to identify fields that should be treated as
161
161
  lists (e.g., `MultipleChoiceField` or `ModelMultipleChoiceField`).
162
162
  filterset (django_filters.FilterSet, optional): A FilterSet instance to identify filters that
@@ -171,7 +171,13 @@ def normalize_querydict(querydict, form_class=None, filterset=None):
171
171
  result = {}
172
172
 
173
173
  if querydict:
174
- for key, value_list in querydict.lists():
174
+ # check if true QueryDict or standard dict in format of querydict, e.g. from `convert_querydict_to_dict`
175
+ if hasattr(querydict, "lists"):
176
+ items = querydict.lists()
177
+ else:
178
+ items = querydict.items()
179
+
180
+ for key, value_list in items:
175
181
  if len(value_list) > 1:
176
182
  # More than one value in the querydict for this key, so keep it as a list
177
183
  # TODO: we could check here and de-listify value_list if the form_class field is a single-value one?
@@ -198,3 +204,22 @@ def add_nautobot_version_query_param_to_url(url):
198
204
  params["version"] = settings.VERSION
199
205
  updated_query = urlencode(params, doseq=True)
200
206
  return parsed_url._replace(query=updated_query).geturl()
207
+
208
+
209
+ def convert_querydict_to_dict(request_querydict):
210
+ """
211
+ Convert QueryDict to standard json serializable dictionary.
212
+
213
+ This is useful when you want to serialize a QueryDict to JSON format such as
214
+ when sending to a Job form or sending it over an API. This is not the same as
215
+ `normalize_querydict` which preserves single values as singletons and multi-values
216
+ as lists. This function preserves all values as lists.
217
+
218
+ Args:
219
+ request_querydict (QueryDict): QueryDict to convert.
220
+
221
+ Examples:
222
+ >>> convert_querydict_to_dict(QueryDict('foo=1&bar=2&bar=3&baz='))
223
+ >>> {'foo': ['1'], 'bar': ['2', '3'], 'baz': ['']}
224
+ """
225
+ return {key: value for key, value in request_querydict.lists()} # pylint: disable=unnecessary-comprehension