nautobot 2.3.16__py3-none-any.whl → 2.4.0__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 (721) hide show
  1. nautobot/__init__.py +15 -0
  2. nautobot/apps/__init__.py +1 -1
  3. nautobot/apps/api.py +8 -10
  4. nautobot/apps/change_logging.py +2 -2
  5. nautobot/apps/choices.py +4 -4
  6. nautobot/apps/config.py +32 -3
  7. nautobot/apps/events.py +19 -0
  8. nautobot/apps/exceptions.py +0 -2
  9. nautobot/apps/factory.py +2 -2
  10. nautobot/apps/filters.py +1 -1
  11. nautobot/apps/forms.py +20 -20
  12. nautobot/apps/graphql.py +2 -2
  13. nautobot/apps/jobs.py +8 -8
  14. nautobot/apps/models.py +19 -19
  15. nautobot/apps/tables.py +1 -1
  16. nautobot/apps/testing.py +10 -10
  17. nautobot/apps/ui.py +44 -9
  18. nautobot/apps/utils.py +7 -15
  19. nautobot/apps/views.py +8 -6
  20. nautobot/circuits/api/serializers.py +1 -0
  21. nautobot/circuits/api/views.py +4 -8
  22. nautobot/circuits/navigation.py +0 -57
  23. nautobot/circuits/templates/circuits/circuit_create.html +1 -7
  24. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -71
  25. nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -64
  26. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +40 -0
  27. nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +26 -0
  28. nautobot/circuits/templates/circuits/provider_retrieve.html +0 -76
  29. nautobot/circuits/tests/integration/test_relationships.py +33 -24
  30. nautobot/circuits/tests/test_filters.py +4 -8
  31. nautobot/circuits/views.py +140 -23
  32. nautobot/cloud/api/views.py +6 -10
  33. nautobot/cloud/factory.py +4 -1
  34. nautobot/cloud/tests/test_filters.py +5 -4
  35. nautobot/cloud/views.py +0 -16
  36. nautobot/core/api/constants.py +11 -0
  37. nautobot/core/api/filter_backends.py +3 -9
  38. nautobot/core/api/metadata.py +28 -256
  39. nautobot/core/api/pagination.py +3 -2
  40. nautobot/core/api/renderers.py +3 -0
  41. nautobot/core/api/schema.py +13 -2
  42. nautobot/core/api/serializers.py +45 -259
  43. nautobot/core/api/urls.py +3 -4
  44. nautobot/core/api/utils.py +0 -62
  45. nautobot/core/api/views.py +99 -157
  46. nautobot/core/apps/__init__.py +22 -578
  47. nautobot/core/celery/__init__.py +13 -0
  48. nautobot/core/celery/schedulers.py +47 -2
  49. nautobot/core/choices.py +2 -2
  50. nautobot/core/cli/__init__.py +8 -0
  51. nautobot/core/constants.py +7 -0
  52. nautobot/core/events/__init__.py +116 -0
  53. nautobot/core/events/base.py +27 -0
  54. nautobot/core/events/exceptions.py +10 -0
  55. nautobot/core/events/redis_broker.py +48 -0
  56. nautobot/core/events/syslog_broker.py +19 -0
  57. nautobot/core/exceptions.py +0 -6
  58. nautobot/core/forms/__init__.py +19 -19
  59. nautobot/core/forms/fields.py +57 -9
  60. nautobot/core/forms/forms.py +33 -2
  61. nautobot/core/forms/utils.py +2 -1
  62. nautobot/core/graphql/schema.py +3 -1
  63. nautobot/core/jobs/__init__.py +24 -3
  64. nautobot/core/jobs/bulk_actions.py +248 -0
  65. nautobot/core/jobs/cleanup.py +1 -1
  66. nautobot/core/management/commands/generate_test_data.py +21 -0
  67. nautobot/core/middleware.py +16 -0
  68. nautobot/core/models/fields.py +11 -7
  69. nautobot/core/settings.py +68 -4
  70. nautobot/core/settings.yaml +99 -0
  71. nautobot/core/tables.py +10 -46
  72. nautobot/core/tasks.py +1 -1
  73. nautobot/core/templates/about.html +67 -0
  74. nautobot/core/templates/components/button/default.html +7 -0
  75. nautobot/core/templates/components/button/dropdown.html +20 -0
  76. nautobot/core/templates/components/layout/one_over_two.html +19 -0
  77. nautobot/core/templates/components/layout/two_over_one.html +19 -0
  78. nautobot/core/templates/components/panel/body_content_data_table.html +27 -0
  79. nautobot/core/templates/components/panel/body_content_objects_table.html +4 -0
  80. nautobot/core/templates/components/panel/body_content_tags.html +6 -0
  81. nautobot/core/templates/components/panel/body_content_text.html +12 -0
  82. nautobot/core/templates/components/panel/body_wrapper_generic.html +3 -0
  83. nautobot/core/templates/components/panel/body_wrapper_key_value_table.html +3 -0
  84. nautobot/core/templates/components/panel/body_wrapper_table.html +3 -0
  85. nautobot/core/templates/components/panel/footer_contacts_table.html +20 -0
  86. nautobot/core/templates/components/panel/footer_content_table.html +14 -0
  87. nautobot/core/templates/components/panel/grouping_toggle.html +14 -0
  88. nautobot/core/templates/components/panel/header_extra_content_table.html +3 -0
  89. nautobot/core/templates/components/panel/panel.html +16 -0
  90. nautobot/core/templates/components/panel/stats_panel_body.html +8 -0
  91. nautobot/core/templates/components/tab/content_wrapper.html +3 -0
  92. nautobot/core/templates/components/tab/label_wrapper.html +5 -0
  93. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +3 -0
  94. nautobot/core/templates/generic/object_retrieve.html +28 -17
  95. nautobot/core/templates/inc/computed_fields/panel_data.html +4 -7
  96. nautobot/core/templates/inc/custom_fields/panel.html +2 -2
  97. nautobot/core/templates/inc/custom_fields/panel_data.html +4 -7
  98. nautobot/core/templates/inc/footer.html +1 -0
  99. nautobot/core/templates/inc/nav_menu.html +2 -1
  100. nautobot/core/templates/inc/relationships_panel.html +1 -1
  101. nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
  102. nautobot/core/templates/inc/tenant_table_row.html +11 -0
  103. nautobot/core/templates/nautobot_config.py.j2 +13 -0
  104. nautobot/core/templates/panel_table.html +12 -0
  105. nautobot/core/templates/utilities/render_jinja2.html +117 -0
  106. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  107. nautobot/core/templates/utilities/theme_preview.html +7 -0
  108. nautobot/core/templatetags/helpers.py +104 -6
  109. nautobot/core/templatetags/ui_framework.py +40 -0
  110. nautobot/core/testing/__init__.py +8 -8
  111. nautobot/core/testing/api.py +187 -137
  112. nautobot/core/testing/context.py +18 -0
  113. nautobot/core/testing/filters.py +41 -35
  114. nautobot/core/testing/forms.py +2 -0
  115. nautobot/core/testing/views.py +65 -148
  116. nautobot/core/tests/integration/test_view_authentication.py +1 -1
  117. nautobot/core/tests/nautobot_config.py +198 -0
  118. nautobot/core/tests/runner.py +2 -2
  119. nautobot/core/tests/test_api.py +154 -176
  120. nautobot/core/tests/test_events.py +214 -0
  121. nautobot/core/tests/test_forms.py +1 -0
  122. nautobot/core/tests/test_jinja_filters.py +1 -0
  123. nautobot/core/tests/test_jobs.py +387 -14
  124. nautobot/core/tests/test_navigations.py +7 -241
  125. nautobot/core/tests/test_settings_schema.py +7 -0
  126. nautobot/core/tests/test_tables.py +100 -0
  127. nautobot/core/tests/test_templatetags_helpers.py +16 -0
  128. nautobot/core/tests/test_ui.py +150 -0
  129. nautobot/core/tests/test_utils.py +55 -18
  130. nautobot/core/tests/test_views.py +124 -5
  131. nautobot/core/ui/__init__.py +0 -0
  132. nautobot/core/ui/base.py +11 -0
  133. nautobot/core/ui/choices.py +44 -0
  134. nautobot/core/ui/homepage.py +167 -0
  135. nautobot/core/ui/nav.py +280 -0
  136. nautobot/core/ui/object_detail.py +1855 -0
  137. nautobot/core/ui/utils.py +36 -0
  138. nautobot/core/urls.py +6 -0
  139. nautobot/core/utils/config.py +30 -3
  140. nautobot/core/utils/lookup.py +12 -2
  141. nautobot/core/utils/querysets.py +64 -0
  142. nautobot/core/utils/requests.py +24 -9
  143. nautobot/core/views/__init__.py +48 -1
  144. nautobot/core/views/generic.py +37 -140
  145. nautobot/core/views/mixins.py +82 -32
  146. nautobot/core/views/paginator.py +8 -5
  147. nautobot/core/views/renderers.py +9 -9
  148. nautobot/core/views/utils.py +11 -0
  149. nautobot/core/wsgi.py +3 -3
  150. nautobot/dcim/api/serializers.py +34 -141
  151. nautobot/dcim/api/urls.py +5 -0
  152. nautobot/dcim/api/views.py +57 -110
  153. nautobot/dcim/apps.py +1 -0
  154. nautobot/dcim/choices.py +28 -0
  155. nautobot/dcim/factory.py +58 -0
  156. nautobot/dcim/filters/__init__.py +204 -2
  157. nautobot/dcim/forms.py +219 -9
  158. nautobot/dcim/migrations/0063_interfacevdcassignment_virtualdevicecontext_and_more.py +165 -0
  159. nautobot/dcim/migrations/0064_virtualdevicecontext_status_data_migration.py +28 -0
  160. nautobot/dcim/migrations/0065_controller_capabilities_and_more.py +29 -0
  161. nautobot/dcim/migrations/0066_controllermanageddevicegroup_radio_profiles_and_more.py +33 -0
  162. nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
  163. nautobot/dcim/models/__init__.py +5 -1
  164. nautobot/dcim/models/devices.py +180 -2
  165. nautobot/dcim/models/racks.py +2 -2
  166. nautobot/dcim/navigation.py +25 -224
  167. nautobot/dcim/signals.py +44 -0
  168. nautobot/dcim/tables/__init__.py +2 -0
  169. nautobot/dcim/tables/devices.py +103 -7
  170. nautobot/dcim/tables/racks.py +1 -1
  171. nautobot/dcim/templates/dcim/controller/base.html +10 -0
  172. nautobot/dcim/templates/dcim/controller_create.html +2 -7
  173. nautobot/dcim/templates/dcim/controller_retrieve.html +6 -10
  174. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +25 -0
  175. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +68 -0
  176. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +51 -0
  177. nautobot/dcim/templates/dcim/device/base.html +6 -42
  178. nautobot/dcim/templates/dcim/device/wireless.html +73 -0
  179. nautobot/dcim/templates/dcim/device.html +4 -10
  180. nautobot/dcim/templates/dcim/device_edit.html +36 -37
  181. nautobot/dcim/templates/dcim/interface.html +1 -0
  182. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  183. nautobot/dcim/templates/dcim/location.html +1 -9
  184. nautobot/dcim/templates/dcim/location_edit.html +1 -7
  185. nautobot/dcim/templates/dcim/locationtype.html +0 -107
  186. nautobot/dcim/templates/dcim/locationtype_retrieve.html +8 -0
  187. nautobot/dcim/templates/dcim/rack.html +1 -9
  188. nautobot/dcim/templates/dcim/rack_edit.html +1 -7
  189. nautobot/dcim/templates/dcim/rackreservation.html +1 -9
  190. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +68 -0
  191. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +28 -0
  192. nautobot/dcim/tests/integration/test_controller.py +62 -0
  193. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
  194. nautobot/dcim/tests/test_api.py +188 -64
  195. nautobot/dcim/tests/test_filters.py +171 -76
  196. nautobot/dcim/tests/test_jobs.py +118 -0
  197. nautobot/dcim/tests/test_models.py +157 -6
  198. nautobot/dcim/tests/test_signals.py +1 -0
  199. nautobot/dcim/tests/test_views.py +118 -88
  200. nautobot/dcim/urls.py +72 -27
  201. nautobot/dcim/utils.py +2 -2
  202. nautobot/dcim/views.py +356 -61
  203. nautobot/extras/api/serializers.py +39 -18
  204. nautobot/extras/api/urls.py +4 -0
  205. nautobot/extras/api/views.py +89 -31
  206. nautobot/extras/choices.py +13 -0
  207. nautobot/extras/constants.py +2 -1
  208. nautobot/extras/context_managers.py +23 -6
  209. nautobot/extras/datasources/git.py +4 -1
  210. nautobot/extras/factory.py +27 -0
  211. nautobot/extras/filters/__init__.py +66 -5
  212. nautobot/extras/forms/base.py +2 -2
  213. nautobot/extras/forms/forms.py +262 -59
  214. nautobot/extras/forms/mixins.py +2 -2
  215. nautobot/extras/graphql/types.py +25 -1
  216. nautobot/extras/jobs.py +109 -15
  217. nautobot/extras/management/__init__.py +1 -0
  218. nautobot/extras/management/commands/runjob.py +7 -79
  219. nautobot/extras/management/commands/runjob_with_job_result.py +46 -0
  220. nautobot/extras/management/utils.py +87 -0
  221. nautobot/extras/migrations/0117_create_job_queue_model.py +129 -0
  222. nautobot/extras/migrations/0118_task_queue_to_job_queue_migration.py +78 -0
  223. nautobot/extras/migrations/0119_remove_task_queues_from_job_and_queue_from_scheduled_job.py +28 -0
  224. nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
  225. nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
  226. nautobot/extras/models/__init__.py +5 -1
  227. nautobot/extras/models/change_logging.py +7 -3
  228. nautobot/extras/models/contacts.py +1 -1
  229. nautobot/extras/models/groups.py +0 -2
  230. nautobot/extras/models/jobs.py +233 -33
  231. nautobot/extras/models/relationships.py +69 -1
  232. nautobot/extras/models/secrets.py +5 -0
  233. nautobot/extras/navigation.py +20 -262
  234. nautobot/extras/plugins/__init__.py +54 -19
  235. nautobot/extras/plugins/marketplace_manifest.yml +455 -0
  236. nautobot/extras/plugins/tables.py +16 -14
  237. nautobot/extras/plugins/urls.py +1 -0
  238. nautobot/extras/plugins/views.py +103 -60
  239. nautobot/extras/registry.py +1 -1
  240. nautobot/extras/secrets/__init__.py +2 -2
  241. nautobot/extras/signals.py +39 -1
  242. nautobot/extras/tables.py +37 -1
  243. nautobot/extras/templates/extras/dynamicgroup.html +1 -9
  244. nautobot/extras/templates/extras/externalintegration_retrieve.html +0 -47
  245. nautobot/extras/templates/extras/inc/tags_panel.html +1 -5
  246. nautobot/extras/templates/extras/job_bulk_edit.html +2 -1
  247. nautobot/extras/templates/extras/job_detail.html +52 -6
  248. nautobot/extras/templates/extras/job_edit.html +6 -2
  249. nautobot/extras/templates/extras/job_list.html +2 -7
  250. nautobot/extras/templates/extras/jobqueue_retrieve.html +36 -0
  251. nautobot/extras/templates/extras/marketplace.html +296 -0
  252. nautobot/extras/templates/extras/plugin_detail.html +32 -15
  253. nautobot/extras/templates/extras/plugins_list.html +35 -1
  254. nautobot/extras/templates/extras/plugins_tiles.html +90 -0
  255. nautobot/extras/templates/extras/role_retrieve.html +16 -0
  256. nautobot/extras/templates/extras/secret.html +0 -65
  257. nautobot/extras/templates/extras/secret_check.js +16 -0
  258. nautobot/extras/templates/extras/secret_create.html +114 -0
  259. nautobot/extras/templates/extras/secret_edit.html +1 -114
  260. nautobot/extras/templates/extras/secretsgroup_edit.html +1 -1
  261. nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +2 -0
  262. nautobot/extras/templatetags/job_buttons.py +5 -4
  263. nautobot/extras/templatetags/plugins.py +69 -6
  264. nautobot/extras/test_jobs/singleton.py +16 -0
  265. nautobot/extras/tests/test_api.py +145 -43
  266. nautobot/extras/tests/test_context_managers.py +4 -1
  267. nautobot/extras/tests/test_filters.py +213 -529
  268. nautobot/extras/tests/test_job_variables.py +73 -152
  269. nautobot/extras/tests/test_jobs.py +181 -51
  270. nautobot/extras/tests/test_models.py +61 -6
  271. nautobot/extras/tests/test_plugins.py +62 -9
  272. nautobot/extras/tests/test_relationships.py +123 -9
  273. nautobot/extras/tests/test_utils.py +23 -2
  274. nautobot/extras/tests/test_views.py +146 -145
  275. nautobot/extras/tests/test_webhooks.py +2 -1
  276. nautobot/extras/urls.py +2 -20
  277. nautobot/extras/utils.py +119 -4
  278. nautobot/extras/views.py +168 -125
  279. nautobot/extras/webhooks.py +5 -2
  280. nautobot/ipam/api/serializers.py +10 -103
  281. nautobot/ipam/api/views.py +31 -49
  282. nautobot/ipam/factory.py +1 -1
  283. nautobot/ipam/filters.py +3 -2
  284. nautobot/ipam/models.py +10 -12
  285. nautobot/ipam/navigation.py +0 -90
  286. nautobot/ipam/tables.py +3 -1
  287. nautobot/ipam/templates/ipam/ipaddress.html +1 -9
  288. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
  289. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
  290. nautobot/ipam/templates/ipam/prefix.html +1 -9
  291. nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
  292. nautobot/ipam/templates/ipam/routetarget.html +0 -28
  293. nautobot/ipam/templates/ipam/vlan.html +1 -9
  294. nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
  295. nautobot/ipam/templates/ipam/vrf.html +0 -47
  296. nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
  297. nautobot/ipam/tests/test_api.py +7 -5
  298. nautobot/ipam/tests/test_filters.py +39 -119
  299. nautobot/ipam/tests/test_forms.py +0 -2
  300. nautobot/ipam/tests/test_models.py +56 -36
  301. nautobot/ipam/tests/test_views.py +3 -0
  302. nautobot/ipam/urls.py +3 -69
  303. nautobot/ipam/utils/__init__.py +16 -10
  304. nautobot/ipam/views.py +91 -162
  305. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
  306. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
  307. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
  308. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
  309. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
  310. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
  311. nautobot/project-static/css/base.css +38 -3
  312. nautobot/project-static/docs/404.html +461 -17
  313. nautobot/project-static/docs/apps/index.html +461 -17
  314. nautobot/project-static/docs/apps/nautobot-apps.html +462 -19
  315. nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
  316. nautobot/project-static/docs/assets/extra.css +5 -1
  317. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +477 -23
  318. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +474 -20
  319. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +790 -289
  320. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +505 -31
  321. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +510 -34
  322. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +471 -20
  323. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +467 -18
  324. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +497 -33
  325. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9883 -0
  326. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +523 -75
  327. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +546 -51
  328. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +670 -94
  329. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1030 -177
  330. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +524 -49
  331. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +874 -188
  332. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +955 -235
  333. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +475 -21
  334. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +486 -28
  335. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +661 -99
  336. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +947 -479
  337. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +6425 -1234
  338. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +474 -20
  339. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +877 -344
  340. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +828 -171
  341. nautobot/project-static/docs/development/apps/api/configuration-view.html +461 -17
  342. nautobot/project-static/docs/development/apps/api/database-backend-config.html +461 -17
  343. nautobot/project-static/docs/development/apps/api/models/django-admin.html +461 -17
  344. nautobot/project-static/docs/development/apps/api/models/global-search.html +461 -17
  345. nautobot/project-static/docs/development/apps/api/models/graphql.html +461 -17
  346. nautobot/project-static/docs/development/apps/api/models/index.html +461 -17
  347. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +461 -17
  348. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +461 -17
  349. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +461 -17
  350. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +461 -17
  351. nautobot/project-static/docs/development/apps/api/platform-features/index.html +461 -17
  352. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +461 -17
  353. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +461 -17
  354. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +461 -17
  355. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +461 -17
  356. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +461 -17
  357. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +461 -17
  358. nautobot/project-static/docs/development/apps/api/prometheus.html +461 -17
  359. nautobot/project-static/docs/development/apps/api/setup.html +465 -153
  360. nautobot/project-static/docs/development/apps/api/testing.html +461 -17
  361. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +461 -17
  362. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +461 -17
  363. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +461 -17
  364. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +461 -17
  365. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +741 -128
  366. nautobot/project-static/docs/development/apps/api/views/base-template.html +461 -17
  367. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +461 -17
  368. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +461 -17
  369. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +461 -17
  370. nautobot/project-static/docs/development/apps/api/views/index.html +463 -18
  371. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +465 -17
  372. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +491 -17
  373. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +461 -17
  374. nautobot/project-static/docs/development/apps/api/views/notes.html +461 -17
  375. nautobot/project-static/docs/development/apps/api/views/rest-api.html +467 -19
  376. nautobot/project-static/docs/development/apps/api/views/urls.html +461 -17
  377. nautobot/project-static/docs/development/apps/index.html +461 -17
  378. nautobot/project-static/docs/development/apps/migration/code-updates.html +462 -50
  379. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +462 -18
  380. nautobot/project-static/docs/development/apps/migration/from-v1.html +461 -17
  381. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +461 -17
  382. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +461 -17
  383. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +461 -17
  384. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +464 -20
  385. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9261 -0
  386. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +9375 -0
  387. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +9671 -0
  388. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +9559 -0
  389. nautobot/project-static/docs/development/apps/porting-from-netbox.html +464 -20
  390. nautobot/project-static/docs/development/core/application-registry.html +461 -17
  391. nautobot/project-static/docs/development/core/best-practices.html +461 -17
  392. nautobot/project-static/docs/development/core/bootstrap-ui.html +461 -17
  393. nautobot/project-static/docs/development/core/caching.html +461 -17
  394. nautobot/project-static/docs/development/core/controllers.html +463 -17
  395. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +464 -20
  396. nautobot/project-static/docs/development/core/generic-views.html +461 -17
  397. nautobot/project-static/docs/development/core/getting-started.html +539 -127
  398. nautobot/project-static/docs/development/core/homepage.html +472 -28
  399. nautobot/project-static/docs/development/core/index.html +461 -17
  400. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9754 -0
  401. nautobot/project-static/docs/development/core/model-checklist.html +471 -25
  402. nautobot/project-static/docs/development/core/model-features.html +461 -17
  403. nautobot/project-static/docs/development/core/natural-keys.html +461 -17
  404. nautobot/project-static/docs/development/core/navigation-menu.html +478 -24
  405. nautobot/project-static/docs/development/core/release-checklist.html +478 -46
  406. nautobot/project-static/docs/development/core/role-internals.html +461 -17
  407. nautobot/project-static/docs/development/core/settings.html +461 -17
  408. nautobot/project-static/docs/development/core/style-guide.html +464 -20
  409. nautobot/project-static/docs/development/core/templates.html +471 -20
  410. nautobot/project-static/docs/development/core/testing.html +461 -17
  411. nautobot/project-static/docs/development/core/ui-component-framework.html +11116 -0
  412. nautobot/project-static/docs/development/core/user-preferences.html +464 -20
  413. nautobot/project-static/docs/development/index.html +461 -17
  414. nautobot/project-static/docs/development/jobs/index.html +499 -19
  415. nautobot/project-static/docs/development/jobs/migration/from-v1.html +461 -17
  416. nautobot/project-static/docs/index.html +469 -36
  417. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
  418. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
  419. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
  420. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
  421. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
  422. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
  423. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
  424. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
  425. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
  426. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
  427. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
  428. nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
  429. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
  430. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
  431. nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
  432. nautobot/project-static/docs/media/development/core/ui-component-framework/basic-panel-layout.png +0 -0
  433. nautobot/project-static/docs/media/development/core/ui-component-framework/button-example.png +0 -0
  434. nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
  435. nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
  436. nautobot/project-static/docs/media/development/core/ui-component-framework/dropdown-button-example.png +0 -0
  437. nautobot/project-static/docs/media/development/core/ui-component-framework/grouped-key-value-table-panel-example-1.png +0 -0
  438. nautobot/project-static/docs/media/development/core/ui-component-framework/grouped-key-value-table-panel-example-2.png +0 -0
  439. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example.png +0 -0
  440. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
  441. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
  442. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example.png +0 -0
  443. nautobot/project-static/docs/media/development/core/ui-component-framework/table-panels-family.png +0 -0
  444. nautobot/project-static/docs/media/development/core/ui-component-framework/text-panels-family.png +0 -0
  445. nautobot/project-static/docs/media/development/core/ui-component-framework/ui-framework-example.png +0 -0
  446. nautobot/project-static/docs/media/models/virtual_device_context_overview.drawio +73 -0
  447. nautobot/project-static/docs/media/models/virtual_device_context_overview.png +0 -0
  448. nautobot/project-static/docs/models/dcim/virtualdevicecontext.html +14 -0
  449. nautobot/project-static/docs/models/extras/jobqueue.html +14 -0
  450. nautobot/project-static/docs/models/wireless/radioprofile.html +14 -0
  451. nautobot/project-static/docs/models/wireless/supporteddatarate.html +14 -0
  452. nautobot/project-static/docs/models/wireless/wirelessnetwork.html +14 -0
  453. nautobot/project-static/docs/objects.inv +0 -0
  454. nautobot/project-static/docs/overview/application_stack.html +467 -21
  455. nautobot/project-static/docs/overview/design_philosophy.html +461 -17
  456. nautobot/project-static/docs/release-notes/index.html +483 -20
  457. nautobot/project-static/docs/release-notes/version-1.0.html +649 -206
  458. nautobot/project-static/docs/release-notes/version-1.1.html +646 -203
  459. nautobot/project-static/docs/release-notes/version-1.2.html +721 -278
  460. nautobot/project-static/docs/release-notes/version-1.3.html +747 -304
  461. nautobot/project-static/docs/release-notes/version-1.4.html +832 -390
  462. nautobot/project-static/docs/release-notes/version-1.5.html +1020 -579
  463. nautobot/project-static/docs/release-notes/version-1.6.html +940 -516
  464. nautobot/project-static/docs/release-notes/version-2.0.html +943 -502
  465. nautobot/project-static/docs/release-notes/version-2.1.html +778 -337
  466. nautobot/project-static/docs/release-notes/version-2.2.html +771 -330
  467. nautobot/project-static/docs/release-notes/version-2.3.html +914 -471
  468. nautobot/project-static/docs/release-notes/version-2.4.html +10323 -0
  469. nautobot/project-static/docs/search/search_index.json +1 -1
  470. nautobot/project-static/docs/sitemap.xml +342 -270
  471. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  472. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +461 -17
  473. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +461 -17
  474. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +461 -17
  475. nautobot/project-static/docs/user-guide/administration/configuration/index.html +473 -30
  476. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +461 -17
  477. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +842 -155
  478. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +461 -17
  479. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +461 -17
  480. nautobot/project-static/docs/user-guide/administration/guides/docker.html +474 -27
  481. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +461 -17
  482. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +461 -17
  483. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +461 -17
  484. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +461 -17
  485. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +461 -17
  486. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +463 -19
  487. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +461 -17
  488. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +461 -17
  489. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +461 -17
  490. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +481 -21
  491. nautobot/project-static/docs/user-guide/administration/installation/index.html +466 -18
  492. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +462 -18
  493. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +461 -17
  494. nautobot/project-static/docs/user-guide/administration/installation/services.html +461 -17
  495. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +461 -17
  496. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +482 -39
  497. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +475 -64
  498. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +475 -64
  499. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +461 -17
  500. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +461 -17
  501. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +461 -17
  502. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +461 -17
  503. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +461 -17
  504. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +464 -21
  505. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +461 -17
  506. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-nautobot-app-location.yaml +0 -16
  507. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +461 -17
  508. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +467 -19
  509. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +461 -17
  510. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +461 -17
  511. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +461 -17
  512. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +461 -17
  513. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +461 -17
  514. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +461 -17
  515. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +461 -17
  516. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +461 -17
  517. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +461 -17
  518. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +461 -17
  519. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +461 -17
  520. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +461 -17
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +461 -17
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +461 -17
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +461 -17
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +461 -17
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +461 -17
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +497 -18
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +487 -20
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +461 -17
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +461 -17
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +461 -17
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +461 -17
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +461 -17
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +461 -17
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +461 -17
  535. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +461 -17
  536. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +461 -17
  537. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +461 -17
  538. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +461 -17
  539. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +461 -17
  540. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +461 -17
  541. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +461 -17
  542. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +461 -17
  543. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +461 -17
  544. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +461 -17
  545. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +461 -17
  546. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +461 -17
  547. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +461 -17
  548. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +461 -17
  549. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +461 -17
  550. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +461 -17
  551. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +461 -17
  552. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +461 -17
  553. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +461 -17
  554. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +461 -17
  555. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +461 -17
  556. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +461 -17
  557. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +461 -17
  558. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +461 -17
  559. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +461 -17
  560. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +461 -17
  561. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +461 -17
  562. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9375 -0
  563. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +468 -28
  564. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +461 -17
  565. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +461 -17
  566. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +461 -17
  567. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +461 -17
  568. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +461 -17
  569. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +461 -17
  570. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +461 -17
  571. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +461 -17
  572. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +461 -17
  573. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +461 -17
  574. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +461 -17
  575. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +461 -17
  576. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +464 -20
  577. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +461 -17
  578. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +461 -17
  579. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +461 -17
  580. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +461 -17
  581. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +461 -17
  582. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +461 -17
  583. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +464 -20
  584. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9313 -0
  585. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9217 -0
  586. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9211 -0
  587. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9277 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +461 -17
  589. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +461 -17
  590. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +461 -17
  591. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +461 -17
  592. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +461 -17
  593. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +461 -17
  594. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +461 -17
  595. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +461 -17
  596. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +461 -17
  597. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +461 -17
  598. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +461 -17
  599. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +466 -20
  600. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +461 -17
  601. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
  602. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
  603. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
  604. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
  605. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
  606. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
  607. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
  608. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
  609. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
  610. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
  611. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
  612. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
  613. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
  614. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +461 -17
  615. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +461 -17
  616. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +464 -20
  617. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9444 -0
  618. nautobot/project-static/docs/user-guide/index.html +461 -17
  619. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +464 -20
  620. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +465 -21
  621. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +461 -17
  622. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +461 -17
  623. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +461 -17
  624. nautobot/project-static/docs/user-guide/platform-functionality/events.html +9617 -0
  625. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +464 -20
  626. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +461 -17
  627. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +461 -17
  628. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +461 -17
  629. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +461 -17
  630. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +461 -17
  631. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +470 -21
  632. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +464 -20
  633. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +464 -20
  634. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +461 -17
  635. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9224 -0
  636. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9722 -0
  637. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +464 -20
  638. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +461 -17
  639. nautobot/project-static/docs/user-guide/platform-functionality/note.html +461 -17
  640. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +461 -17
  641. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +465 -21
  642. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9292 -0
  643. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +461 -17
  644. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +509 -38
  645. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +492 -21
  646. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +461 -17
  647. nautobot/project-static/docs/user-guide/platform-functionality/role.html +461 -17
  648. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +461 -17
  649. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +461 -17
  650. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +464 -20
  651. nautobot/project-static/docs/user-guide/platform-functionality/status.html +461 -17
  652. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +461 -17
  653. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +529 -54
  654. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +461 -17
  655. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +461 -17
  656. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +461 -17
  657. nautobot/project-static/img/jinja_logo.svg +97 -0
  658. nautobot/project-static/js/forms.js +6 -1
  659. nautobot/project-static/js/nav_menu.js +2 -1
  660. nautobot/tenancy/api/serializers.py +0 -2
  661. nautobot/tenancy/api/views.py +9 -13
  662. nautobot/tenancy/factory.py +1 -1
  663. nautobot/tenancy/navigation.py +0 -29
  664. nautobot/tenancy/templates/tenancy/tenant.html +4 -91
  665. nautobot/tenancy/tests/test_filters.py +29 -134
  666. nautobot/tenancy/views.py +35 -24
  667. nautobot/users/admin.py +2 -0
  668. nautobot/users/api/views.py +2 -2
  669. nautobot/users/forms.py +19 -0
  670. nautobot/users/templates/users/preferences.html +22 -0
  671. nautobot/users/tests/test_filters.py +1 -19
  672. nautobot/users/tests/test_views.py +57 -0
  673. nautobot/users/utils.py +8 -0
  674. nautobot/users/views.py +48 -11
  675. nautobot/virtualization/api/views.py +5 -24
  676. nautobot/virtualization/filters.py +1 -2
  677. nautobot/virtualization/models.py +1 -1
  678. nautobot/virtualization/navigation.py +0 -48
  679. nautobot/virtualization/tables.py +2 -2
  680. nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
  681. nautobot/virtualization/templates/virtualization/clustertype.html +0 -39
  682. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -9
  683. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
  684. nautobot/virtualization/tests/test_filters.py +57 -166
  685. nautobot/virtualization/views.py +18 -15
  686. nautobot/wireless/__init__.py +0 -0
  687. nautobot/wireless/api/__init__.py +0 -0
  688. nautobot/wireless/api/serializers.py +44 -0
  689. nautobot/wireless/api/urls.py +20 -0
  690. nautobot/wireless/api/views.py +34 -0
  691. nautobot/wireless/apps.py +8 -0
  692. nautobot/wireless/choices.py +345 -0
  693. nautobot/wireless/factory.py +138 -0
  694. nautobot/wireless/filters.py +167 -0
  695. nautobot/wireless/forms.py +283 -0
  696. nautobot/wireless/homepage.py +19 -0
  697. nautobot/wireless/migrations/0001_initial.py +223 -0
  698. nautobot/wireless/migrations/__init__.py +0 -0
  699. nautobot/wireless/models.py +207 -0
  700. nautobot/wireless/navigation.py +105 -0
  701. nautobot/wireless/tables.py +244 -0
  702. nautobot/wireless/templates/wireless/radioprofile_retrieve.html +81 -0
  703. nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +26 -0
  704. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +88 -0
  705. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +56 -0
  706. nautobot/wireless/tests/__init__.py +0 -0
  707. nautobot/wireless/tests/integration/__init__.py +0 -0
  708. nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
  709. nautobot/wireless/tests/test_api.py +247 -0
  710. nautobot/wireless/tests/test_filters.py +82 -0
  711. nautobot/wireless/tests/test_models.py +22 -0
  712. nautobot/wireless/tests/test_views.py +378 -0
  713. nautobot/wireless/urls.py +13 -0
  714. nautobot/wireless/views.py +119 -0
  715. {nautobot-2.3.16.dist-info → nautobot-2.4.0.dist-info}/METADATA +9 -12
  716. {nautobot-2.3.16.dist-info → nautobot-2.4.0.dist-info}/RECORD +720 -549
  717. nautobot/core/utils/navigation.py +0 -54
  718. {nautobot-2.3.16.dist-info → nautobot-2.4.0.dist-info}/LICENSE.txt +0 -0
  719. {nautobot-2.3.16.dist-info → nautobot-2.4.0.dist-info}/NOTICE +0 -0
  720. {nautobot-2.3.16.dist-info → nautobot-2.4.0.dist-info}/WHEEL +0 -0
  721. {nautobot-2.3.16.dist-info → nautobot-2.4.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,36 @@
1
+ from django.template import Context
2
+ from django.template.loader import get_template
3
+
4
+
5
+ def flatten_context(context) -> dict:
6
+ """
7
+ If the given context is already a dict, return it unmodified; if it is a Context, flatten it to a dict.
8
+
9
+ This is working around a bug (not sure if in Django or in our code) where, if a `Context`'s `dicts` contain
10
+ a `RequestContext`, calling `Context.flatten()` throws an exception.
11
+ """
12
+ if isinstance(context, dict):
13
+ return context
14
+ flat = {}
15
+ for d in context.dicts:
16
+ if isinstance(d, dict):
17
+ flat.update(d)
18
+ else:
19
+ flat.update(flatten_context(d))
20
+ return flat
21
+
22
+
23
+ def render_component_template(template_path: str, context: Context, **kwargs) -> str:
24
+ """
25
+ Render the template located at the given path with the given context, possibly augmented via additional kwargs.
26
+
27
+ Args:
28
+ template_path (str): Path to the template to render, for example `"components/tab/label_wrapper.html"`.
29
+ context (Context): Rendering context for the template
30
+ **kwargs (dict): Additional key/value pairs to extend the context with for this specific template.
31
+
32
+ Examples:
33
+ >>> render_component_template(self.label_wrapper_template_path, context, tab_id=self.tab_id, label="Hello")
34
+ """
35
+ with context.update(kwargs):
36
+ return get_template(template_path).render(flatten_context(context))
nautobot/core/urls.py CHANGED
@@ -5,11 +5,13 @@ from django.views.generic import TemplateView
5
5
  from django.views.static import serve
6
6
 
7
7
  from nautobot.core.views import (
8
+ AboutView,
8
9
  CustomGraphQLView,
9
10
  get_file_with_authorization,
10
11
  HomeView,
11
12
  NautobotMetricsView,
12
13
  NautobotMetricsViewAuth,
14
+ RenderJinjaView,
13
15
  SearchView,
14
16
  StaticMediaFailureView,
15
17
  ThemePreviewView,
@@ -27,6 +29,7 @@ from .admin import admin_site
27
29
  urlpatterns = [
28
30
  # Base views
29
31
  path("", HomeView.as_view(), name="home"),
32
+ path("about/", AboutView.as_view(), name="about"),
30
33
  path("search/", SearchView.as_view(), name="search"),
31
34
  # Login/logout
32
35
  path("login/", LoginView.as_view(), name="login"),
@@ -42,6 +45,7 @@ urlpatterns = [
42
45
  path("user/", include("nautobot.users.urls")),
43
46
  path("users/", include("nautobot.users.urls", "users")),
44
47
  path("virtualization/", include("nautobot.virtualization.urls")),
48
+ path("wireless/", include("nautobot.wireless.urls")),
45
49
  # API
46
50
  path("api/", include("nautobot.core.api.urls")),
47
51
  # GraphQL
@@ -69,6 +73,8 @@ urlpatterns = [
69
73
  ),
70
74
  # Celery worker status page
71
75
  path("worker-status/", WorkerStatusView.as_view(), name="worker_status"),
76
+ # Jinja template renderer tool
77
+ path("render-jinja-template/", RenderJinjaView.as_view(), name="render_jinja_template"),
72
78
  # Templated css file
73
79
  path(
74
80
  "template.css", TemplateView.as_view(template_name="template.css", content_type="text/css"), name="template_css"
@@ -1,12 +1,39 @@
1
1
  """Helper code for loading values that may be defined in settings.py/nautobot_config.py *or* in django-constance."""
2
2
 
3
+ import contextlib
4
+ import logging
5
+
3
6
  from constance import config
4
7
  from django.conf import settings
8
+ from django.core.exceptions import ObjectDoesNotExist
9
+ from django.db import OperationalError, ProgrammingError
10
+
11
+ logger = logging.getLogger(__name__)
12
+
5
13
 
14
+ def get_settings_or_config(variable_name, fallback=None):
15
+ """
16
+ Get a value from Django settings (if specified there) or Constance configuration (otherwise).
6
17
 
7
- def get_settings_or_config(variable_name):
8
- """Get a value from Django settings (if specified there) or Constance configuration (otherwise)."""
18
+ The fallback value is returned *only* if the requested variable cannot be found at all - this is an error case,
19
+ and will generate warning logs.
20
+ """
9
21
  # Explicitly set in settings.py or nautobot_config.py takes precedence, for now
10
22
  if hasattr(settings, variable_name):
11
23
  return getattr(settings, variable_name)
12
- return getattr(config, variable_name)
24
+ # django-constance 4.x removed some built-in error handling here, so we have to do it ourselves now
25
+ with contextlib.suppress(ObjectDoesNotExist, OperationalError, ProgrammingError):
26
+ return getattr(config, variable_name)
27
+ logger.warning(
28
+ 'Configuration "%s" is not in settings, and could not read from the Constance database table '
29
+ "(perhaps not initialized yet?)",
30
+ variable_name,
31
+ )
32
+ if variable_name in settings.CONSTANCE_CONFIG:
33
+ default = settings.CONSTANCE_CONFIG[variable_name][0]
34
+ logger.warning('Using default value of "%s" from Constance configuration for "%s"', default, variable_name)
35
+ return default
36
+ logger.warning(
37
+ 'Constance configuration does not include an entry for "%s" - must return %s', variable_name, fallback
38
+ )
39
+ return fallback
@@ -10,6 +10,7 @@ from django.contrib.contenttypes.models import ContentType
10
10
  from django.db.models import Model
11
11
  from django.urls import get_resolver, resolve, reverse, URLPattern, URLResolver
12
12
  from django.utils.module_loading import import_string
13
+ from django.views.generic.base import RedirectView
13
14
 
14
15
 
15
16
  def get_changes_for_model(model):
@@ -315,7 +316,7 @@ def get_created_and_last_updated_usernames_for_model(instance):
315
316
  return created_by, last_updated_by
316
317
 
317
318
 
318
- def get_url_patterns(urlconf=None, patterns_list=None, base_path="/"):
319
+ def get_url_patterns(urlconf=None, patterns_list=None, base_path="/", ignore_redirects=False):
319
320
  """
320
321
  Recursively yield a list of registered URL patterns.
321
322
 
@@ -326,6 +327,7 @@ def get_url_patterns(urlconf=None, patterns_list=None, base_path="/"):
326
327
  Default if unspecified is the `url_patterns` attribute of the given `urlconf` module.
327
328
  base_path (str): String to prepend to all URL patterns yielded.
328
329
  Default if unspecified is the string `"/"`.
330
+ ignore_redirects (bool): If True, skip URL patterns that correspond to RedirectViews.
329
331
 
330
332
  Yields:
331
333
  (str): Each URL pattern defined in the given urlconf and its descendants
@@ -384,10 +386,18 @@ def get_url_patterns(urlconf=None, patterns_list=None, base_path="/"):
384
386
 
385
387
  for item in patterns_list:
386
388
  if isinstance(item, URLPattern):
389
+ if (
390
+ ignore_redirects
391
+ and hasattr(item.callback, "view_class")
392
+ and issubclass(item.callback.view_class, RedirectView)
393
+ ):
394
+ continue
387
395
  yield base_path + str(item.pattern)
388
396
  elif isinstance(item, URLResolver):
389
397
  # Recurse!
390
- yield from get_url_patterns(urlconf, item.url_patterns, base_path + str(item.pattern))
398
+ yield from get_url_patterns(
399
+ urlconf, item.url_patterns, base_path + str(item.pattern), ignore_redirects=ignore_redirects
400
+ )
391
401
 
392
402
 
393
403
  def get_url_for_url_pattern(url_pattern):
@@ -0,0 +1,64 @@
1
+ import logging
2
+
3
+ from django.db import NotSupportedError
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ def maybe_select_related(queryset, select_fields):
9
+ """Attempt to perform a select_related() on the given queryset, but fail gracefully if not permitted."""
10
+ model = queryset.model
11
+
12
+ # Django doesn't allow .select_related() on a QuerySet that had .values()/.values_list() applied, or
13
+ # one that has had union()/intersection()/difference() applied.
14
+ # We can detect and avoid these cases the same way that Django itself does.
15
+ if queryset._fields is not None:
16
+ logger.debug(
17
+ "NOT applying select_related(%s) to %s QuerySet as it includes .values()/.values_list()",
18
+ select_fields,
19
+ model.__name__,
20
+ )
21
+ elif queryset.query.combinator:
22
+ logger.debug(
23
+ "NOT applying select_related(%s) to %s QuerySet as it is a combinator query",
24
+ select_fields,
25
+ model.__name__,
26
+ )
27
+ else:
28
+ logger.debug("Applying .select_related(%s) to %s QuerySet", select_fields, model.__name__)
29
+ # Belt and suspenders - we should have avoided any error cases above, but be safe anyway:
30
+ try:
31
+ queryset = queryset.select_related(*select_fields)
32
+ except (TypeError, ValueError, NotSupportedError) as exc:
33
+ logger.warning(
34
+ "Unexpected error when trying to .select_related() on %s QuerySet: %s",
35
+ model.__name__,
36
+ exc,
37
+ )
38
+
39
+ return queryset
40
+
41
+
42
+ def maybe_prefetch_related(queryset, prefetch_fields):
43
+ """Attempt to perform a select_related() on the given queryset, but fail gracefully if not permitted."""
44
+ model = queryset.model
45
+
46
+ if queryset.query.combinator:
47
+ logger.debug(
48
+ "NOT applying prefetch_related(%s) to %s QuerySet as it is a combinator query",
49
+ prefetch_fields,
50
+ model.__name__,
51
+ )
52
+ else:
53
+ logger.debug("Applying .prefetch_related(%s) to %s QuerySet", prefetch_fields, model.__name__)
54
+ # Belt and suspenders - we should have avoided any error cases above, but be safe anyway:
55
+ try:
56
+ queryset = queryset.prefetch_related(*prefetch_fields)
57
+ except (AttributeError, TypeError, ValueError, NotSupportedError) as exc:
58
+ logger.warning(
59
+ "Unexpected error when trying to .prefetch_related() on %s QuerySet: %s",
60
+ model.__name__,
61
+ exc,
62
+ )
63
+
64
+ return queryset
@@ -141,22 +141,35 @@ def get_filterable_params_from_filter_params(filter_params, non_filter_params, f
141
141
  return final_filter_params
142
142
 
143
143
 
144
- def normalize_querydict(querydict, form_class=None):
144
+ def normalize_querydict(querydict, form_class=None, filterset=None):
145
145
  """
146
- Convert a QueryDict to a normal, mutable dictionary, preserving list values. For example,
146
+ Converts a QueryDict into a standard, mutable dictionary while preserving multiple values as lists.
147
147
 
148
- QueryDict('foo=1&bar=2&bar=3&baz=')
148
+ Example:
149
+ A QueryDict like:
150
+ QueryDict('foo=1&bar=2&bar=3&baz=')
149
151
 
150
- becomes:
152
+ Converts to:
153
+ {'foo': '1', 'bar': ['2', '3'], 'baz': ''}
151
154
 
152
- {'foo': '1', 'bar': ['2', '3'], 'baz': ''}
155
+ This function ensures that fields with multiple values are handled correctly, as QueryDict
156
+ does not inherently preserve multiple values as lists.
153
157
 
154
- This function is necessary because QueryDict does not provide any built-in mechanism which preserves multiple
155
- values.
156
-
157
- A `form_class` can be provided as a way to hint which query parameters should be treated as lists.
158
+ Args:
159
+ querydict (QueryDict): The QueryDict to be normalized.
160
+ form_class (forms.Form, optional): A form class to identify fields that should be treated as
161
+ lists (e.g., `MultipleChoiceField` or `ModelMultipleChoiceField`).
162
+ filterset (django_filters.FilterSet, optional): A FilterSet instance to identify filters that
163
+ should preserve multiple values as lists (e.g., non-single-choice fields).
164
+
165
+ Raises:
166
+ AttributeError: If both `form_class` and `filterset` are provided.
158
167
  """
168
+ if form_class and filterset:
169
+ raise AttributeError("Either form_class or filterset_class is to be provided not both")
170
+
159
171
  result = {}
172
+
160
173
  if querydict:
161
174
  for key, value_list in querydict.lists():
162
175
  if len(value_list) > 1:
@@ -171,6 +184,8 @@ def normalize_querydict(querydict, form_class=None):
171
184
  ):
172
185
  # Even though there's only a single value in the querydict for this key, the form wants it as a list
173
186
  result[key] = value_list
187
+ elif filterset is not None and filterset.filters.get(key) and not is_single_choice_field(filterset, key):
188
+ result[key] = value_list
174
189
  else:
175
190
  # Only a single value in the querydict for this key, and no guidance otherwise, so make it single
176
191
  result[key] = value_list[0]
@@ -1,4 +1,5 @@
1
1
  import contextlib
2
+ import datetime
2
3
  import logging
3
4
  import os
4
5
  import platform
@@ -45,6 +46,7 @@ from nautobot.core.celery import app
45
46
  from nautobot.core.constants import SEARCH_MAX_RESULTS
46
47
  from nautobot.core.forms import SearchForm
47
48
  from nautobot.core.releases import get_latest_release
49
+ from nautobot.core.utils.config import get_settings_or_config
48
50
  from nautobot.core.utils.lookup import get_route_for_model
49
51
  from nautobot.core.utils.permissions import get_permission_for_model
50
52
  from nautobot.extras.forms import GraphQLQueryForm
@@ -56,7 +58,6 @@ logger = logging.getLogger(__name__)
56
58
 
57
59
  class HomeView(AccessMixin, TemplateView):
58
60
  template_name = "home.html"
59
- use_new_ui = True
60
61
 
61
62
  def render_additional_content(self, request, context, details):
62
63
  # Collect all custom data using callback functions.
@@ -479,3 +480,49 @@ def get_file_with_authorization(request, *args, **kwargs):
479
480
  get_object_or_404(queryset, file=request.GET.get("name"))
480
481
 
481
482
  return get_file(request, *args, **kwargs)
483
+
484
+
485
+ class AboutView(AccessMixin, TemplateView):
486
+ """
487
+ Nautobot About View which displays general information about Nautobot and contact details
488
+ for Network to Code.
489
+ """
490
+
491
+ template_name = "about.html"
492
+
493
+ def get(self, request, *args, **kwargs):
494
+ # Redirect user to login page if not authenticated
495
+ if not request.user.is_authenticated:
496
+ return self.handle_no_permission()
497
+ # Check whether a new release is available. (Only for staff/superusers.)
498
+ new_release = None
499
+ if request.user.is_staff or request.user.is_superuser:
500
+ latest_release, release_url = get_latest_release()
501
+ if isinstance(latest_release, version.Version):
502
+ current_version = version.parse(settings.VERSION)
503
+ if latest_release > current_version:
504
+ new_release = {
505
+ "version": str(latest_release),
506
+ "url": release_url,
507
+ }
508
+
509
+ # Support contract state
510
+ support_expiration_date = get_settings_or_config("NTC_SUPPORT_CONTRACT_EXPIRATION_DATE")
511
+ support_contract_active = support_expiration_date and support_expiration_date >= datetime.date.today()
512
+
513
+ context = self.get_context_data()
514
+ context.update(
515
+ {
516
+ "new_release": new_release,
517
+ "support_contract_active": support_contract_active,
518
+ "support_expiration_date": support_expiration_date,
519
+ }
520
+ )
521
+
522
+ return self.render_to_response(context)
523
+
524
+
525
+ class RenderJinjaView(LoginRequiredMixin, TemplateView):
526
+ """Render a Jinja template with context data."""
527
+
528
+ template_name = "utilities/render_jinja2.html"
@@ -1,9 +1,7 @@
1
- from __future__ import annotations # python 3.8
2
-
3
1
  from copy import deepcopy
4
2
  import logging
5
3
  import re
6
- from typing import Optional
4
+ from typing import ClassVar, Optional
7
5
 
8
6
  from django.conf import settings
9
7
  from django.contrib import messages
@@ -11,12 +9,11 @@ from django.contrib.auth.mixins import LoginRequiredMixin
11
9
  from django.contrib.auth.models import AnonymousUser
12
10
  from django.contrib.contenttypes.models import ContentType
13
11
  from django.core.exceptions import (
14
- FieldDoesNotExist,
15
12
  ObjectDoesNotExist,
16
13
  ValidationError,
17
14
  )
18
15
  from django.db import IntegrityError, transaction
19
- from django.db.models import ManyToManyField, Model, ProtectedError, Q, QuerySet
16
+ from django.db.models import Model, ProtectedError, Q, QuerySet
20
17
  from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
21
18
  from django.http import HttpResponse
22
19
  from django.shortcuts import get_object_or_404, redirect, render
@@ -29,6 +26,7 @@ from django_filters import FilterSet
29
26
  from django_tables2 import RequestConfig, Table
30
27
 
31
28
  from nautobot.core.api.utils import get_serializer_for_model
29
+ from nautobot.core.constants import MAX_PAGE_SIZE_DEFAULT
32
30
  from nautobot.core.exceptions import AbortTransaction
33
31
  from nautobot.core.forms import (
34
32
  BootstrapMixin,
@@ -50,7 +48,7 @@ from nautobot.core.utils.requests import (
50
48
  get_filterable_params_from_filter_params,
51
49
  normalize_querydict,
52
50
  )
53
- from nautobot.core.views.mixins import EditAndDeleteAllModelMixin, GetReturnURLMixin, ObjectPermissionRequiredMixin
51
+ from nautobot.core.views.mixins import BulkEditAndBulkDeleteModelMixin, GetReturnURLMixin, ObjectPermissionRequiredMixin
54
52
  from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
55
53
  from nautobot.core.views.utils import (
56
54
  check_filter_for_display,
@@ -61,9 +59,7 @@ from nautobot.core.views.utils import (
61
59
  prepare_cloned_fields,
62
60
  view_changes_not_saved,
63
61
  )
64
- from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
65
62
  from nautobot.extras.models import ExportTemplate, SavedView, UserSavedViewAssociation
66
- from nautobot.extras.utils import bulk_delete_with_bulk_change_logging, remove_prefix_from_cf_key
67
63
 
68
64
 
69
65
  class GenericView(LoginRequiredMixin, View):
@@ -82,8 +78,9 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
82
78
  template_name: Name of the template to use
83
79
  """
84
80
 
85
- queryset: QuerySet
86
- template_name: Optional[str] = None
81
+ queryset: ClassVar[QuerySet]
82
+ template_name: ClassVar[Optional[str]] = None
83
+ object_detail_content = None
87
84
 
88
85
  def get_required_permission(self):
89
86
  return get_permission_for_model(self.queryset.model, "view")
@@ -123,6 +120,7 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
123
120
  "content_type": content_type,
124
121
  "verbose_name": self.queryset.model._meta.verbose_name,
125
122
  "verbose_name_plural": self.queryset.model._meta.verbose_name_plural,
123
+ "object_detail_content": self.object_detail_content,
126
124
  **common_detail_view_context(request, instance),
127
125
  **self.get_extra_context(request, instance),
128
126
  }
@@ -355,7 +353,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
355
353
  }
356
354
  RequestConfig(request, paginate).configure(table)
357
355
  table_config_form = TableConfigForm(table=table)
358
- max_page_size = get_settings_or_config("MAX_PAGE_SIZE")
356
+ max_page_size = get_settings_or_config("MAX_PAGE_SIZE", fallback=MAX_PAGE_SIZE_DEFAULT)
359
357
  if max_page_size and paginate["per_page"] > max_page_size:
360
358
  messages.warning(
361
359
  request,
@@ -987,7 +985,7 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): #
987
985
  )
988
986
 
989
987
 
990
- class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDeleteAllModelMixin, View):
988
+ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, BulkEditAndBulkDeleteModelMixin, View):
991
989
  """
992
990
  Edit objects in bulk.
993
991
 
@@ -1037,93 +1035,7 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDele
1037
1035
 
1038
1036
  if form.is_valid():
1039
1037
  logger.debug("Form validation was successful")
1040
- form_custom_fields = getattr(form, "custom_fields", [])
1041
- form_relationships = getattr(form, "relationships", [])
1042
- standard_fields = [
1043
- field
1044
- for field in form.fields
1045
- if field not in form_custom_fields + form_relationships + ["pk"] + ["object_note"]
1046
- ]
1047
- nullified_fields = request.POST.getlist("_nullify")
1048
-
1049
- try:
1050
- with deferred_change_logging_for_bulk_operation():
1051
- updated_objects = []
1052
- queryset = queryset if edit_all else queryset.filter(pk__in=form.cleaned_data["pk"])
1053
- for obj in queryset:
1054
- obj = self.alter_obj(obj, request, [], kwargs)
1055
-
1056
- # Update standard fields. If a field is listed in _nullify, delete its value.
1057
- for name in standard_fields:
1058
- try:
1059
- model_field = model._meta.get_field(name)
1060
- except FieldDoesNotExist:
1061
- # This form field is used to modify a field rather than set its value directly
1062
- model_field = None
1063
-
1064
- # Handle nullification
1065
- if name in form.nullable_fields and name in nullified_fields:
1066
- if isinstance(model_field, ManyToManyField):
1067
- getattr(obj, name).set([])
1068
- else:
1069
- setattr(obj, name, None if model_field is not None and model_field.null else "")
1070
-
1071
- # ManyToManyFields
1072
- elif isinstance(model_field, ManyToManyField):
1073
- if form.cleaned_data[name]:
1074
- getattr(obj, name).set(form.cleaned_data[name])
1075
- # Normal fields
1076
- elif form.cleaned_data[name] not in (None, ""):
1077
- setattr(obj, name, form.cleaned_data[name])
1078
-
1079
- # Update custom fields
1080
- for field_name in form_custom_fields:
1081
- if field_name in form.nullable_fields and field_name in nullified_fields:
1082
- obj.cf[remove_prefix_from_cf_key(field_name)] = None
1083
- elif form.cleaned_data.get(field_name) not in (None, "", []):
1084
- obj.cf[remove_prefix_from_cf_key(field_name)] = form.cleaned_data[field_name]
1085
-
1086
- obj.full_clean()
1087
- obj.save()
1088
- updated_objects.append(obj)
1089
- logger.debug(f"Saved {obj} (PK: {obj.pk})")
1090
-
1091
- # Add/remove tags
1092
- if form.cleaned_data.get("add_tags", None):
1093
- obj.tags.add(*form.cleaned_data["add_tags"])
1094
- if form.cleaned_data.get("remove_tags", None):
1095
- obj.tags.remove(*form.cleaned_data["remove_tags"])
1096
-
1097
- if hasattr(form, "save_relationships") and callable(form.save_relationships):
1098
- # Add/remove relationship associations
1099
- form.save_relationships(instance=obj, nullified_fields=nullified_fields)
1100
-
1101
- if hasattr(form, "save_note") and callable(form.save_note):
1102
- form.save_note(instance=obj, user=request.user)
1103
-
1104
- self.extra_post_save_action(obj, form)
1105
-
1106
- # Enforce object-level permissions
1107
- if self.queryset.filter(pk__in=[obj.pk for obj in updated_objects]).count() != len(
1108
- updated_objects
1109
- ):
1110
- raise ObjectDoesNotExist
1111
-
1112
- if updated_objects:
1113
- msg = f"Updated {len(updated_objects)} {model._meta.verbose_name_plural}"
1114
- logger.info(msg)
1115
- messages.success(self.request, msg)
1116
-
1117
- return redirect(self.get_return_url(request))
1118
-
1119
- except ValidationError as e:
1120
- messages.error(self.request, f"{obj} failed validation: {e}")
1121
-
1122
- except ObjectDoesNotExist:
1123
- msg = "Object update failed due to object-level permissions violation"
1124
- logger.debug(msg)
1125
- form.add_error(None, msg)
1126
-
1038
+ return self.send_bulk_edit_objects_to_job(request, form, model)
1127
1039
  else:
1128
1040
  logger.debug("Form validation failed")
1129
1041
 
@@ -1267,7 +1179,7 @@ class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
1267
1179
  return ""
1268
1180
 
1269
1181
 
1270
- class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDeleteAllModelMixin, View):
1182
+ class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, BulkEditAndBulkDeleteModelMixin, View):
1271
1183
  """
1272
1184
  Delete objects in bulk.
1273
1185
 
@@ -1290,48 +1202,30 @@ class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDe
1290
1202
  def get(self, request):
1291
1203
  return redirect(self.get_return_url(request))
1292
1204
 
1293
- def _perform_delete_operation(self, request, queryset, model):
1294
- logger = logging.getLogger(__name__ + ".BulkDeleteView")
1295
- self.perform_pre_delete(request, queryset)
1296
- try:
1297
- _, deleted_info = bulk_delete_with_bulk_change_logging(queryset)
1298
- deleted_count = deleted_info[model._meta.label]
1299
- except ProtectedError as e:
1300
- logger.info("Caught ProtectedError while attempting to delete objects")
1301
- handle_protectederror(queryset, request, e)
1302
- return redirect(self.get_return_url(request))
1303
- msg = f"Deleted {deleted_count} {model._meta.verbose_name_plural}"
1304
- logger.info(msg)
1305
- messages.success(request, msg)
1306
- return redirect(self.get_return_url(request))
1307
-
1308
1205
  def post(self, request, **kwargs):
1309
1206
  logger = logging.getLogger(f"{__name__}.BulkDeleteView")
1310
1207
  model = self.queryset.model
1208
+ delete_all = request.POST.get("_all")
1311
1209
 
1312
1210
  # Are we deleting *all* objects in the queryset or just a selected subset?
1313
- if request.POST.get("_all"):
1211
+ if delete_all:
1314
1212
  queryset = self._get_bulk_edit_delete_all_queryset(request)
1315
-
1316
- if "_confirm" in request.POST:
1317
- return self._perform_delete_operation(request, queryset, model)
1318
-
1319
- context = self._bulk_delete_all_context(request, queryset)
1320
- context.update(self.extra_context())
1321
- return render(request, self.template_name, context)
1322
-
1323
- pk_list = request.POST.getlist("pk")
1213
+ pk_list = []
1214
+ else:
1215
+ pk_list = request.POST.getlist("pk")
1216
+ queryset = self.queryset.filter(pk__in=pk_list)
1324
1217
 
1325
1218
  form_cls = self.get_form()
1326
1219
 
1327
1220
  if "_confirm" in request.POST:
1328
1221
  form = form_cls(request.POST)
1222
+ # Set pk field requirement here instead of BulkDeleteForm since get_form() may return a different form class
1223
+ if delete_all:
1224
+ form.fields["pk"].required = False
1225
+
1329
1226
  if form.is_valid():
1330
1227
  logger.debug("Form validation was successful")
1331
-
1332
- # Delete objects
1333
- queryset = self.queryset.filter(pk__in=pk_list)
1334
- return self._perform_delete_operation(request, queryset, model)
1228
+ return self.send_bulk_delete_objects_to_job(request, pk_list, model, delete_all)
1335
1229
  else:
1336
1230
  logger.debug("Form validation failed")
1337
1231
 
@@ -1344,23 +1238,26 @@ class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDe
1344
1238
  )
1345
1239
 
1346
1240
  # Retrieve objects being deleted
1347
- table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False)
1348
- if not table.rows:
1349
- messages.warning(
1350
- request,
1351
- f"No {model._meta.verbose_name_plural} were selected for deletion.",
1352
- )
1353
- return redirect(self.get_return_url(request))
1354
- # Hide actions column if present
1355
- if "actions" in table.columns:
1356
- table.columns.hide("actions")
1241
+ table = None
1242
+ if not delete_all:
1243
+ table = self.table(queryset, orderable=False)
1244
+ if not table.rows:
1245
+ messages.warning(
1246
+ request,
1247
+ f"No {model._meta.verbose_name_plural} were selected for deletion.",
1248
+ )
1249
+ return redirect(self.get_return_url(request))
1250
+ # Hide actions column if present
1251
+ if "actions" in table.columns:
1252
+ table.columns.hide("actions")
1357
1253
 
1358
1254
  context = {
1359
1255
  "form": form,
1360
1256
  "obj_type_plural": model._meta.verbose_name_plural,
1361
1257
  "table": table,
1362
1258
  "return_url": self.get_return_url(request),
1363
- "total_objs_to_delete": len(table.rows),
1259
+ "total_objs_to_delete": queryset.count(),
1260
+ "delete_all": delete_all,
1364
1261
  }
1365
1262
  context.update(self.extra_context())
1366
1263
  return render(request, self.template_name, context)