nautobot 2.4.17__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 (627) hide show
  1. nautobot/apps/ui.py +6 -0
  2. nautobot/apps/views.py +2 -0
  3. nautobot/circuits/tables.py +1 -1
  4. nautobot/circuits/templates/circuits/circuit_create.html +7 -7
  5. nautobot/circuits/templates/circuits/circuit_retrieve.html +1 -5
  6. nautobot/circuits/templates/circuits/circuittermination_create.html +26 -26
  7. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -8
  8. nautobot/circuits/templates/circuits/inc/circuit_termination.html +20 -20
  9. nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +3 -3
  10. nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +9 -0
  11. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +1 -3
  12. nautobot/circuits/tests/integration/test_circuit.py +2 -2
  13. nautobot/circuits/views.py +49 -15
  14. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +1 -4
  15. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +1 -7
  16. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +1 -3
  17. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +1 -5
  18. nautobot/cloud/views.py +45 -0
  19. nautobot/core/filters.py +2 -2
  20. nautobot/core/graphql/generators.py +5 -2
  21. nautobot/core/jobs/bulk_actions.py +48 -85
  22. nautobot/core/models/querysets.py +2 -1
  23. nautobot/core/settings.py +1 -0
  24. nautobot/core/settings.yaml +9 -0
  25. nautobot/core/tables.py +21 -23
  26. nautobot/core/templates/40x.html +15 -15
  27. nautobot/core/templates/500.html +21 -21
  28. nautobot/core/templates/admin/app_index.html +8 -8
  29. nautobot/core/templates/admin/base.html +104 -104
  30. nautobot/core/templates/admin/change_form.html +65 -65
  31. nautobot/core/templates/admin/change_list.html +60 -60
  32. nautobot/core/templates/admin/change_list_results.html +39 -39
  33. nautobot/core/templates/admin/config/config.html +47 -47
  34. nautobot/core/templates/admin/delete_confirmation.html +47 -47
  35. nautobot/core/templates/admin/edit_inline/stacked.html +124 -124
  36. nautobot/core/templates/admin/edit_inline/tabular.html +60 -60
  37. nautobot/core/templates/admin/includes/fieldset.html +4 -4
  38. nautobot/core/templates/admin/index.html +60 -60
  39. nautobot/core/templates/admin/prepopulated_fields_js.html +18 -18
  40. nautobot/core/templates/admin/submit_line.html +4 -4
  41. nautobot/core/templates/base_django.html +46 -46
  42. nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +8 -8
  43. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +8 -8
  44. nautobot/core/templates/buttons/export.html +3 -3
  45. nautobot/core/templates/components/breadcrumbs.html +19 -0
  46. nautobot/core/templates/components/button/default.html +3 -3
  47. nautobot/core/templates/components/button/dropdown.html +7 -7
  48. nautobot/core/templates/components/button/formbutton.html +4 -4
  49. nautobot/core/templates/components/panel/body_content_data_table.html +1 -1
  50. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +3 -0
  51. nautobot/core/templates/components/panel/footer_content_table.html +3 -1
  52. nautobot/core/templates/components/panel/header_extra_content_table.html +10 -1
  53. nautobot/core/templates/components/tab/content_wrapper.html +1 -1
  54. nautobot/core/templates/components/tab/label_wrapper.html +1 -1
  55. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +10 -3
  56. nautobot/core/templates/generic/object_bulk_add_component.html +40 -40
  57. nautobot/core/templates/generic/object_bulk_create.html +3 -3
  58. nautobot/core/templates/generic/object_bulk_destroy.html +6 -6
  59. nautobot/core/templates/generic/object_bulk_update.html +52 -52
  60. nautobot/core/templates/generic/object_changelog.html +0 -2
  61. nautobot/core/templates/generic/object_import.html +33 -33
  62. nautobot/core/templates/generic/object_list.html +271 -268
  63. nautobot/core/templates/generic/object_notes.html +0 -2
  64. nautobot/core/templates/generic/object_retrieve.html +264 -257
  65. nautobot/core/templates/graphene/graphiql.html +127 -127
  66. nautobot/core/templates/home.html +62 -62
  67. nautobot/core/templates/inc/computed_fields/panel_data.html +13 -13
  68. nautobot/core/templates/inc/created_updated.html +8 -8
  69. nautobot/core/templates/inc/custom_fields/panel_data.html +13 -13
  70. nautobot/core/templates/inc/dynamic_groups_panel.html +11 -11
  71. nautobot/core/templates/inc/footer.html +19 -19
  72. nautobot/core/templates/inc/javascript.html +1 -1
  73. nautobot/core/templates/inc/media.html +46 -46
  74. nautobot/core/templates/inc/nav_menu.html +1 -1
  75. nautobot/core/templates/inc/relationships_table_rows.html +22 -22
  76. nautobot/core/templates/inc/tenant_table_row.html +1 -1
  77. nautobot/core/templates/login.html +77 -77
  78. nautobot/core/templates/media_failure.html +38 -38
  79. nautobot/core/templates/panel_table.html +1 -1
  80. nautobot/core/templates/rest_framework/api.html +3 -3
  81. nautobot/core/templates/search.html +1 -1
  82. nautobot/core/templates/swagger_ui.html +9 -9
  83. nautobot/core/templates/utilities/confirmation_form.html +18 -18
  84. nautobot/core/templates/utilities/render_field.html +1 -1
  85. nautobot/core/templates/utilities/render_jinja2.html +43 -43
  86. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +56 -56
  87. nautobot/core/templates/utilities/templatetags/utilization_graph.html +1 -1
  88. nautobot/core/templates/utilities/theme_preview.html +799 -799
  89. nautobot/core/templates/utilities/worker_status.html +122 -122
  90. nautobot/core/templates/widgets/clearable_file.html +3 -3
  91. nautobot/core/templates/widgets/sluginput.html +1 -1
  92. nautobot/core/templatetags/buttons.py +8 -2
  93. nautobot/core/templatetags/helpers.py +24 -0
  94. nautobot/core/templatetags/ui_framework.py +40 -5
  95. nautobot/core/testing/filters.py +37 -21
  96. nautobot/core/testing/integration.py +7 -4
  97. nautobot/core/testing/views.py +49 -5
  98. nautobot/core/tests/test_breadcrumbs.py +78 -4
  99. nautobot/core/tests/test_commands.py +7 -4
  100. nautobot/core/tests/test_graphql.py +20 -5
  101. nautobot/core/tests/test_jobs.py +34 -21
  102. nautobot/core/tests/test_tables.py +43 -6
  103. nautobot/core/tests/test_templatetags_ui_framework.py +146 -0
  104. nautobot/core/tests/test_titles.py +2 -2
  105. nautobot/core/tests/test_ui.py +188 -1
  106. nautobot/core/tests/test_utils.py +35 -0
  107. nautobot/core/tests/test_views.py +45 -0
  108. nautobot/core/tests/test_views_generic.py +43 -0
  109. nautobot/core/tests/test_views_utils.py +239 -5
  110. nautobot/core/ui/breadcrumbs.py +220 -28
  111. nautobot/core/ui/bulk_buttons.py +8 -0
  112. nautobot/core/ui/object_detail.py +181 -60
  113. nautobot/core/ui/titles.py +10 -5
  114. nautobot/core/utils/requests.py +27 -2
  115. nautobot/core/views/__init__.py +24 -3
  116. nautobot/core/views/generic.py +70 -35
  117. nautobot/core/views/mixins.py +226 -122
  118. nautobot/core/views/utils.py +270 -1
  119. nautobot/dcim/api/serializers.py +8 -2
  120. nautobot/dcim/constants.py +1 -0
  121. nautobot/dcim/factory.py +4 -3
  122. nautobot/dcim/filters/mixins.py +1 -2
  123. nautobot/dcim/forms.py +5 -1
  124. nautobot/dcim/migrations/0074_alter_rack_u_height.py +21 -0
  125. nautobot/dcim/models/devices.py +30 -1
  126. nautobot/dcim/models/racks.py +2 -2
  127. nautobot/dcim/tables/__init__.py +2 -0
  128. nautobot/dcim/tables/devices.py +24 -0
  129. nautobot/dcim/tables/power.py +2 -2
  130. nautobot/dcim/templates/dcim/cable.html +53 -53
  131. nautobot/dcim/templates/dcim/cable_connect.html +182 -182
  132. nautobot/dcim/templates/dcim/cable_trace.html +1 -1
  133. nautobot/dcim/templates/dcim/console_port_connection_list.html +5 -5
  134. nautobot/dcim/templates/dcim/consoleport.html +86 -86
  135. nautobot/dcim/templates/dcim/consoleserverport.html +86 -86
  136. nautobot/dcim/templates/dcim/controller_create.html +34 -34
  137. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +68 -68
  138. nautobot/dcim/templates/dcim/device/base.html +1 -114
  139. nautobot/dcim/templates/dcim/device/config.html +17 -17
  140. nautobot/dcim/templates/dcim/device/consoleports.html +1 -52
  141. nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -52
  142. nautobot/dcim/templates/dcim/device/devicebays.html +1 -48
  143. nautobot/dcim/templates/dcim/device/frontports.html +1 -52
  144. nautobot/dcim/templates/dcim/device/interfaces.html +1 -56
  145. nautobot/dcim/templates/dcim/device/inventory.html +1 -48
  146. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +64 -64
  147. nautobot/dcim/templates/dcim/device/modulebays.html +1 -48
  148. nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -52
  149. nautobot/dcim/templates/dcim/device/powerports.html +1 -52
  150. nautobot/dcim/templates/dcim/device/rearports.html +1 -52
  151. nautobot/dcim/templates/dcim/device/status.html +66 -66
  152. nautobot/dcim/templates/dcim/device/wireless.html +1 -72
  153. nautobot/dcim/templates/dcim/device.html +4 -422
  154. nautobot/dcim/templates/dcim/device_component.html +0 -19
  155. nautobot/dcim/templates/dcim/device_component_add.html +25 -25
  156. nautobot/dcim/templates/dcim/device_create.html +229 -0
  157. nautobot/dcim/templates/dcim/device_edit.html +2 -227
  158. nautobot/dcim/templates/dcim/devicebay.html +41 -41
  159. nautobot/dcim/templates/dcim/devicebay_populate.html +32 -32
  160. nautobot/dcim/templates/dcim/devicetype_component_add.html +28 -28
  161. nautobot/dcim/templates/dcim/devicetype_retrieve.html +1 -3
  162. nautobot/dcim/templates/dcim/frontport.html +84 -84
  163. nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +1 -1
  164. nautobot/dcim/templates/dcim/inc/device_interface_filter.html +8 -0
  165. nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +1 -15
  166. nautobot/dcim/templates/dcim/inc/location_hierarchy.html +22 -22
  167. nautobot/dcim/templates/dcim/interface.html +206 -206
  168. nautobot/dcim/templates/dcim/interface_connection_list.html +5 -5
  169. nautobot/dcim/templates/dcim/interfaceredundancygroupassociation_create.html +6 -6
  170. nautobot/dcim/templates/dcim/inventoryitem.html +44 -44
  171. nautobot/dcim/templates/dcim/inventoryitem_add.html +32 -32
  172. nautobot/dcim/templates/dcim/inventoryitem_edit.html +22 -22
  173. nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +46 -46
  174. nautobot/dcim/templates/dcim/location_retrieve.html +1 -7
  175. nautobot/dcim/templates/dcim/locationtype.html +1 -6
  176. nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -7
  177. nautobot/dcim/templates/dcim/module/base.html +85 -85
  178. nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
  179. nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
  180. nautobot/dcim/templates/dcim/module_retrieve.html +52 -52
  181. nautobot/dcim/templates/dcim/module_update.html +61 -61
  182. nautobot/dcim/templates/dcim/modulebay_destroy.html +1 -1
  183. nautobot/dcim/templates/dcim/modulebay_retrieve.html +83 -99
  184. nautobot/dcim/templates/dcim/modulebay_update.html +33 -33
  185. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  186. nautobot/dcim/templates/dcim/moduletype_retrieve.html +140 -144
  187. nautobot/dcim/templates/dcim/platform_create.html +38 -38
  188. nautobot/dcim/templates/dcim/power_port_connection_list.html +5 -5
  189. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -8
  190. nautobot/dcim/templates/dcim/poweroutlet.html +85 -85
  191. nautobot/dcim/templates/dcim/powerpanel_retrieve.html +1 -8
  192. nautobot/dcim/templates/dcim/powerport.html +91 -91
  193. nautobot/dcim/templates/dcim/rack_elevation_list.html +18 -18
  194. nautobot/dcim/templates/dcim/rack_retrieve.html +264 -274
  195. nautobot/dcim/templates/dcim/rackreservation_retrieve.html +0 -3
  196. nautobot/dcim/templates/dcim/rearport.html +78 -78
  197. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -50
  198. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -5
  199. nautobot/dcim/tests/integration/test_device_bulk_operations.py +3 -2
  200. nautobot/dcim/tests/integration/test_location_bulk_operations.py +6 -2
  201. nautobot/dcim/tests/test_api.py +33 -1
  202. nautobot/dcim/tests/test_views.py +189 -4
  203. nautobot/dcim/ui.py +29 -0
  204. nautobot/dcim/urls.py +1 -109
  205. nautobot/dcim/utils.py +30 -0
  206. nautobot/dcim/views.py +1149 -550
  207. nautobot/extras/filters/mixins.py +1 -1
  208. nautobot/extras/forms/forms.py +15 -0
  209. nautobot/extras/models/groups.py +10 -1
  210. nautobot/extras/models/jobs.py +2 -2
  211. nautobot/extras/plugins/views.py +18 -5
  212. nautobot/extras/tables.py +24 -2
  213. nautobot/extras/templates/extras/computedfield_edit.html +4 -4
  214. nautobot/extras/templates/extras/configcontext_update.html +1 -1
  215. nautobot/extras/templates/extras/configcontextschema_retrieve.html +32 -32
  216. nautobot/extras/templates/extras/customfield_retrieve.html +1 -128
  217. nautobot/extras/templates/extras/customfield_update.html +23 -23
  218. nautobot/extras/templates/extras/dynamicgroup.html +2 -99
  219. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -199
  220. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +99 -0
  221. nautobot/extras/templates/extras/dynamicgroup_update.html +199 -0
  222. nautobot/extras/templates/extras/gitrepository.html +2 -82
  223. nautobot/extras/templates/extras/gitrepository_list.html +10 -10
  224. nautobot/extras/templates/extras/gitrepository_object_edit.html +2 -13
  225. nautobot/extras/templates/extras/gitrepository_retrieve.html +82 -0
  226. nautobot/extras/templates/extras/gitrepository_update.html +13 -0
  227. nautobot/extras/templates/extras/graphqlquery_retrieve.html +73 -73
  228. nautobot/extras/templates/extras/inc/configcontext_format.html +2 -2
  229. nautobot/extras/templates/extras/inc/job_table.html +10 -10
  230. nautobot/extras/templates/extras/inc/jobresult.html +21 -21
  231. nautobot/extras/templates/extras/inc/jobresult_js.html +6 -6
  232. nautobot/extras/templates/extras/inc/tags_panel.html +10 -10
  233. nautobot/extras/templates/extras/job.html +64 -64
  234. nautobot/extras/templates/extras/job_approval_request.html +9 -9
  235. nautobot/extras/templates/extras/job_bulk_edit.html +13 -13
  236. nautobot/extras/templates/extras/job_edit.html +45 -45
  237. nautobot/extras/templates/extras/job_list.html +4 -4
  238. nautobot/extras/templates/extras/jobresult_retrieve.html +0 -25
  239. nautobot/extras/templates/extras/marketplace.html +101 -101
  240. nautobot/extras/templates/extras/metadatatype_create.html +20 -20
  241. nautobot/extras/templates/extras/note_retrieve.html +0 -52
  242. nautobot/extras/templates/extras/object_assign_contact_or_team.html +18 -18
  243. nautobot/extras/templates/extras/object_configcontext.html +1 -3
  244. nautobot/extras/templates/extras/objectchange.html +2 -165
  245. nautobot/extras/templates/extras/objectchange_retrieve.html +165 -0
  246. nautobot/extras/templates/extras/plugin_detail.html +44 -48
  247. nautobot/extras/templates/extras/plugins_list.html +9 -11
  248. nautobot/extras/templates/extras/plugins_tiles.html +26 -26
  249. nautobot/extras/templates/extras/relationship_edit.html +4 -4
  250. nautobot/extras/templates/extras/role_retrieve.html +13 -13
  251. nautobot/extras/templates/extras/scheduled_jobs_approval_queue_list.html +21 -21
  252. nautobot/extras/templates/extras/scheduledjob.html +128 -128
  253. nautobot/extras/templates/extras/secret_create.html +53 -53
  254. nautobot/extras/templates/extras/secretsgroup_update.html +13 -13
  255. nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +3 -3
  256. nautobot/extras/templates/extras/webhook.html +79 -79
  257. nautobot/extras/tests/integration/test_relationships.py +6 -6
  258. nautobot/extras/tests/test_dynamicgroups.py +73 -18
  259. nautobot/extras/tests/test_filters.py +1 -1
  260. nautobot/extras/tests/test_jobs.py +2 -0
  261. nautobot/extras/tests/test_views.py +8 -3
  262. nautobot/extras/urls.py +3 -97
  263. nautobot/extras/views.py +524 -456
  264. nautobot/ipam/filters.py +2 -2
  265. nautobot/ipam/migrations/0053_alter_vrfdeviceassignment_options_and_more.py +20 -0
  266. nautobot/ipam/models.py +34 -0
  267. nautobot/ipam/querysets.py +3 -3
  268. nautobot/ipam/signals.py +6 -1
  269. nautobot/ipam/tables.py +3 -1
  270. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +4 -0
  271. nautobot/ipam/templates/ipam/inc/toggle_available.html +8 -8
  272. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +4 -4
  273. nautobot/ipam/templates/ipam/ipaddress.html +119 -123
  274. nautobot/ipam/templates/ipam/ipaddress_assign.html +10 -10
  275. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -1
  276. nautobot/ipam/templates/ipam/ipaddress_merge.html +180 -180
  277. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +48 -48
  278. nautobot/ipam/templates/ipam/prefix.html +2 -115
  279. nautobot/ipam/templates/ipam/prefix_create.html +34 -0
  280. nautobot/ipam/templates/ipam/prefix_edit.html +1 -34
  281. nautobot/ipam/templates/ipam/prefix_retrieve.html +3 -0
  282. nautobot/ipam/templates/ipam/service_retrieve.html +1 -6
  283. nautobot/ipam/templates/ipam/vlan_retrieve.html +1 -7
  284. nautobot/ipam/templates/ipam/vrf_edit.html +1 -1
  285. nautobot/ipam/tests/test_api.py +5 -0
  286. nautobot/ipam/tests/test_models.py +387 -0
  287. nautobot/ipam/tests/test_querysets.py +46 -0
  288. nautobot/ipam/tests/test_views.py +34 -0
  289. nautobot/ipam/ui.py +145 -0
  290. nautobot/ipam/urls.py +1 -46
  291. nautobot/ipam/utils/__init__.py +26 -0
  292. nautobot/ipam/utils/migrations.py +1 -1
  293. nautobot/ipam/views.py +234 -112
  294. nautobot/project-static/docs/404.html +11 -11
  295. nautobot/project-static/docs/apps/index.html +11 -11
  296. nautobot/project-static/docs/apps/nautobot-apps.html +11 -11
  297. nautobot/project-static/docs/assets/javascripts/{bundle.92b07e13.min.js → bundle.f55a23d4.min.js} +2 -2
  298. nautobot/project-static/docs/assets/javascripts/{bundle.92b07e13.min.js.map → bundle.f55a23d4.min.js.map} +2 -2
  299. nautobot/project-static/docs/assets/stylesheets/{main.7e37652d.min.css → main.e53b48f4.min.css} +1 -1
  300. nautobot/project-static/docs/assets/stylesheets/{main.7e37652d.min.css.map → main.e53b48f4.min.css.map} +1 -1
  301. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +11 -11
  302. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +11 -11
  303. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +11 -11
  304. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +11 -11
  305. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +11 -11
  306. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +11 -11
  307. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +11 -11
  308. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +11 -11
  309. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +11 -11
  310. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +11 -11
  311. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +11 -11
  312. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +11 -11
  313. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +11 -11
  314. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +11 -11
  315. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +11 -11
  316. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -11
  317. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +11 -11
  318. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +11 -11
  319. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -11
  320. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +83 -11
  321. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1265 -281
  322. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +11 -11
  323. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +12 -12
  324. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +452 -29
  325. nautobot/project-static/docs/development/apps/api/configuration-view.html +11 -11
  326. nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -11
  327. nautobot/project-static/docs/development/apps/api/models/django-admin.html +11 -11
  328. nautobot/project-static/docs/development/apps/api/models/global-search.html +11 -11
  329. nautobot/project-static/docs/development/apps/api/models/graphql.html +11 -11
  330. nautobot/project-static/docs/development/apps/api/models/index.html +11 -11
  331. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +12 -12
  332. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +11 -11
  333. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +11 -11
  334. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +11 -11
  335. nautobot/project-static/docs/development/apps/api/platform-features/index.html +11 -11
  336. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -11
  337. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +11 -11
  338. nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +11 -11
  339. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +11 -11
  340. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +11 -11
  341. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +11 -11
  342. nautobot/project-static/docs/development/apps/api/prometheus.html +11 -11
  343. nautobot/project-static/docs/development/apps/api/setup.html +11 -11
  344. nautobot/project-static/docs/development/apps/api/testing.html +11 -11
  345. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +11 -11
  346. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +11 -11
  347. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +11 -11
  348. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +11 -11
  349. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +11 -11
  350. nautobot/project-static/docs/development/apps/api/views/base-template.html +11 -11
  351. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +11 -11
  352. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +11 -11
  353. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +11 -11
  354. nautobot/project-static/docs/development/apps/api/views/index.html +11 -11
  355. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +11 -11
  356. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +11 -11
  357. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +11 -11
  358. nautobot/project-static/docs/development/apps/api/views/notes.html +11 -11
  359. nautobot/project-static/docs/development/apps/api/views/rest-api.html +11 -11
  360. nautobot/project-static/docs/development/apps/api/views/urls.html +11 -11
  361. nautobot/project-static/docs/development/apps/index.html +11 -11
  362. nautobot/project-static/docs/development/apps/migration/code-updates.html +11 -11
  363. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +11 -11
  364. nautobot/project-static/docs/development/apps/migration/from-v1.html +11 -11
  365. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +11 -11
  366. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +11 -11
  367. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +11 -11
  368. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +11 -11
  369. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +11 -11
  370. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +11 -11
  371. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +11 -11
  372. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +11 -11
  373. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +11 -11
  374. nautobot/project-static/docs/development/apps/porting-from-netbox.html +11 -11
  375. nautobot/project-static/docs/development/core/application-registry.html +11 -11
  376. nautobot/project-static/docs/development/core/best-practices.html +11 -11
  377. nautobot/project-static/docs/development/core/bootstrap-ui.html +11 -11
  378. nautobot/project-static/docs/development/core/caching.html +11 -11
  379. nautobot/project-static/docs/development/core/controllers.html +11 -11
  380. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +11 -11
  381. nautobot/project-static/docs/development/core/generic-views.html +11 -11
  382. nautobot/project-static/docs/development/core/getting-started.html +50 -63
  383. nautobot/project-static/docs/development/core/homepage.html +11 -11
  384. nautobot/project-static/docs/development/core/index.html +11 -11
  385. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +11 -11
  386. nautobot/project-static/docs/development/core/model-checklist.html +11 -11
  387. nautobot/project-static/docs/development/core/model-features.html +11 -11
  388. nautobot/project-static/docs/development/core/natural-keys.html +11 -11
  389. nautobot/project-static/docs/development/core/navigation-menu.html +11 -11
  390. nautobot/project-static/docs/development/core/release-checklist.html +11 -11
  391. nautobot/project-static/docs/development/core/role-internals.html +11 -11
  392. nautobot/project-static/docs/development/core/settings.html +11 -11
  393. nautobot/project-static/docs/development/core/style-guide.html +15 -11
  394. nautobot/project-static/docs/development/core/templates.html +11 -11
  395. nautobot/project-static/docs/development/core/testing.html +11 -11
  396. nautobot/project-static/docs/development/core/ui-component-framework.html +17 -22
  397. nautobot/project-static/docs/development/core/user-preferences.html +11 -11
  398. nautobot/project-static/docs/development/index.html +11 -11
  399. nautobot/project-static/docs/development/jobs/getting-started.html +11 -11
  400. nautobot/project-static/docs/development/jobs/index.html +11 -11
  401. nautobot/project-static/docs/development/jobs/installation.html +11 -11
  402. nautobot/project-static/docs/development/jobs/job-extensions.html +11 -11
  403. nautobot/project-static/docs/development/jobs/job-logging.html +11 -11
  404. nautobot/project-static/docs/development/jobs/job-patterns.html +11 -11
  405. nautobot/project-static/docs/development/jobs/job-structure.html +11 -11
  406. nautobot/project-static/docs/development/jobs/migration/from-v1.html +11 -11
  407. nautobot/project-static/docs/development/jobs/testing.html +11 -11
  408. nautobot/project-static/docs/index.html +11 -11
  409. nautobot/project-static/docs/objects.inv +0 -0
  410. nautobot/project-static/docs/overview/application_stack.html +11 -11
  411. nautobot/project-static/docs/overview/design_philosophy.html +11 -11
  412. nautobot/project-static/docs/release-notes/index.html +11 -11
  413. nautobot/project-static/docs/release-notes/version-1.0.html +11 -11
  414. nautobot/project-static/docs/release-notes/version-1.1.html +11 -11
  415. nautobot/project-static/docs/release-notes/version-1.2.html +11 -11
  416. nautobot/project-static/docs/release-notes/version-1.3.html +11 -11
  417. nautobot/project-static/docs/release-notes/version-1.4.html +11 -11
  418. nautobot/project-static/docs/release-notes/version-1.5.html +11 -11
  419. nautobot/project-static/docs/release-notes/version-1.6.html +11 -11
  420. nautobot/project-static/docs/release-notes/version-2.0.html +11 -11
  421. nautobot/project-static/docs/release-notes/version-2.1.html +11 -11
  422. nautobot/project-static/docs/release-notes/version-2.2.html +11 -11
  423. nautobot/project-static/docs/release-notes/version-2.3.html +11 -11
  424. nautobot/project-static/docs/release-notes/version-2.4.html +418 -11
  425. nautobot/project-static/docs/search/search_index.json +1 -1
  426. nautobot/project-static/docs/sitemap.xml +300 -300
  427. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  428. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +11 -11
  429. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +11 -11
  430. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +11 -11
  431. nautobot/project-static/docs/user-guide/administration/configuration/index.html +11 -11
  432. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +11 -11
  433. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +38 -11
  434. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +11 -11
  435. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +89 -14
  436. nautobot/project-static/docs/user-guide/administration/guides/docker.html +11 -11
  437. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +11 -11
  438. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +11 -11
  439. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +11 -11
  440. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +11 -11
  441. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +11 -11
  442. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +11 -11
  443. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +11 -11
  444. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +11 -11
  445. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +11 -11
  446. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +11 -11
  447. nautobot/project-static/docs/user-guide/administration/installation/index.html +11 -11
  448. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +11 -11
  449. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +11 -11
  450. nautobot/project-static/docs/user-guide/administration/installation/services.html +11 -11
  451. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +11 -11
  452. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +11 -11
  453. nautobot/project-static/docs/user-guide/administration/security/index.html +11 -11
  454. nautobot/project-static/docs/user-guide/administration/security/notices.html +11 -11
  455. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +11 -11
  456. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +11 -11
  457. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +11 -11
  458. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +11 -11
  459. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +11 -11
  460. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +11 -11
  461. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +11 -11
  462. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +11 -11
  463. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +11 -11
  464. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +11 -11
  465. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +11 -11
  466. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +11 -11
  467. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +11 -11
  468. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +11 -11
  469. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +11 -11
  470. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +11 -11
  471. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +11 -11
  472. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +11 -11
  473. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +11 -11
  474. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +11 -11
  475. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +11 -11
  476. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +11 -11
  477. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +11 -11
  478. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +11 -11
  479. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +11 -11
  480. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +11 -11
  481. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +11 -11
  482. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +11 -11
  483. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +11 -11
  484. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +11 -11
  485. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +11 -11
  486. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +11 -11
  487. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +11 -11
  488. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +11 -11
  489. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +11 -11
  490. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +11 -11
  491. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +11 -11
  492. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +11 -11
  493. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +11 -11
  494. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +11 -11
  495. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +11 -11
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +11 -11
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +11 -11
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +11 -11
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +11 -11
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +11 -11
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +11 -11
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +11 -11
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +11 -11
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +11 -11
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +11 -11
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +11 -11
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +11 -11
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +11 -11
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +11 -11
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +11 -11
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +11 -11
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +11 -11
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +11 -11
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +11 -11
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +11 -11
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +11 -11
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +11 -11
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +11 -11
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +11 -11
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +11 -11
  521. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +11 -11
  522. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +11 -11
  523. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -11
  524. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -11
  525. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +11 -11
  526. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +11 -11
  527. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +11 -11
  528. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +11 -11
  529. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +11 -11
  530. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +11 -11
  531. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +11 -11
  532. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +11 -11
  533. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +11 -11
  534. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +11 -11
  535. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +11 -11
  536. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +11 -11
  537. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +11 -11
  538. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +11 -11
  539. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +11 -11
  540. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +11 -11
  541. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +11 -11
  542. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +11 -11
  543. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +11 -11
  544. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +11 -11
  545. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +11 -11
  546. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -11
  547. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +11 -11
  548. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +11 -11
  549. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +11 -11
  550. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +11 -11
  551. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +11 -11
  552. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +11 -11
  553. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +11 -11
  554. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +11 -11
  555. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +11 -11
  556. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +11 -11
  557. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +11 -11
  558. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +11 -11
  559. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +11 -11
  560. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +11 -11
  561. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +11 -11
  562. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +11 -11
  563. nautobot/project-static/docs/user-guide/index.html +11 -11
  564. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +11 -11
  565. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +11 -11
  566. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +11 -11
  567. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +11 -11
  568. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +11 -11
  569. nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -11
  570. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +11 -11
  571. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +11 -11
  572. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +11 -11
  573. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +11 -11
  574. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +11 -11
  575. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +11 -11
  576. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +11 -11
  577. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +11 -11
  578. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +11 -11
  579. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +11 -11
  580. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +11 -11
  581. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +11 -11
  582. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +11 -11
  583. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +11 -11
  584. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +11 -11
  585. nautobot/project-static/docs/user-guide/platform-functionality/note.html +11 -11
  586. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +11 -11
  587. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +11 -11
  588. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +11 -11
  589. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +11 -11
  590. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +11 -11
  591. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +11 -11
  592. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +11 -11
  593. nautobot/project-static/docs/user-guide/platform-functionality/role.html +11 -11
  594. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +11 -11
  595. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -11
  596. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +11 -11
  597. nautobot/project-static/docs/user-guide/platform-functionality/status.html +11 -11
  598. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +11 -11
  599. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +11 -11
  600. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +11 -11
  601. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +11 -11
  602. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +11 -11
  603. nautobot/project-static/img/nautobot_icon.svg +32 -34
  604. nautobot/project-static/js/table_sorting_indicator.js +0 -2
  605. nautobot/tenancy/templates/tenancy/tenant.html +1 -7
  606. nautobot/tenancy/views.py +13 -0
  607. nautobot/users/templates/users/api_tokens.html +4 -4
  608. nautobot/users/templates/users/base.html +28 -28
  609. nautobot/virtualization/templates/virtualization/cluster.html +64 -64
  610. nautobot/virtualization/templates/virtualization/inc/virtualmachine_vminterface_filter.html +8 -0
  611. nautobot/virtualization/templates/virtualization/virtualmachine_component_add.html +25 -25
  612. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +1 -251
  613. nautobot/virtualization/templates/virtualization/vminterface.html +70 -70
  614. nautobot/virtualization/urls.py +0 -12
  615. nautobot/virtualization/views.py +158 -54
  616. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +13 -13
  617. nautobot/wireless/tests/integration/test_radio_profile.py +1 -1
  618. {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/METADATA +4 -4
  619. {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/RECORD +623 -607
  620. nautobot/core/templates/inc/breadcrumbs.html +0 -14
  621. nautobot/ipam/templates/ipam/prefix_ipaddresses.html +0 -11
  622. nautobot/ipam/templates/ipam/prefix_prefixes.html +0 -11
  623. nautobot/project-static/docs/requirements.txt +0 -14
  624. {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/LICENSE.txt +0 -0
  625. {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/NOTICE +0 -0
  626. {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/WHEEL +0 -0
  627. {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/entry_points.txt +0 -0
@@ -1,16 +1,22 @@
1
1
  import urllib.parse
2
2
 
3
3
  from django.contrib.auth.models import AnonymousUser
4
+ from django.contrib.contenttypes.models import ContentType
4
5
  from django.db import ProgrammingError
5
- from django.test import TestCase
6
6
 
7
7
  from nautobot.core.models.querysets import count_related
8
- from nautobot.core.testing import TransactionTestCase
9
- from nautobot.core.views.utils import check_filter_for_display, get_saved_views_for_user, prepare_cloned_fields
8
+ from nautobot.core.testing import TestCase
9
+ from nautobot.core.views.utils import (
10
+ check_filter_for_display,
11
+ get_bulk_queryset_from_view,
12
+ get_saved_views_for_user,
13
+ prepare_cloned_fields,
14
+ )
10
15
  from nautobot.dcim.filters import DeviceFilterSet
11
16
  from nautobot.dcim.models import Device, DeviceRedundancyGroup, DeviceType, InventoryItem, Location, Manufacturer
12
17
  from nautobot.extras.models import Role, SavedView, Status
13
- from nautobot.users.models import User
18
+ from nautobot.ipam.models import Namespace, VRF
19
+ from nautobot.users.models import ObjectPermission, User
14
20
 
15
21
 
16
22
  class CheckFilterForDisplayTest(TestCase):
@@ -173,7 +179,7 @@ class CheckPrepareClonedFields(TestCase):
173
179
  self.assertTrue(query_params["description"][0] == description)
174
180
 
175
181
 
176
- class GetSavedViewsForUserTestCase(TransactionTestCase):
182
+ class GetSavedViewsForUserTestCase(TestCase):
177
183
  """
178
184
  Class to test `get_saved_views_for_user`.
179
185
  """
@@ -186,6 +192,8 @@ class GetSavedViewsForUserTestCase(TransactionTestCase):
186
192
 
187
193
  def setUp(self):
188
194
  super().setUp()
195
+ # We want a clean slate for SavedViews
196
+ SavedView.objects.all().delete()
189
197
  self.user2 = User.objects.create_user(username="second_user")
190
198
  self.create_saved_view(name="saved_view")
191
199
  self.create_saved_view(name="saved_view_shared", is_shared=True)
@@ -219,3 +227,229 @@ class GetSavedViewsForUserTestCase(TransactionTestCase):
219
227
  self.assertEqual(saved_views.count(), 2)
220
228
  expected_names = ["saved_view_shared", "saved_view_shared_different_owner"]
221
229
  self.assertEqual(list(saved_views.values_list("name", flat=True)), expected_names)
230
+
231
+
232
+ class GetBulkQuerysetFromViewTestCase(TestCase):
233
+ """Class to test get_bulk_queryset_from_view with VRF."""
234
+
235
+ def setUp(self):
236
+ super().setUp()
237
+ self.user = User.objects.create_user(username="testuser", password="testpass", is_superuser=True) # noqa: S106 # hardcoded-password-func-arg -- ok as this is test code only
238
+ self.vrfs = []
239
+ namespace = Namespace.objects.create(name="test-namespace")
240
+ for i in range(10):
241
+ vrf = VRF.objects.create(
242
+ name=f"VRF {i}",
243
+ description=f"desc-{i % 3}", # 3 unique descriptions, repeated
244
+ namespace=namespace,
245
+ )
246
+ self.vrfs.append(vrf)
247
+ # SavedView filters by name of VRF 5
248
+ self.saved_view = SavedView.objects.create(
249
+ name="VRF 5 Only",
250
+ owner=self.user,
251
+ view="ipam:vrf_list",
252
+ config={"filter_params": {"name": self.vrfs[5].name}},
253
+ )
254
+ self.saved_view_empty = SavedView.objects.create(
255
+ name="NO Filter Params", owner=self.user, view="ipam:vrf_list", config={"per_page": 50, "sort_order": []}
256
+ )
257
+ self.content_type = ContentType.objects.get_for_model(VRF)
258
+
259
+ def test_not_is_all_and_pk_list(self):
260
+ """!is_all and pk_list: Return queryset filtered by pk_list"""
261
+ qs = get_bulk_queryset_from_view(
262
+ user=self.user,
263
+ content_type=self.content_type,
264
+ edit_all=False,
265
+ filter_query_params={},
266
+ pk_list=[self.vrfs[2].pk, self.vrfs[4].pk],
267
+ saved_view_id=None,
268
+ action="change",
269
+ )
270
+ self.assertQuerysetEqual(qs, [self.vrfs[2], self.vrfs[4]], ordered=False)
271
+
272
+ def test_not_is_all_and_no_pk_list(self):
273
+ """!is_all and !pk_list: Return empty queryset. This should not normally happen in practice."""
274
+ qs = get_bulk_queryset_from_view(
275
+ user=self.user,
276
+ content_type=self.content_type,
277
+ edit_all=False,
278
+ filter_query_params={},
279
+ pk_list=[],
280
+ saved_view_id=None,
281
+ action="change",
282
+ )
283
+ self.assertEqual(qs.count(), 0)
284
+
285
+ def test_is_all_and_no_saved_view_and_no_filter_query_params(self):
286
+ """is_all and !saved_view_id and !filter_query_params: Return all objects"""
287
+ qs = get_bulk_queryset_from_view(
288
+ user=self.user,
289
+ content_type=self.content_type,
290
+ edit_all=True,
291
+ filter_query_params={},
292
+ pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
293
+ saved_view_id=None,
294
+ action="change",
295
+ )
296
+ self.assertQuerysetEqual(qs, VRF.objects.all(), ordered=False)
297
+
298
+ def test_all_filters_removed_flag_ignores_saved_view(self):
299
+ """
300
+ If filter_query_params contains 'all_filters_removed', the saved view is ignored and all objects are returned.
301
+ """
302
+ # Pass the all_filters_removed flag in filter_query_params
303
+ qs = get_bulk_queryset_from_view(
304
+ user=self.user,
305
+ content_type=self.content_type,
306
+ filter_query_params={"all_filters_removed": [True], "saved_view": [self.saved_view.id]},
307
+ pk_list=[],
308
+ saved_view_id=self.saved_view.id,
309
+ action="change",
310
+ edit_all=True,
311
+ )
312
+ # Should return all VRFs, not just the one from the saved view
313
+ self.assertQuerysetEqual(qs, VRF.objects.all(), ordered=False)
314
+
315
+ def test_is_all_and_filter_query_params(self):
316
+ """is_all and filter_query_params: Return queryset filtered by filter_query_params (description)"""
317
+ qs = get_bulk_queryset_from_view(
318
+ user=self.user,
319
+ content_type=self.content_type,
320
+ edit_all=True,
321
+ filter_query_params={"description": ["desc-1"]},
322
+ pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
323
+ saved_view_id=None,
324
+ action="change",
325
+ )
326
+ self.assertQuerysetEqual(qs, VRF.objects.filter(description="desc-1"), ordered=False)
327
+
328
+ def test_is_all_and_saved_view_id(self):
329
+ """is_all and saved_view_id: Return queryset filtered by saved_view_filter_params (name)"""
330
+ qs = get_bulk_queryset_from_view(
331
+ user=self.user,
332
+ content_type=self.content_type,
333
+ edit_all=True,
334
+ filter_query_params={},
335
+ pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
336
+ saved_view_id=self.saved_view.id,
337
+ action="change",
338
+ )
339
+ self.assertQuerysetEqual(qs, [self.vrfs[5]], ordered=False)
340
+
341
+ def test_is_all_and_not_saved_view_id_but_saved_view_filter_params(self):
342
+ """is_all and not saved_view_id: Return queryset filtered by filter_query_params (name)"""
343
+ qs = get_bulk_queryset_from_view(
344
+ user=self.user,
345
+ content_type=self.content_type,
346
+ edit_all=True,
347
+ filter_query_params={"name": [self.vrfs[7].name]},
348
+ pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
349
+ saved_view_id=None,
350
+ action="change",
351
+ )
352
+ self.assertQuerysetEqual(qs, [self.vrfs[7]], ordered=False)
353
+
354
+ def test_queryset_respects_permissions(self):
355
+ """is_all and not saved_view_id: Return queryset filtered by filter_query_params (name)"""
356
+ # Create a non-superuser with no permissions
357
+ limited_permissions_user = User.objects.create_user(
358
+ username="user_no_perms",
359
+ password="testpass", # noqa: S106 # hardcoded-password-func-arg -- ok as this is test code only
360
+ is_superuser=False,
361
+ )
362
+ # Assign object permission
363
+ obj_perm = ObjectPermission.objects.create(
364
+ name="Test permission",
365
+ constraints={"name": self.vrfs[2].name},
366
+ actions=["view", "change", "delete", "add"],
367
+ )
368
+ obj_perm.users.add(limited_permissions_user)
369
+ obj_perm.object_types.add(self.content_type)
370
+
371
+ qs = get_bulk_queryset_from_view(
372
+ user=limited_permissions_user,
373
+ content_type=self.content_type,
374
+ edit_all=True,
375
+ filter_query_params={},
376
+ pk_list=[self.vrfs[3].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
377
+ saved_view_id=None,
378
+ action="change",
379
+ )
380
+ self.assertQuerysetEqual(qs, VRF.objects.filter(name=self.vrfs[2].name), ordered=False)
381
+
382
+ def test_querydict_ignores(self):
383
+ """is_all and not saved_view_id: Return queryset filtered by filter_query_params (name)"""
384
+ filter_query_params = {"name": [self.vrfs[7].name], "per_page": [10]}
385
+ qs = get_bulk_queryset_from_view(
386
+ user=self.user,
387
+ content_type=self.content_type,
388
+ edit_all=True,
389
+ filter_query_params=filter_query_params,
390
+ pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
391
+ saved_view_id=None,
392
+ action="change",
393
+ )
394
+ self.assertQuerysetEqual(qs, VRF.objects.filter(name=self.vrfs[7].name), ordered=False)
395
+
396
+ def test_no_valid_operation_found_raises(self):
397
+ """If no valid operation is found, raise RuntimeError."""
398
+ # Simulate a situation where all logic branches are skipped
399
+ with self.assertRaises(RuntimeError):
400
+ get_bulk_queryset_from_view(
401
+ user=self.user,
402
+ content_type=self.content_type,
403
+ filter_query_params=None,
404
+ pk_list=[],
405
+ saved_view_id=None,
406
+ action="change",
407
+ edit_all=None, # Not True, so not valid for 'change'
408
+ )
409
+
410
+ def test_runtime_error_when_all_param_missing(self):
411
+ """Raise RuntimeError if required *_all param is not provided for the action."""
412
+ with self.assertRaises(RuntimeError):
413
+ get_bulk_queryset_from_view(
414
+ user=self.user,
415
+ content_type=self.content_type,
416
+ filter_query_params={},
417
+ pk_list=[],
418
+ saved_view_id=None,
419
+ action="delete", # delete_all is missing
420
+ )
421
+ with self.assertRaises(RuntimeError):
422
+ get_bulk_queryset_from_view(
423
+ user=self.user,
424
+ content_type=self.content_type,
425
+ filter_query_params={},
426
+ pk_list=[],
427
+ saved_view_id=None,
428
+ action="change", # edit_all is missing
429
+ )
430
+
431
+ def test_is_all_and_saved_view__with_no_filter_params(self):
432
+ """is_all and saved_view_id with no filter_params in saved view: Return all objects"""
433
+ qs = get_bulk_queryset_from_view(
434
+ user=self.user,
435
+ content_type=self.content_type,
436
+ edit_all=True,
437
+ filter_query_params={},
438
+ pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
439
+ saved_view_id=self.saved_view_empty.id,
440
+ action="change",
441
+ )
442
+ self.assertQuerysetEqual(qs, VRF.objects.all(), ordered=False)
443
+
444
+ def test_bad_saved_view(self):
445
+ """Test with a saved view ID that does not exist: Return no objects"""
446
+ qs = get_bulk_queryset_from_view(
447
+ user=self.user,
448
+ content_type=self.content_type,
449
+ edit_all=True,
450
+ filter_query_params={},
451
+ pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
452
+ saved_view_id="00000000-0000-4000-8000-000000000000", # valid UUID but does not exist
453
+ action="change",
454
+ )
455
+ self.assertQuerysetEqual(qs, VRF.objects.none(), ordered=False)
@@ -1,5 +1,6 @@
1
1
  from dataclasses import dataclass
2
2
  import logging
3
+ from operator import attrgetter
3
4
  from typing import Any, Callable, Literal, Optional, Protocol, Type, Union
4
5
  from urllib.parse import urlencode
5
6
 
@@ -7,10 +8,12 @@ from django.db.models import Model
7
8
  from django.template import Context
8
9
  from django.urls import NoReverseMatch, reverse
9
10
 
11
+ from nautobot.core.models.tree_queries import TreeModel
10
12
  from nautobot.core.templatetags import helpers
11
13
  from nautobot.core.ui.utils import get_absolute_url, render_component_template
12
14
  from nautobot.core.utils import lookup
13
15
  from nautobot.core.utils.lookup import get_model_for_view_name, get_model_from_name
16
+ from nautobot.core.views.utils import get_obj_from_context
14
17
 
15
18
  logger = logging.getLogger(__name__)
16
19
 
@@ -27,6 +30,32 @@ BreadcrumbItemsType = dict[str, list["BaseBreadcrumbItem"]]
27
30
  ReverseParams = Union[dict[str, Any], Callable[[Context], dict[str, Any]], None]
28
31
 
29
32
 
33
+ def context_object_attr(attr_path: str, context_key: str = "object"):
34
+ """
35
+ Helper function that creates callable to fetch the object from context and then nested attributes.
36
+
37
+ Useful for composing breadcrumbs. For example:
38
+ ```
39
+ context = Context({"object": Device.objects.first()})
40
+ breadcrumbs = Breadcrumbs(items={"detail": [
41
+ InstanceBreadcrumbItem(instance=context_object_attr("location.location_type")),
42
+ InstanceBreadcrumbItem(instance=context_object_attr("location")),
43
+ ]})
44
+ ```
45
+ Will render:
46
+ - url to the device.location.location_type detail page
47
+ - url to the device.location detail page
48
+
49
+ Examples:
50
+ >>> context_object_attr("location.name")
51
+ lambda context: context['object'].location.name
52
+ >>> context_object_attr("location.name", context_key="device")
53
+ lambda context: context['device'].location.name
54
+
55
+ """
56
+ return lambda context: attrgetter(attr_path)(context[context_key]) if context.get(context_key) else None
57
+
58
+
30
59
  @dataclass
31
60
  class BaseBreadcrumbItem:
32
61
  """
@@ -128,17 +157,17 @@ class BaseBreadcrumbItem:
128
157
  """
129
158
  Construct the (URL, label) pair for the breadcrumb.
130
159
 
131
- Combines `get_url()` and `get_label()` and applies title casing to the label.
160
+ Combines `get_url()` and `get_label()` and applies formatting to the label.
132
161
 
133
162
  Args:
134
163
  context (Context): Context object used to resolve the breadcrumb parts.
135
164
 
136
165
  Returns:
137
166
  tuple[str, Optional[str]]: A tuple of (URL, label), where URL may be an empty string
138
- if unresolved, and label is title-cased.
167
+ if unresolved, and label.
139
168
  """
140
169
  url = self.get_url(context) or ""
141
- label = helpers.bettertitle(self.get_label(context))
170
+ label = self.get_label(context)
142
171
  return url, label
143
172
 
144
173
 
@@ -192,9 +221,14 @@ class ViewNameBreadcrumbItem(BaseBreadcrumbItem):
192
221
 
193
222
  def get_label(self, context: Context) -> str:
194
223
  if self.label_from_view_name:
195
- model = get_model_for_view_name(self.get_view_name(context))
196
- if model is not None:
197
- return model._meta.verbose_name_plural
224
+ try:
225
+ model = get_model_for_view_name(self.get_view_name(context))
226
+ if model is not None:
227
+ return helpers.bettertitle(model._meta.verbose_name_plural)
228
+ except ValueError:
229
+ # `get_model_for_view_name` is not working properly with some proper paths like "home"
230
+ # and because by default we're trying to resolve label by using `list_url` this error may occur in some apps
231
+ pass
198
232
  return super().get_label(context)
199
233
 
200
234
  def get_view_name(self, context: Context) -> Optional[str]:
@@ -217,7 +251,7 @@ class ModelBreadcrumbItem(BaseBreadcrumbItem):
217
251
 
218
252
  Attributes:
219
253
  model (Union[str, Type[Model], None, Callable[[Context], Union[str, Type[Model], None]]): Django model class, instance, or dotted path string or callable that returns one of this.
220
- model_key (Optional[str]): Context key to fetch a model class, instance or dotted path string.
254
+ model_key (Optional[str]): Context key to fetch a model class, instance or dotted path string. By default: "object".
221
255
  action (str): Action to use when resolving a model-based route (default: "list").
222
256
  label_type (Literal["singular", "plural"]): Whether to use `verbose_name` or `verbose_name_plural`.
223
257
  reverse_kwargs (Union[dict[str, Any], Callable[[Context], dict[str, Any]], None]): Keyword arguments passed to `reverse()`.
@@ -236,7 +270,7 @@ class ModelBreadcrumbItem(BaseBreadcrumbItem):
236
270
  """
237
271
 
238
272
  model: Union[ModelType, Callable[[Context], ModelType]] = None
239
- model_key: Optional[str] = None
273
+ model_key: Optional[str] = "object"
240
274
  action: str = "list"
241
275
  label_type: ModelLabelType = "plural"
242
276
  reverse_kwargs: ReverseParams = None
@@ -276,21 +310,26 @@ class ModelBreadcrumbItem(BaseBreadcrumbItem):
276
310
 
277
311
  model_obj = self.get_model(context)
278
312
  name_attr = "verbose_name" if self.label_type == "singular" else "verbose_name_plural"
313
+ label = ""
279
314
 
280
315
  if model_obj is not None:
281
316
  if isinstance(model_obj, str):
282
317
  model_cls = get_model_from_name(model_obj)
283
- return getattr(model_cls._meta, name_attr)
284
- return getattr(model_obj._meta, name_attr)
285
- return ""
318
+ label = getattr(model_cls._meta, name_attr)
319
+ else:
320
+ label = getattr(model_obj._meta, name_attr)
321
+
322
+ return helpers.bettertitle(label)
286
323
 
287
324
  def get_model(self, context: Context) -> ModelType:
288
- if self.model_key:
289
- return context.get(self.model_key)
290
325
  if self.model:
291
326
  if callable(self.model):
292
327
  return self.model(context)
293
328
  return self.model
329
+
330
+ if self.model_key:
331
+ return context.get(self.model_key)
332
+
294
333
  return None
295
334
 
296
335
 
@@ -303,8 +342,8 @@ class InstanceBreadcrumbItem(BaseBreadcrumbItem):
303
342
  Label will be generated from object, but you can still override it.
304
343
 
305
344
  Attributes:
306
- instance_key (Optional[str]): Context key to fetch a Django model instance for building the breadcrumb.
307
- instance (Callable[[Context], Optional[Model]): Callable to fetch the instance from context. If
345
+ instance_key (Optional[str]): Context key to fetch a Django model instance for building the breadcrumb. Default: "object".
346
+ instance (Union[Callable[[Context], Optional[Model]], Model, None]): Callable to fetch the instance from context. If
308
347
  should_render (Callable[[Context], bool]): Callable to decide whether this item should be rendered or not.
309
348
  label (Union[Callable[[Context], str], WithStr, None]): Optional override for the display label in the breadcrumb.
310
349
  label_key (Optional[str]): Optional key to take label from the context.
@@ -316,8 +355,8 @@ class InstanceBreadcrumbItem(BaseBreadcrumbItem):
316
355
  ("/dcim/devices/1234", "Custom Device Label") # Assuming that under "object" there is a Device instance
317
356
  """
318
357
 
319
- instance_key: str = "object"
320
- instance: Optional[Callable[[Context], Optional[Model]]] = None
358
+ instance_key: Optional[str] = "object"
359
+ instance: Union[Callable[[Context], Optional[Model]], Model, None] = None
321
360
  label: Union[Callable[[Context], str], WithStr, None] = None
322
361
 
323
362
  def get_url(self, context: Context) -> Optional[str]:
@@ -361,9 +400,95 @@ class InstanceBreadcrumbItem(BaseBreadcrumbItem):
361
400
  Optional[Model]: Instance from context.
362
401
  """
363
402
  if self.instance:
364
- return self.instance(context)
403
+ if callable(self.instance):
404
+ return self.instance(context)
405
+ return self.instance
406
+
407
+ if self.instance_key:
408
+ return context.get(self.instance_key)
409
+
410
+ return None
411
+
412
+
413
+ @dataclass
414
+ class InstanceParentBreadcrumbItem(InstanceBreadcrumbItem):
415
+ """
416
+ Breadcrumb item prepared to be "parent" or "group" of given model.
417
+
418
+ It will create a breadcrumb item to the object list but will try to filter it by given parent.
419
+
420
+ Attributes:
421
+ parent_key (str): Instance attribute to get the parent instance. Default: "parent".
422
+ parent_query_param (Optional[str]): Query param name under which parent lookup key will be added into breadcrumb url. If None, will be the same as `parent_key`.
423
+ parent_lookup_key (Optional[str]): Parent attribute which will be used to build the url and filter the instance list url. Can be set to None to use parent as key.
424
+
425
+ Examples:
426
+ >>> InstanceParentBreadcrumbItem()
427
+ ("/dcim/devices?parent=<parent.pk>", "Parent") # Assuming that under "object" there is a Device instance and has "parent"
428
+ >>> InstanceParentBreadcrumbItem(parent_key="group", parent_lookup_key="name")
429
+ ("/dcim/devices?group=<group_name>", "Group") # Assuming that under "object" there is a Device instance and has "group"
430
+ """
431
+
432
+ parent_key: str = "parent"
433
+ parent_query_param: Optional[str] = None
434
+ parent_lookup_key: Optional[str] = "pk"
435
+
436
+ def __post_init__(self):
437
+ """
438
+ Set `parent_query_param` if not filled.
439
+ """
440
+ if self.parent_query_param is None:
441
+ self.parent_query_param = self.parent_key
442
+
443
+ def get_url(self, context: Context) -> Optional[str]:
444
+ """
445
+ Resolve the URL for the breadcrumb item based on the instance and provided parent.
446
+
447
+ Args:
448
+ context (Context): The current template context.
449
+
450
+ Returns:
451
+ Optional[str]: The URL as a string, or None.
452
+ """
453
+ instance = self.get_instance(context)
454
+ parent = self.get_parent_attr(instance)
455
+ if not instance or not parent:
456
+ return None
457
+
458
+ view_name = lookup.get_route_for_model(instance, "list")
459
+ return self.reverse_view_name(view_name, context, reverse_query_params=self.get_reverse_query_params(parent))
460
+
461
+ def get_label(self, context: Context) -> str:
462
+ """
463
+ Get the label (display text) for the breadcrumb from instance and provided parent.
464
+
465
+ Args:
466
+ context (Context): The current template context.
365
467
 
366
- return context.get(self.instance_key)
468
+ Returns:
469
+ str: Label as a string.
470
+ """
471
+ if self.label or self.label_key:
472
+ return super().get_label(context)
473
+ instance = self.get_instance(context)
474
+ parent = self.get_parent_attr(instance)
475
+ if not instance or not parent:
476
+ return ""
477
+ return getattr(parent, "display", str(parent))
478
+
479
+ def get_reverse_query_params(self, parent: Model) -> Optional[dict]:
480
+ if self.parent_lookup_key is None:
481
+ return {self.parent_query_param: parent}
482
+
483
+ if hasattr(parent, self.parent_lookup_key):
484
+ if query_param := getattr(parent, self.parent_lookup_key):
485
+ return {self.parent_query_param: query_param}
486
+ return {}
487
+
488
+ def get_parent_attr(self, instance: Model) -> Optional[Model]:
489
+ if hasattr(instance, self.parent_key):
490
+ return getattr(instance, self.parent_key) or None
491
+ return None
367
492
 
368
493
 
369
494
  class Breadcrumbs:
@@ -401,6 +526,7 @@ class Breadcrumbs:
401
526
  # Default breadcrumb if view defines `list_url` in the Context
402
527
  ViewNameBreadcrumbItem(
403
528
  view_name_key="list_url",
529
+ label_key="title",
404
530
  label_from_view_name=True,
405
531
  should_render=lambda context: context.get("list_url") is not None,
406
532
  ),
@@ -411,7 +537,8 @@ class Breadcrumbs:
411
537
  def __init__(
412
538
  self,
413
539
  items: BreadcrumbItemsType = None,
414
- template: str = "inc/breadcrumbs.html",
540
+ template: str = "components/breadcrumbs.html",
541
+ detail_item_label: LabelType = None,
415
542
  ):
416
543
  """
417
544
  Initialize the Breadcrumbs configuration.
@@ -419,8 +546,10 @@ class Breadcrumbs:
419
546
  Args:
420
547
  items (Optional[dict[str, list[BreadcrumbItem]]]): Default breadcrumb items for each action.
421
548
  template (str): The template used to render the breadcrumbs.
549
+ detail_item_label (Union[Callable[[Context], str], WithStr, None]): Custom label for last built-in breadcrumb item with link to the object details.
422
550
  """
423
551
  self.template = template
552
+ self.detail_item_label = detail_item_label
424
553
 
425
554
  # Set the default breadcrumbs
426
555
  self.items = {
@@ -433,7 +562,7 @@ class Breadcrumbs:
433
562
  self.items = {**self.items, **items}
434
563
 
435
564
  # Built-in feature: always add the instance details at the end of breadcrumbs path
436
- self.items["detail"].append(InstanceBreadcrumbItem())
565
+ self.items["detail"].append(InstanceBreadcrumbItem(label=detail_item_label))
437
566
 
438
567
  def get_breadcrumbs_items(self, context: Context) -> list[tuple[str, str]]:
439
568
  """
@@ -447,9 +576,9 @@ class Breadcrumbs:
447
576
  Returns:
448
577
  (list[tuple[str, str]]): A list of (url, label) tuples representing breadcrumb entries.
449
578
  """
450
- action = context.get("view_action", "list")
579
+ action = context.get("view_action", "")
451
580
  detail = context.get("detail", False)
452
- items = self.get_items_for_action(self.items, action, detail)
581
+ items = self.get_items_for_action(self.items, action, detail, context)
453
582
  return [item.as_pair(context) for item in items if item.should_render(context)]
454
583
 
455
584
  def filter_breadcrumbs_items(self, items: list[tuple[str, str]], context: Context) -> list[tuple[str, str]]:
@@ -457,7 +586,7 @@ class Breadcrumbs:
457
586
  Filters out all items that both label and url are None or empty str.
458
587
 
459
588
  Args:
460
- items (list[tuple[str, str]]): breadcrumb items pair.s
589
+ items (list[tuple[str, str]]): breadcrumb items pairs.
461
590
  context (Context): The view or template context.
462
591
 
463
592
  Returns:
@@ -478,16 +607,17 @@ class Breadcrumbs:
478
607
  """
479
608
  return label and label.strip()
480
609
 
481
- @staticmethod
482
- def get_items_for_action(items: BreadcrumbItemsType, action: str, detail: bool) -> list[BaseBreadcrumbItem]:
610
+ def get_items_for_action(
611
+ self, items: BreadcrumbItemsType, action: str, detail: bool, context: Context
612
+ ) -> list[BaseBreadcrumbItem]:
483
613
  """
484
- Get the breadcrumb items for a specific action, with fallback to 'detail' if not found
485
- and to asterisk (*) if present.
614
+ Get the breadcrumb items for a specific action, 'detail' or to asterisk (*) if present.
486
615
 
487
616
  Args:
488
617
  items (BreadcrumbItemsType): Dictionary mapping action names to breadcrumb item lists.
489
618
  action (str): Current action name (e.g., "list", "detail").
490
619
  detail (bool): Whether this is a detail view (for fallback).
620
+ context (Context): The view or template context.
491
621
 
492
622
  Returns:
493
623
  list[BaseBreadcrumbItem]: List of breadcrumb items for the action.
@@ -536,3 +666,65 @@ class Breadcrumbs:
536
666
  (dict): A dictionary of extra context variables.
537
667
  """
538
668
  return {}
669
+
670
+
671
+ class AncestorsBreadcrumbs(Breadcrumbs):
672
+ """
673
+ Breadcrumbs class which can render list of ancestors of given instance.
674
+
675
+ Default behavior:
676
+ - render breadcrumb item with link to the list view
677
+ - dynamically add list of `InstanceBreadcrumbItem` from `ancestors()`
678
+ - adds standard breadcrumb item with link to the details
679
+ """
680
+
681
+ def get_items_for_action(
682
+ self, items: BreadcrumbItemsType, action: str, detail: bool, context: Context
683
+ ) -> list[BaseBreadcrumbItem]:
684
+ """
685
+ Get the breadcrumb items for a specific action, 'detail' or to asterisk (*) if present.
686
+
687
+ For detail action it calls `self.get_detail_items()` method. For other actions, it calls parent class method.
688
+
689
+ Args:
690
+ items (BreadcrumbItemsType): Dictionary mapping action names to breadcrumb item lists.
691
+ action (str): Current action name (e.g., "list", "detail").
692
+ detail (bool): Whether this is a detail view (for fallback).
693
+ context (Context): The view or template context.
694
+
695
+ Returns:
696
+ list[BaseBreadcrumbItem]: List of breadcrumb items for the action.
697
+ """
698
+ if not detail:
699
+ return super().get_items_for_action(items, action, detail, context)
700
+
701
+ return self.get_detail_items(context)
702
+
703
+ def get_detail_items(self, context: Context) -> list[BaseBreadcrumbItem]:
704
+ """
705
+ Build breadcrumb items for a detail view, including the model, its ancestors, and the instance itself.
706
+
707
+ Args:
708
+ context (Context): Template or view context containing the current object.
709
+
710
+ Returns:
711
+ list[BaseBreadcrumbItem]: Ordered breadcrumb items for the given instance.
712
+ """
713
+ instance = get_obj_from_context(context)
714
+ return [
715
+ ModelBreadcrumbItem(model=instance),
716
+ *self.get_ancestors_items(instance),
717
+ InstanceBreadcrumbItem(instance=instance, label=self.detail_item_label),
718
+ ]
719
+
720
+ def get_ancestors_items(self, instance: TreeModel) -> list[BaseBreadcrumbItem]:
721
+ """
722
+ Create breadcrumb items for all ancestor objects of the given instance.
723
+
724
+ Args:
725
+ instance (TreeModel): Object from context.
726
+
727
+ Returns:
728
+ list[BaseBreadcrumbItem]: Breadcrumb items for each ancestor of the instance.
729
+ """
730
+ return [InstanceBreadcrumbItem(instance=ancestor, label=ancestor.name) for ancestor in instance.ancestors()]