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
@@ -1,7 +1,6 @@
1
- from __future__ import annotations # python 3.8
2
-
3
1
  import random
4
2
  import string
3
+ from typing import ClassVar, Iterable
5
4
 
6
5
  from django.contrib.contenttypes.models import ContentType
7
6
  from django.db.models import Count, Q, QuerySet
@@ -21,6 +20,7 @@ from nautobot.core.filters import (
21
20
  )
22
21
  from nautobot.core.models.generics import PrimaryModel
23
22
  from nautobot.core.testing import views
23
+ from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
24
24
  from nautobot.extras.models import Contact, ContactAssociation, Role, Status, Tag, Team
25
25
  from nautobot.tenancy import models
26
26
 
@@ -30,7 +30,7 @@ class FilterTestCases:
30
30
  class BaseFilterTestCase(views.TestCase):
31
31
  """Base class for testing of FilterSets."""
32
32
 
33
- queryset: QuerySet
33
+ queryset: ClassVar[QuerySet]
34
34
 
35
35
  def get_filterset_test_values(self, field_name, queryset=None):
36
36
  """Returns a list of distinct values from the requested queryset field to use in filterset tests.
@@ -73,7 +73,7 @@ class FilterTestCases:
73
73
  class FilterTestCase(BaseFilterTestCase):
74
74
  """Add common tests for all FilterSets."""
75
75
 
76
- filterset: type[FilterSet]
76
+ filterset: ClassVar[type[FilterSet]]
77
77
 
78
78
  # filter predicate fields that should be excluded from q test case
79
79
  exclude_q_filter_predicates = []
@@ -85,7 +85,13 @@ class FilterTestCases:
85
85
  # ["filter1"],
86
86
  # ["filter2", "field2__name"],
87
87
  # ]
88
- generic_filter_tests = []
88
+ generic_filter_tests: ClassVar[Iterable]
89
+
90
+ def setUp(self):
91
+ for attr in ["queryset", "filterset", "generic_filter_tests"]:
92
+ if not hasattr(self, attr):
93
+ raise NotImplementedError(f'{self} is missing a value for required attribute "{attr}"')
94
+ super().setUp()
89
95
 
90
96
  def get_q_filter(self):
91
97
  """Helper method to return q filter."""
@@ -141,15 +147,22 @@ class FilterTestCases:
141
147
  )
142
148
  This expects a field named `devices` on the model and a filter named `devices` on the filterset.
143
149
  """
150
+ if not any(test[0] == "id" for test in self.generic_filter_tests):
151
+ self.generic_filter_tests = (["id"], *self.generic_filter_tests)
152
+
144
153
  if getattr(self.queryset.model, "is_contact_associable_model", False):
145
- for generic_filter_test in (
146
- ["contacts", "associated_contacts__contact__name"],
147
- ["contacts", "associated_contacts__contact__id"],
148
- ["teams", "associated_contacts__team__name"],
149
- ["teams", "associated_contacts__team__id"],
150
- ):
151
- if generic_filter_test not in self.generic_filter_tests:
152
- self.generic_filter_tests = (*self.generic_filter_tests, generic_filter_test)
154
+ if not any(test[0] == "contacts" for test in self.generic_filter_tests):
155
+ self.generic_filter_tests = (
156
+ *self.generic_filter_tests,
157
+ ["contacts", "associated_contacts__contact__name"],
158
+ ["contacts", "associated_contacts__contact__id"],
159
+ )
160
+ if not any(test[0] == "teams" for test in self.generic_filter_tests):
161
+ self.generic_filter_tests = (
162
+ *self.generic_filter_tests,
163
+ ["teams", "associated_contacts__team__name"],
164
+ ["teams", "associated_contacts__team__id"],
165
+ )
153
166
 
154
167
  # Make sure we have at least 3 contacts and 3 teams in the database
155
168
  if Contact.objects.count() < 3:
@@ -177,9 +190,6 @@ class FilterTestCases:
177
190
  status=Status.objects.get_for_model(ContactAssociation).last(),
178
191
  )
179
192
 
180
- if not self.generic_filter_tests:
181
- self.skipTest("No generic_filter_tests defined?")
182
-
183
193
  for test in self.generic_filter_tests:
184
194
  filter_name = test[0]
185
195
  field_name = test[-1] # default to filter_name if a second list item was not supplied
@@ -238,10 +248,6 @@ class FilterTestCases:
238
248
  qs_result = self.queryset.filter(tags=tags[0]).filter(tags=tags[1]).distinct()
239
249
  self.assertQuerysetEqualAndNotEmpty(filterset_result, qs_result)
240
250
 
241
- def test_q_filter_exists(self):
242
- """Test the `q` filter exists on a filterset, does not validate the filter works as expected."""
243
- self.assertIn("q", self.filterset.declared_filters)
244
-
245
251
  def _assert_valid_filter_predicates(self, obj, field_name):
246
252
  self.assertTrue(
247
253
  hasattr(obj, field_name),
@@ -377,27 +383,27 @@ class FilterTestCases:
377
383
  filter_field, (ContentTypeFilter, ContentTypeMultipleChoiceFilter, ContentTypeChoiceFilter)
378
384
  )
379
385
 
386
+ # Test cases should just explicitly include `name` as a generic_filter_tests entry
387
+ @class_deprecated_in_favor_of(FilterTestCase) # pylint: disable=undefined-variable
380
388
  class NameOnlyFilterTestCase(FilterTestCase):
381
389
  """Add simple tests for filtering by name."""
382
390
 
383
- def test_name(self):
384
- """Verify that the filterset supports filtering by name."""
385
- params = {"name": list(self.queryset.values_list("name", flat=True)[:2])}
386
- filterset = self.filterset(params, self.queryset)
387
- self.assertTrue(filterset.is_valid())
388
- self.assertQuerysetEqualAndNotEmpty(
389
- filterset.qs.order_by("name"), self.queryset.filter(name__in=params["name"]).order_by("name")
390
- )
391
+ def test_filters_generic(self):
392
+ if not any(test[0] == "name" for test in self.generic_filter_tests):
393
+ self.generic_filter_tests = (["name"], *self.generic_filter_tests)
394
+ super().test_filters_generic()
391
395
 
392
- class NameSlugFilterTestCase(NameOnlyFilterTestCase):
396
+ # Test cases should just explicitly include `name` and `slug` as generic_filter_tests entries
397
+ @class_deprecated_in_favor_of(FilterTestCase) # pylint: disable=undefined-variable
398
+ class NameSlugFilterTestCase(FilterTestCase):
393
399
  """Add simple tests for filtering by name and by slug."""
394
400
 
395
- def test_slug(self):
396
- """Verify that the filterset supports filtering by slug."""
397
- params = {"slug": self.queryset.values_list("slug", flat=True)[:2]}
398
- filterset = self.filterset(params, self.queryset)
399
- self.assertTrue(filterset.is_valid())
400
- self.assertEqual(filterset.qs.count(), 2)
401
+ def test_filters_generic(self):
402
+ if not any(test[0] == "slug" for test in self.generic_filter_tests):
403
+ self.generic_filter_tests = (["slug"], *self.generic_filter_tests)
404
+ if not any(test[0] == "name" for test in self.generic_filter_tests):
405
+ self.generic_filter_tests = (["name"], *self.generic_filter_tests)
406
+ super().test_filters_generic()
401
407
 
402
408
  class TenancyFilterTestCaseMixin(views.TestCase):
403
409
  """Add test cases for tenant and tenant-group filters."""
@@ -1,5 +1,6 @@
1
1
  from django.test import tag, TestCase
2
2
 
3
+ from nautobot.core.api.constants import NON_FILTER_QUERY_PARAMS
3
4
  from nautobot.core.forms.fields import DynamicModelChoiceMixin
4
5
  from nautobot.core.utils.lookup import get_filterset_for_model
5
6
 
@@ -18,6 +19,7 @@ class FormTestCases:
18
19
  continue
19
20
  with self.subTest(f"Assert {self.form_class.__name__}.{field_name} query_params are valid."):
20
21
  query_params_fields = set(field_class.query_params.keys())
22
+ query_params_fields = query_params_fields - set(NON_FILTER_QUERY_PARAMS)
21
23
  if not query_params_fields:
22
24
  self.skipTest(f"{self.form_class.__name__}.{field_name} has no query_params")
23
25
  field_model = field_class.queryset.model
@@ -1,5 +1,3 @@
1
- from __future__ import annotations # python 3.8
2
-
3
1
  import contextlib
4
2
  import re
5
3
  from typing import Optional, Sequence
@@ -27,14 +25,15 @@ from nautobot.dcim.models.device_components import ComponentModel
27
25
  from nautobot.extras import choices as extras_choices, models as extras_models, querysets as extras_querysets
28
26
  from nautobot.extras.forms import CustomFieldModelFormMixin, RelationshipModelFormMixin
29
27
  from nautobot.extras.models import CustomFieldModel, RelationshipModel
28
+ from nautobot.extras.models.jobs import JobResult
30
29
  from nautobot.extras.models.mixins import NotesMixin
31
30
  from nautobot.ipam.models import Prefix
32
31
  from nautobot.users import models as users_models
33
32
 
34
33
  __all__ = (
35
- "TestCase",
36
34
  "ModelTestCase",
37
35
  "ModelViewTestCase",
36
+ "TestCase",
38
37
  "ViewTestCases",
39
38
  )
40
39
 
@@ -174,10 +173,7 @@ class ViewTestCases:
174
173
  instance = self._get_queryset().first()
175
174
 
176
175
  # Add model-level permission
177
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["view"])
178
- obj_perm.save()
179
- obj_perm.users.add(self.user)
180
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
176
+ self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
181
177
 
182
178
  # Try GET with model-level permission
183
179
  response = self.client.get(instance.get_absolute_url())
@@ -326,10 +322,7 @@ class ViewTestCases:
326
322
  initial_count = self._get_queryset().count()
327
323
 
328
324
  # Assign unconstrained permission
329
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["add"])
330
- obj_perm.save()
331
- obj_perm.users.add(self.user)
332
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
325
+ self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
333
326
 
334
327
  # Try GET with model-level permission
335
328
  self.assertHttpStatus(self.client.get(self._get_url("add")), 200)
@@ -436,32 +429,6 @@ class ViewTestCases:
436
429
  if isinstance(model_class, PrimaryModel):
437
430
  self.assertIsNotNone(fields.get("tags"))
438
431
 
439
- def test_slug_autocreation(self):
440
- """Test that slug is autocreated through ORM."""
441
- # This really should go on a models test page, but we don't have test structures for models.
442
- if getattr(self.model, "slug_source", None) is not None:
443
- obj = self.model.objects.get(**{self.slug_source: self.slug_test_object})
444
- expected_slug = self.slugify_function(getattr(obj, self.slug_source))
445
- self.assertEqual(obj.slug, expected_slug)
446
-
447
- def test_slug_not_modified(self):
448
- """Ensure save method does not modify slug that is passed in."""
449
- # This really should go on a models test page, but we don't have test structures for models.
450
- if getattr(self.model, "slug_source", None) is not None:
451
- new_slug_source_value = "kwyjibo"
452
-
453
- obj = self.model.objects.get(**{self.slug_source: self.slug_test_object})
454
- expected_slug = self.slugify_function(getattr(obj, self.slug_source))
455
- # Update slug source field str
456
- filter_ = self.slug_source + "__exact"
457
- self.model.objects.filter(**{filter_: self.slug_test_object}).update(
458
- **{self.slug_source: new_slug_source_value}
459
- )
460
-
461
- obj.refresh_from_db()
462
- self.assertEqual(getattr(obj, self.slug_source), new_slug_source_value)
463
- self.assertEqual(obj.slug, expected_slug)
464
-
465
432
  class EditObjectViewTestCase(ModelViewTestCase):
466
433
  """
467
434
  Edit a single existing instance.
@@ -494,10 +461,7 @@ class ViewTestCases:
494
461
  instance = self._get_queryset().first()
495
462
 
496
463
  # Assign model-level permission
497
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
498
- obj_perm.save()
499
- obj_perm.users.add(self.user)
500
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
464
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
501
465
 
502
466
  # Try GET with model-level permission
503
467
  self.assertHttpStatus(self.client.get(self._get_url("edit", instance)), 200)
@@ -618,10 +582,7 @@ class ViewTestCases:
618
582
  instance_note_pk_list.append(note.pk)
619
583
 
620
584
  # Assign model-level permission
621
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
622
- obj_perm.save()
623
- obj_perm.users.add(self.user)
624
- obj_perm.object_types.add(assigned_object_type)
585
+ self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
625
586
 
626
587
  # Try GET with model-level permission
627
588
  self.assertHttpStatus(self.client.get(self._get_url("delete", instance)), 200)
@@ -657,10 +618,7 @@ class ViewTestCases:
657
618
  instance = self.get_deletable_object()
658
619
 
659
620
  # Assign model-level permission
660
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
661
- obj_perm.save()
662
- obj_perm.users.add(self.user)
663
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
621
+ self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
664
622
 
665
623
  # Try GET with model-level permission
666
624
  self.assertHttpStatus(self.client.get(self._get_url("delete", instance)), 200)
@@ -865,10 +823,7 @@ class ViewTestCases:
865
823
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
866
824
  def test_list_objects_with_permission(self):
867
825
  # Add model-level permission
868
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["view"])
869
- obj_perm.save()
870
- obj_perm.users.add(self.user)
871
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
826
+ self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
872
827
 
873
828
  # Try GET with model-level permission
874
829
  response = self.client.get(self._get_url("list"))
@@ -881,11 +836,17 @@ class ViewTestCases:
881
836
  # Check if breadcrumb is rendered correctly
882
837
  self.assertBodyContains(response, f'<a href="{list_url}">{title}</a>', html=True)
883
838
 
884
- # Check if import button is absent due to user permissions
885
- self.assertNotIn(
886
- reverse("extras:job_run_by_class_path", kwargs={"class_path": "nautobot.core.jobs.ImportObjects"}),
887
- response_body,
888
- )
839
+ with self.subTest("Assert import-objects URL is absent due to user permissions"):
840
+ self.assertNotIn(
841
+ reverse("extras:job_run_by_class_path", kwargs={"class_path": "nautobot.core.jobs.ImportObjects"}),
842
+ response_body,
843
+ )
844
+
845
+ if "example_app" in settings.PLUGINS:
846
+ with self.subTest("Assert example-app banner is present"):
847
+ self.assertIn(
848
+ f"<div>You are viewing a table of {self.model._meta.verbose_name_plural}</div>", response_body
849
+ )
889
850
 
890
851
  return response
891
852
 
@@ -992,13 +953,7 @@ class ViewTestCases:
992
953
  }
993
954
 
994
955
  # Assign non-constrained permission
995
- obj_perm = users_models.ObjectPermission(
996
- name="Test permission",
997
- actions=["add"],
998
- )
999
- obj_perm.save()
1000
- obj_perm.users.add(self.user)
1001
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
956
+ self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
1002
957
 
1003
958
  # Bulk create objects
1004
959
  response = self.client.post(**request)
@@ -1052,34 +1007,6 @@ class ViewTestCases:
1052
1007
  pass
1053
1008
  self.assertEqual(matching_count, self.bulk_create_count)
1054
1009
 
1055
- class BulkImportObjectsViewTestCase(ModelViewTestCase): # 3.0 TODO: remove this test mixin, no longer relevant.
1056
- """
1057
- Vestigial test case, to be removed in 3.0.
1058
-
1059
- This is vestigial since the introduction of the ImportObjects system Job to handle bulk-import of all
1060
- content-types via REST API serializers. The parsing of CSV data by the serializer is exercised by
1061
- APIViewTestCases.CreateObjectViewTestCase.test_recreate_object_csv(), and the basic operation of the Job is
1062
- exercised by nautobot.core.tests.test_jobs.
1063
- """
1064
-
1065
- csv_data = ()
1066
-
1067
- def _get_csv_data(self):
1068
- return "\n".join(self.csv_data)
1069
-
1070
- # Just in case Apps are extending any of these tests and calling super() in them.
1071
- def test_bulk_import_objects_without_permission(self):
1072
- pass
1073
-
1074
- def test_bulk_import_objects_with_permission(self):
1075
- pass
1076
-
1077
- def test_bulk_import_objects_with_permission_csv_file(self):
1078
- pass
1079
-
1080
- def test_bulk_import_objects_with_constrained_permission(self):
1081
- pass
1082
-
1083
1010
  class BulkEditObjectsViewTestCase(ModelViewTestCase):
1084
1011
  """
1085
1012
  Edit multiple instances.
@@ -1094,6 +1021,17 @@ class ViewTestCases:
1094
1021
  for instance in self._get_queryset().filter(pk__in=pk_list):
1095
1022
  self.assertInstanceEqual(instance, self.bulk_edit_data)
1096
1023
 
1024
+ def validate_redirect_to_job_result(self, response):
1025
+ # Get the last Bulk Edit Objects JobResult created
1026
+ job_result = JobResult.objects.filter(name="Bulk Edit Objects").first()
1027
+ # Assert redirect to Job Results
1028
+ self.assertRedirects(
1029
+ response,
1030
+ reverse("extras:jobresult", args=[job_result.pk]),
1031
+ status_code=302,
1032
+ target_status_code=200,
1033
+ )
1034
+
1097
1035
  def test_bulk_edit_objects_without_permission(self):
1098
1036
  pk_list = list(self._get_queryset().values_list("pk", flat=True)[:3])
1099
1037
  data = {
@@ -1117,14 +1055,10 @@ class ViewTestCases:
1117
1055
  data.update(utils.post_data(self.bulk_edit_data))
1118
1056
 
1119
1057
  # Assign model-level permission
1120
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
1121
- obj_perm.save()
1122
- obj_perm.users.add(self.user)
1123
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1058
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
1124
1059
 
1125
- # Try POST with model-level permission
1126
- self.assertHttpStatus(self.client.post(self._get_url("bulk_edit"), data), 302)
1127
- self.validate_object_data_after_bulk_edit(pk_list)
1060
+ response = self.client.post(self._get_url("bulk_edit"), data)
1061
+ self.validate_redirect_to_job_result(response)
1128
1062
 
1129
1063
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1130
1064
  def test_bulk_edit_form_contains_all_pks(self):
@@ -1135,10 +1069,7 @@ class ViewTestCases:
1135
1069
  "_all": "on",
1136
1070
  }
1137
1071
  # Assign model-level permission
1138
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
1139
- obj_perm.save()
1140
- obj_perm.users.add(self.user)
1141
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1072
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
1142
1073
 
1143
1074
  # Try POST with model-level permission
1144
1075
  response = self.client.post(self._get_url("bulk_edit"), selected_data)
@@ -1152,7 +1083,7 @@ class ViewTestCases:
1152
1083
  for pk in pk_list:
1153
1084
  self.assertNotIn(str(pk), response_body)
1154
1085
  self.assertInHTML(
1155
- '<input type="hidden" name="_all" value="True" class="form-control" required="required" placeholder="None" id="id__all">',
1086
+ '<input type="hidden" name="_all" value="True" class="form-control" placeholder="None" id="id__all">',
1156
1087
  response_body,
1157
1088
  )
1158
1089
 
@@ -1188,7 +1119,7 @@ class ViewTestCases:
1188
1119
  self.assertNotIn(str(third_pk), response_body)
1189
1120
  self.assertIn("Editing 2 ", response_body)
1190
1121
  self.assertInHTML(
1191
- '<input type="hidden" name="_all" value="True" class="form-control" required="required" placeholder="None" id="id__all">',
1122
+ '<input type="hidden" name="_all" value="True" class="form-control" placeholder="None" id="id__all">',
1192
1123
  response_body,
1193
1124
  )
1194
1125
 
@@ -1228,21 +1159,9 @@ class ViewTestCases:
1228
1159
 
1229
1160
  # Attempt to bulk edit permitted objects into a non-permitted state
1230
1161
  response = self.client.post(self._get_url("bulk_edit"), data)
1231
- # 200 because we're sent back to the edit form to try again; if the update were successful it'd be a 302
1232
- self.assertHttpStatus(response, 200)
1233
- # Assert that the objects are NOT updated
1234
- for instance in self._get_queryset().filter(pk__in=pk_list):
1235
- self.assertIn(field.value_from_object(instance), values)
1236
- self.assertNotEqual(field.value_from_object(instance), self.bulk_edit_data[attr_name])
1237
-
1238
- # Update permission constraints to permit all objects
1239
- obj_perm.constraints = {"pk__gt": 0}
1240
- obj_perm.save()
1241
-
1242
- # Bulk edit permitted objects and expect a redirect back to the list view
1243
- self.assertHttpStatus(self.client.post(self._get_url("bulk_edit"), data), 302)
1244
- # Assert that the objects were all updated correctly
1245
- self.validate_object_data_after_bulk_edit(pk_list)
1162
+ # NOTE: There is no way of testing constrained failure as bulk edit is a system Job;
1163
+ # and can only be tested by checking JobLogs.
1164
+ self.validate_redirect_to_job_result(response)
1246
1165
 
1247
1166
  class BulkDeleteObjectsViewTestCase(ModelViewTestCase):
1248
1167
  """
@@ -1274,7 +1193,6 @@ class ViewTestCases:
1274
1193
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
1275
1194
  def test_bulk_delete_objects_with_permission(self):
1276
1195
  pk_list = self.get_deletable_object_pks()
1277
- initial_count = self._get_queryset().count()
1278
1196
  data = {
1279
1197
  "pk": pk_list,
1280
1198
  "confirm": True,
@@ -1282,14 +1200,18 @@ class ViewTestCases:
1282
1200
  }
1283
1201
 
1284
1202
  # Assign unconstrained permission
1285
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
1286
- obj_perm.save()
1287
- obj_perm.users.add(self.user)
1288
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1203
+ self.add_permissions(
1204
+ f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}", "extras.view_jobresult"
1205
+ )
1289
1206
 
1290
- # Try POST with model-level permission
1291
- self.assertHttpStatus(self.client.post(self._get_url("bulk_delete"), data), 302)
1292
- self.assertEqual(self._get_queryset().count(), initial_count - len(pk_list))
1207
+ response = self.client.post(self._get_url("bulk_delete"), data)
1208
+ job_result = JobResult.objects.filter(name="Bulk Delete Objects").first()
1209
+ self.assertRedirects(
1210
+ response,
1211
+ reverse("extras:jobresult", args=[job_result.pk]),
1212
+ status_code=302,
1213
+ target_status_code=200,
1214
+ )
1293
1215
 
1294
1216
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
1295
1217
  def test_bulk_delete_form_contains_all_objects(self):
@@ -1301,10 +1223,7 @@ class ViewTestCases:
1301
1223
  }
1302
1224
 
1303
1225
  # Assign unconstrained permission
1304
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
1305
- obj_perm.save()
1306
- obj_perm.users.add(self.user)
1307
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1226
+ self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
1308
1227
 
1309
1228
  # Try POST with the selected data first. Emulating selecting all -> pressing Delete Selected button.
1310
1229
  response = self.client.post(self._get_url("bulk_delete"), selected_data)
@@ -1376,9 +1295,16 @@ class ViewTestCases:
1376
1295
  obj_perm.constraints = {"pk__isnull": False} # Match a non-existent pk (i.e., allow all)
1377
1296
  obj_perm.save()
1378
1297
 
1379
- # Bulk delete permitted objects
1380
- self.assertHttpStatus(self.client.post(self._get_url("bulk_delete"), data), 302)
1381
- self.assertEqual(self._get_queryset().count(), initial_count - len(pk_list))
1298
+ # User would be redirected to Job Result therefore user needs to have permission to view Job Result
1299
+ self.add_permissions("extras.view_jobresult")
1300
+ response = self.client.post(self._get_url("bulk_delete"), data)
1301
+ job_result = JobResult.objects.filter(name="Bulk Delete Objects").first()
1302
+ self.assertRedirects(
1303
+ response,
1304
+ reverse("extras:jobresult", args=[job_result.pk]),
1305
+ status_code=302,
1306
+ target_status_code=200,
1307
+ )
1382
1308
 
1383
1309
  class BulkRenameObjectsViewTestCase(ModelViewTestCase):
1384
1310
  """
@@ -1418,10 +1344,7 @@ class ViewTestCases:
1418
1344
  data.update(self.rename_data)
1419
1345
 
1420
1346
  # Assign model-level permission
1421
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
1422
- obj_perm.save()
1423
- obj_perm.users.add(self.user)
1424
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1347
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
1425
1348
 
1426
1349
  # Try POST with model-level permission
1427
1350
  self.assertHttpStatus(self.client.post(self._get_url("bulk_rename"), data), 302)
@@ -1537,10 +1460,7 @@ class ViewTestCases:
1537
1460
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1538
1461
  def test_bulk_add_component(self):
1539
1462
  """Test bulk-adding this component to devices/virtual-machines."""
1540
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["add"])
1541
- obj_perm.save()
1542
- obj_perm.users.add(self.user)
1543
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1463
+ self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
1544
1464
 
1545
1465
  initial_count = self._get_queryset().count()
1546
1466
 
@@ -1584,10 +1504,7 @@ class ViewTestCases:
1584
1504
 
1585
1505
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1586
1506
  def test_bulk_rename(self):
1587
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
1588
- obj_perm.save()
1589
- obj_perm.users.add(self.user)
1590
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1507
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
1591
1508
 
1592
1509
  objects = self.selected_objects
1593
1510
  pk_list = [obj.pk for obj in objects]
@@ -14,7 +14,7 @@ class AuthenticationEnforcedTestCase(TestCase):
14
14
 
15
15
  def test_all_views_require_authentication(self):
16
16
  self.client.logout()
17
- url_patterns = get_url_patterns()
17
+ url_patterns = get_url_patterns(ignore_redirects=True)
18
18
 
19
19
  for url_pattern in url_patterns:
20
20
  with self.subTest(url_pattern=url_pattern):