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
@@ -12,6 +12,7 @@ from constance.test import override_config
12
12
  from django.conf import settings
13
13
  from django.contrib.contenttypes.models import ContentType
14
14
  from django.core.cache import cache
15
+ from django.core.exceptions import ValidationError
15
16
  from django.core.files.uploadedfile import SimpleUploadedFile
16
17
  from django.core.management import call_command
17
18
  from django.core.management.base import CommandError
@@ -30,12 +31,15 @@ from nautobot.dcim.models import Device, Location, LocationType
30
31
  from nautobot.extras import models
31
32
  from nautobot.extras.choices import (
32
33
  JobExecutionType,
34
+ JobQueueTypeChoices,
33
35
  JobResultStatusChoices,
34
36
  LogLevelChoices,
35
37
  ObjectChangeEventContextChoices,
36
38
  )
37
39
  from nautobot.extras.context_managers import change_logging, JobHookChangeContext, web_request_context
38
40
  from nautobot.extras.jobs import get_job, get_jobs
41
+ from nautobot.extras.models import Job, JobQueue
42
+ from nautobot.extras.models.jobs import JobLogEntry
39
43
 
40
44
 
41
45
  class JobTest(TestCase):
@@ -71,7 +75,7 @@ class JobTest(TestCase):
71
75
  name = "TestFieldOrder"
72
76
  job_class = get_job(f"{module}.{name}")
73
77
  form = job_class().as_form()
74
- self.assertSequenceEqual(list(form.fields.keys()), ["var1", "var2", "var23", "_task_queue", "_profile"])
78
+ self.assertSequenceEqual(list(form.fields.keys()), ["var1", "var2", "var23", "_job_queue", "_profile"])
75
79
 
76
80
  def test_no_field_order(self):
77
81
  """
@@ -81,7 +85,7 @@ class JobTest(TestCase):
81
85
  name = "TestNoFieldOrder"
82
86
  job_class = get_job(f"{module}.{name}")
83
87
  form = job_class().as_form()
84
- self.assertSequenceEqual(list(form.fields.keys()), ["var23", "var2", "_task_queue", "_profile"])
88
+ self.assertSequenceEqual(list(form.fields.keys()), ["var23", "var2", "_job_queue", "_profile"])
85
89
 
86
90
  def test_no_field_order_inherited_variable(self):
87
91
  """
@@ -93,7 +97,7 @@ class JobTest(TestCase):
93
97
  form = job_class().as_form()
94
98
  self.assertSequenceEqual(
95
99
  list(form.fields.keys()),
96
- ["testvar1", "b_testvar2", "a_testvar3", "_task_queue", "_profile"],
100
+ ["testvar1", "b_testvar2", "a_testvar3", "_job_queue", "_profile"],
97
101
  )
98
102
 
99
103
  def test_dryrun_default(self):
@@ -115,47 +119,36 @@ class JobTest(TestCase):
115
119
  form = job_class().as_form()
116
120
  self.assertEqual(form.fields["dryrun"].initial, job_model.dryrun_default)
117
121
 
118
- @mock.patch("nautobot.extras.utils.get_celery_queues")
119
- def test_job_class_task_queues(self, mock_get_celery_queues):
120
- """
121
- Test job form with custom task queues defined on the job class
122
- """
123
- module = "task_queues"
124
- name = "TestWorkerQueues"
125
- mock_get_celery_queues.return_value = {"celery": 4, "irrelevant": 5}
126
- job_class, _ = get_job_class_and_model(module, name)
127
- form = job_class().as_form()
128
- self.assertInHTML(
129
- """<tr><th><label for="id__task_queue">Task queue:</label></th>
130
- <td><select name="_task_queue" class="form-control" placeholder="Task queue" id="id__task_queue">
131
- <option value="celery">celery (4 workers)</option>
132
- <option value="nonexistent">nonexistent (0 workers)</option></select><br>
133
- <span class="helptext">The task queue to route this job to</span>
134
- <input type="hidden" name="_profile" value="False" id="id__profile"></td></tr>""",
135
- form.as_table(),
136
- )
122
+ def test_job_task_queues_setter(self):
123
+ """Test the task_queues property setter on Job."""
124
+ module = "dry_run"
125
+ name = "TestDryRun"
126
+ _, job_model = get_job_class_and_model(module, name)
127
+
128
+ invalid_queue = "Invalid job Queue"
129
+ with self.assertRaises(ValidationError) as cm:
130
+ job_model.task_queues = [invalid_queue]
131
+ job_model.validated_save()
132
+ self.assertIn(f"Job Queue {invalid_queue} does not exist in the database.", str(cm.exception))
137
133
 
138
- @mock.patch("nautobot.extras.utils.get_celery_queues")
139
- def test_job_class_task_queues_override(self, mock_get_celery_queues):
134
+ def test_job_class_job_queues(self):
140
135
  """
141
- Test job form with custom task queues defined on the job class and overridden on the model
136
+ Test job form with custom task queues defined on the job class
142
137
  """
143
138
  module = "task_queues"
144
139
  name = "TestWorkerQueues"
145
- mock_get_celery_queues.return_value = {"default": 1, "irrelevant": 5}
146
140
  job_class, job_model = get_job_class_and_model(module, name)
147
- job_model.task_queues = ["default", "priority"]
148
- job_model.task_queues_override = True
149
- job_model.save()
141
+ jq_1, _ = models.JobQueue.objects.get_or_create(
142
+ name="celery", defaults={"queue_type": JobQueueTypeChoices.TYPE_CELERY}
143
+ )
144
+ jq_2, _ = models.JobQueue.objects.get_or_create(
145
+ name="irrelevant", defaults={"queue_type": JobQueueTypeChoices.TYPE_CELERY}
146
+ )
147
+ job_model.job_queues.set([jq_1, jq_2])
150
148
  form = job_class().as_form()
151
- self.assertInHTML(
152
- """<tr><th><label for="id__task_queue">Task queue:</label></th>
153
- <td><select name="_task_queue" class="form-control" placeholder="Task queue" id="id__task_queue">
154
- <option value="default">default (1 worker)</option>
155
- <option value="priority">priority (0 workers)</option>
156
- </select><br><span class="helptext">The task queue to route this job to</span>
157
- <input type="hidden" name="_profile" value="False" id="id__profile"></td></tr>""",
158
- form.as_table(),
149
+ self.assertQuerySetEqual(
150
+ form.fields["_job_queue"].queryset,
151
+ models.JobQueue.objects.filter(jobs=job_model),
159
152
  )
160
153
 
161
154
  def test_supports_dryrun(self):
@@ -200,13 +193,15 @@ class JobTest(TestCase):
200
193
  with override_settings(JOBS_ROOT=temp_dir):
201
194
  # Create a new Job and make sure it's discovered correctly
202
195
  with open(os.path.join(temp_dir, "my_jobs.py"), "w") as fd:
203
- fd.write("""\
196
+ fd.write(
197
+ """\
204
198
  from nautobot.apps.jobs import Job, register_jobs
205
199
  class MyJob(Job):
206
200
  def run(self):
207
201
  pass
208
202
  register_jobs(MyJob)
209
- """)
203
+ """
204
+ )
210
205
  jobs_data = get_jobs(reload=True)
211
206
  self.assertIn("my_jobs.MyJob", jobs_data.keys())
212
207
  self.assertIsNotNone(get_job("my_jobs.MyJob"))
@@ -216,11 +211,13 @@ register_jobs(MyJob)
216
211
 
217
212
  # Create a second Job in the same module
218
213
  with open(os.path.join(temp_dir, "my_jobs.py"), "a") as fd:
219
- fd.write("""
214
+ fd.write(
215
+ """
220
216
  class MyOtherJob(MyJob):
221
217
  pass
222
218
  register_jobs(MyOtherJob)
223
- """)
219
+ """
220
+ )
224
221
  jobs_data = get_jobs(reload=True)
225
222
  self.assertIn("my_jobs.MyJob", jobs_data.keys())
226
223
  self.assertIsNotNone(get_job("my_jobs.MyJob"))
@@ -229,14 +226,16 @@ register_jobs(MyOtherJob)
229
226
 
230
227
  # Create a third Job in another module
231
228
  with open(os.path.join(temp_dir, "their_jobs.py"), "w") as fd:
232
- fd.write("""
229
+ fd.write(
230
+ """
233
231
  from nautobot.apps.jobs import Job, register_jobs
234
232
 
235
233
  class MyJob(Job):
236
234
  def run(self):
237
235
  pass
238
236
  register_jobs(MyJob)
239
- """)
237
+ """
238
+ )
240
239
  jobs_data = get_jobs(reload=True)
241
240
  self.assertIn("my_jobs.MyJob", jobs_data.keys())
242
241
  self.assertIsNotNone(get_job("my_jobs.MyJob"))
@@ -258,14 +257,16 @@ register_jobs(MyJob)
258
257
 
259
258
  # Create a module with an inauspicious name
260
259
  with open(os.path.join(temp_dir, "traceback.py"), "w") as fd:
261
- fd.write("""
260
+ fd.write(
261
+ """
262
262
  from nautobot.apps.jobs import Job, register_jobs
263
263
 
264
264
  class BadJob(Job):
265
265
  def run(self):
266
266
  raise RuntimeError("You ran a bad job!")
267
267
  register_jobs(BadJob)
268
- """)
268
+ """
269
+ )
269
270
  jobs_data = get_jobs(reload=True)
270
271
  self.assertIn("my_jobs.MyJob", jobs_data.keys())
271
272
  self.assertIsNotNone(get_job("my_jobs.MyJob"))
@@ -297,6 +298,105 @@ class JobTransactionTest(TransactionTestCase):
297
298
  self.request.id = uuid.uuid4()
298
299
  self.request.user = self.user
299
300
 
301
+ def test_bulk_delete_system_jobs_fail(self):
302
+ system_job_queryset = Job.objects.filter(module_name__startswith="nautobot.")
303
+ pk_list = [str(pk) for pk in system_job_queryset.values_list("pk", flat=True)[:3]]
304
+ initial_count = Job.objects.all().count()
305
+ self.add_permissions("extras.delete_job")
306
+ job_result = create_job_result_and_run_job(
307
+ "nautobot.core.jobs.bulk_actions",
308
+ "BulkDeleteObjects",
309
+ content_type=ContentType.objects.get_for_model(Job).id,
310
+ delete_all=False,
311
+ filter_query_params={},
312
+ pk_list=pk_list,
313
+ username=self.user.username,
314
+ )
315
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
316
+ error_log = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
317
+ self.assertIn(
318
+ f"Unable to delete Job {system_job_queryset.first()}. System Job cannot be deleted", error_log.message
319
+ )
320
+ self.assertEqual(initial_count, Job.objects.all().count())
321
+
322
+ def test_job_bulk_edit_default_queue_stays_default_after_one_field_update(self):
323
+ self.add_permissions("extras.change_job", "extras.view_job")
324
+ job_class_to_test = "TestPassJob"
325
+ job_description = "default job queue test"
326
+ job_to_test = Job.objects.get(job_class_name=job_class_to_test)
327
+ queryset = Job.objects.filter(installed=True, hidden=False, job_class_name=job_class_to_test)
328
+ default_job_queue = job_to_test.default_job_queue
329
+ pk_list = list(queryset.values_list("pk", flat=True))
330
+
331
+ job_ct = ContentType.objects.get_for_model(Job)
332
+ job_result = create_job_result_and_run_job(
333
+ "nautobot.core.jobs.bulk_actions",
334
+ "BulkEditObjects",
335
+ content_type=job_ct.id,
336
+ edit_all=False,
337
+ filter_query_params={},
338
+ form_data={
339
+ "pk": pk_list,
340
+ "description": job_description,
341
+ },
342
+ username=self.user.username,
343
+ )
344
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
345
+ self.assertEqual(Job.objects.filter(description=job_description).count(), queryset.count())
346
+ self.assertFalse(
347
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
348
+ )
349
+ self.assertFalse(
350
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
351
+ )
352
+ instance = Job.objects.get(job_class_name=job_class_to_test)
353
+ self.assertEqual(instance.description, job_description)
354
+ self.assertEqual(instance.default_job_queue, default_job_queue)
355
+
356
+ def test_job_bulk_edit_preserve_job_queues_after_one_field_update(self):
357
+ self.add_permissions("extras.change_job", "extras.view_job")
358
+ job_class_to_test = "TestPassJob"
359
+ job_description = "job queues test"
360
+ job_to_test = Job.objects.get(job_class_name=job_class_to_test)
361
+
362
+ # Simulate 3 job queues set
363
+ job_queues = JobQueue.objects.all()[:3]
364
+ job_to_test.job_queues.set(job_queues)
365
+ job_to_test.job_queues.add(job_to_test.default_job_queue)
366
+ job_to_test.job_queues_override = True
367
+ job_to_test.save()
368
+
369
+ expected_job_queues = list(job_to_test.job_queues.values_list("pk", flat=True))
370
+
371
+ queryset = Job.objects.filter(installed=True, hidden=False, job_class_name=job_class_to_test)
372
+ pk_list = list(queryset.values_list("pk", flat=True))
373
+
374
+ job_ct = ContentType.objects.get_for_model(Job)
375
+ job_result = create_job_result_and_run_job(
376
+ "nautobot.core.jobs.bulk_actions",
377
+ "BulkEditObjects",
378
+ content_type=job_ct.id,
379
+ edit_all=False,
380
+ filter_query_params={},
381
+ form_data={
382
+ "pk": pk_list,
383
+ "description": job_description,
384
+ },
385
+ username=self.user.username,
386
+ )
387
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
388
+ self.assertEqual(Job.objects.filter(description=job_description).count(), queryset.count())
389
+ self.assertFalse(
390
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
391
+ )
392
+ self.assertFalse(
393
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
394
+ )
395
+
396
+ instance = Job.objects.get(job_class_name=job_class_to_test)
397
+ self.assertEqual(instance.description, job_description)
398
+ self.assertQuerySetEqual(instance.job_queues.all(), JobQueue.objects.filter(pk__in=expected_job_queues))
399
+
300
400
  def test_job_hard_time_limit_less_than_soft_time_limit(self):
301
401
  """
302
402
  Job test which produces a warning log message because the time_limit is less than the soft_time_limit.
@@ -615,6 +715,38 @@ class JobTransactionTest(TransactionTestCase):
615
715
  self.assertTrue(profiling_result.exists())
616
716
  profiling_result.unlink()
617
717
 
718
+ def test_job_singleton(self):
719
+ module = "singleton"
720
+ name = "TestSingletonJob"
721
+
722
+ job_class, _ = get_job_class_and_model(module, name, "local")
723
+ self.assertTrue(job_class.is_singleton)
724
+ cache.set(job_class.singleton_cache_key, 1)
725
+ failed_job_result = create_job_result_and_run_job(module, name)
726
+
727
+ self.assertEqual(
728
+ failed_job_result.status, JobResultStatusChoices.STATUS_FAILURE, msg="Duplicate singleton job didn't error."
729
+ )
730
+ self.assertIsNone(cache.get(job_class.singleton_cache_key, None))
731
+
732
+ def test_job_ignore_singleton(self):
733
+ module = "singleton"
734
+ name = "TestSingletonJob"
735
+
736
+ job_class, _ = get_job_class_and_model(module, name, "local")
737
+ self.assertTrue(job_class.is_singleton)
738
+ cache.set(job_class.singleton_cache_key, 1)
739
+ passed_job_result = create_job_result_and_run_job(
740
+ module, name, celery_kwargs={"nautobot_job_ignore_singleton_lock": True}
741
+ )
742
+
743
+ self.assertEqual(
744
+ passed_job_result.status,
745
+ JobResultStatusChoices.STATUS_SUCCESS,
746
+ msg="Duplicate singleton job didn't succeed with nautobot_job_ignore_singleton_lock=True.",
747
+ )
748
+ self.assertIsNone(cache.get(job_class.singleton_cache_key, None))
749
+
618
750
  @mock.patch("nautobot.extras.context_managers.enqueue_webhooks")
619
751
  def test_job_fires_webhooks(self, mock_enqueue_webhooks):
620
752
  module = "atomic_transaction"
@@ -890,9 +1022,7 @@ class JobButtonReceiverTest(TestCase):
890
1022
  name = "TestJobButtonReceiverSimple"
891
1023
  job_class, _job_model = get_job_class_and_model(module, name)
892
1024
  form = job_class().as_form()
893
- self.assertSequenceEqual(
894
- list(form.fields.keys()), ["object_pk", "object_model_name", "_task_queue", "_profile"]
895
- )
1025
+ self.assertSequenceEqual(list(form.fields.keys()), ["object_pk", "object_model_name", "_job_queue", "_profile"])
896
1026
 
897
1027
  def test_hidden(self):
898
1028
  module = "job_button_receiver"
@@ -955,7 +1085,7 @@ class JobHookReceiverTest(TestCase):
955
1085
  name = "TestJobHookReceiverLog"
956
1086
  job_class, _job_model = get_job_class_and_model(module, name)
957
1087
  form = job_class().as_form()
958
- self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_task_queue", "_profile"])
1088
+ self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_job_queue", "_profile"])
959
1089
 
960
1090
  def test_hidden(self):
961
1091
  module = "job_hook_receiver"
@@ -1090,7 +1220,7 @@ class JobHookTransactionTest(TransactionTestCase): # TODO: BaseModelTestCase mi
1090
1220
  ("info", "action: create"),
1091
1221
  ("info", f"jobresult.user: {self.user.username}"),
1092
1222
  ("info", "Test Job Hook Location 1"),
1093
- ("info", "Job completed"),
1223
+ ("success", "Job completed"),
1094
1224
  ]
1095
1225
  log_messages = models.JobLogEntry.objects.filter(job_result=job_result).values_list("log_level", "message")
1096
1226
  self.assertSequenceEqual(log_messages, expected_log_messages)
@@ -1116,7 +1246,7 @@ class JobHookTransactionTest(TransactionTestCase): # TODO: BaseModelTestCase mi
1116
1246
  ("info", "action: update"),
1117
1247
  ("info", f"jobresult.user: {self.user.username}"),
1118
1248
  ("info", "Test Job Hook Location 1"),
1119
- ("info", "Job completed"),
1249
+ ("success", "Job completed"),
1120
1250
  ]
1121
1251
  log_messages = models.JobLogEntry.objects.filter(job_result=job_result).values_list("log_level", "message")
1122
1252
  self.assertSequenceEqual(log_messages, expected_log_messages)
@@ -4,6 +4,7 @@ import tempfile
4
4
  from unittest import expectedFailure, mock
5
5
  import uuid
6
6
  import warnings
7
+ from zoneinfo import ZoneInfo
7
8
 
8
9
  from django.conf import settings
9
10
  from django.contrib.auth import get_user_model
@@ -19,11 +20,6 @@ from django_celery_beat.tzcrontab import TzAwareCrontab
19
20
  from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
20
21
  import time_machine
21
22
 
22
- try:
23
- from zoneinfo import ZoneInfo
24
- except ImportError: # python 3.8
25
- from backports.zoneinfo import ZoneInfo
26
-
27
23
  from nautobot.circuits.models import CircuitType
28
24
  from nautobot.core.choices import ColorChoices
29
25
  from nautobot.core.testing import TestCase
@@ -66,6 +62,7 @@ from nautobot.extras.models import (
66
62
  GitRepository,
67
63
  Job as JobModel,
68
64
  JobLogEntry,
65
+ JobQueue,
69
66
  JobResult,
70
67
  MetadataChoice,
71
68
  MetadataType,
@@ -1135,6 +1132,11 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
1135
1132
  getattr(job_model.job_class, field_name),
1136
1133
  field_name,
1137
1134
  )
1135
+ if not job_model.job_queues_override:
1136
+ self.assertEqual(
1137
+ sorted(job_model.task_queues),
1138
+ sorted(job_model.job_class.task_queues) or [settings.CELERY_TASK_DEFAULT_QUEUE],
1139
+ )
1138
1140
  except AssertionError:
1139
1141
  print(list(JobModel.objects.all()))
1140
1142
  print(registry["jobs"])
@@ -1159,7 +1161,6 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
1159
1161
  "has_sensitive_variables": not self.job_containing_sensitive_variables.has_sensitive_variables,
1160
1162
  "soft_time_limit": 350,
1161
1163
  "time_limit": 650,
1162
- "task_queues": ["overridden", "worker", "queues"],
1163
1164
  }
1164
1165
 
1165
1166
  # Override values to non-defaults and ensure they are preserved
@@ -1237,6 +1238,25 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
1237
1238
  "A job that may have sensitive variables cannot be marked as requiring approval",
1238
1239
  )
1239
1240
 
1241
+ def test_default_job_queue_always_included_in_job_queues(self):
1242
+ default_job_queue = JobQueue.objects.first()
1243
+ job_queues = list(JobQueue.objects.exclude(pk=default_job_queue.pk))[:3]
1244
+
1245
+ job = JobModel.objects.first()
1246
+ job.default_job_queue = default_job_queue
1247
+ job.save()
1248
+ job.job_queues.set(job_queues)
1249
+
1250
+ self.assertTrue(job.job_queues.filter(pk=default_job_queue.pk).exists())
1251
+
1252
+
1253
+ class JobQueueTest(ModelTestCases.BaseModelTestCase):
1254
+ """
1255
+ Tests for the `JobQueue` model class.
1256
+ """
1257
+
1258
+ model = JobQueue
1259
+
1240
1260
 
1241
1261
  class MetadataChoiceTest(ModelTestCases.BaseModelTestCase):
1242
1262
  model = MetadataChoice
@@ -1862,6 +1882,14 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
1862
1882
  start_time=datetime(year=2050, month=1, day=22, hour=0, minute=0, tzinfo=ZoneInfo("America/New_York")),
1863
1883
  )
1864
1884
 
1885
+ def test_scheduled_job_queue_setter(self):
1886
+ """Test the queue property setter on ScheduledJob."""
1887
+ invalid_queue = "Invalid job Queue"
1888
+ with self.assertRaises(ValidationError) as cm:
1889
+ self.daily_utc_job.queue = invalid_queue
1890
+ self.daily_utc_job.validated_save()
1891
+ self.assertIn(f"Job Queue {invalid_queue} does not exist in the database.", str(cm.exception))
1892
+
1865
1893
  def test_schedule(self):
1866
1894
  """Test the schedule property."""
1867
1895
  with self.subTest("Test TYPE_DAILY schedules"):
@@ -2092,6 +2120,33 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
2092
2120
  is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 9, 17, 0, tzinfo=ZoneInfo("America/New_York")))
2093
2121
  self.assertTrue(is_due)
2094
2122
 
2123
+ # TODO uncomment when we have a way to setup the NautobotDatabaseScheduler correctly
2124
+ # @mock.patch("nautobot.extras.utils.run_kubernetes_job_and_return_job_result")
2125
+ # def test_nautobot_database_scheduler_apply_async_method(self, mock_run_kubernetes_job_and_return_job_result):
2126
+ # jq = JobQueue.objects.create(name="kubernetes", queue_type=JobQueueTypeChoices.TYPE_KUBERNETES)
2127
+ # sj = ScheduledJob.objects.create(
2128
+ # name="Export Object List Hourly",
2129
+ # task="nautobot.core.jobs.ExportObjectList",
2130
+ # job_model=JobModel.objects.get(name="Export Object List"),
2131
+ # interval=JobExecutionType.TYPE_HOURLY,
2132
+ # user=User.objects.first(),
2133
+ # approval_required=False,
2134
+ # start_time=datetime.now(ZoneInfo("America/New_York")),
2135
+ # time_zone=ZoneInfo("America/New_York"),
2136
+ # job_queue=jq,
2137
+ # kwargs='{"content_type": 1}',
2138
+ # )
2139
+ # jr = JobResult.objects.create(
2140
+ # name=sj.job_model.name,
2141
+ # job_model=sj.job_model,
2142
+ # scheduled_job=sj,
2143
+ # user=sj.user,
2144
+ # )
2145
+ # mock_run_kubernetes_job_and_return_job_result.return_value = jr
2146
+ # entry = NautobotScheduleEntry(model=sj)
2147
+ # scheduler = NautobotDatabaseScheduler(app=entry.app)
2148
+ # scheduler.apply_async(entry=entry, producer=None, advance=False)
2149
+
2095
2150
 
2096
2151
  class SecretTest(ModelTestCases.BaseModelTestCase):
2097
2152
  """
@@ -1,5 +1,6 @@
1
- from unittest import mock, skip, skipIf
1
+ from unittest import mock, skip
2
2
 
3
+ from django.apps import apps
3
4
  from django.conf import settings
4
5
  from django.contrib.contenttypes.models import ContentType
5
6
  from django.core.exceptions import ValidationError
@@ -21,6 +22,7 @@ from nautobot.extras.models import CustomField, Relationship, RelationshipAssoci
21
22
  from nautobot.extras.plugins.exceptions import PluginImproperlyConfigured
22
23
  from nautobot.extras.plugins.utils import load_plugin
23
24
  from nautobot.extras.plugins.validators import wrap_model_clean_methods
25
+ from nautobot.extras.plugins.views import extract_app_data
24
26
  from nautobot.extras.registry import DatasourceContent, registry
25
27
  from nautobot.ipam.models import IPAddress, Namespace, Prefix
26
28
  from nautobot.tenancy.filters import TenantFilterSet
@@ -315,6 +317,61 @@ class AppListViewTest(TestCase):
315
317
  response_body = extract_page_body(response.content.decode(response.charset)).lower()
316
318
  self.assertIn("example app", response_body, msg=response_body)
317
319
 
320
+ def test_extract_app_data(self):
321
+ app_config = apps.get_app_config("example_app")
322
+ # Without a corresponding entry in marketplace_data
323
+ self.assertEqual(
324
+ extract_app_data(app_config, {"apps": []}),
325
+ {
326
+ "name": "Example Nautobot App",
327
+ "package": "example_app",
328
+ "app_label": "example_app",
329
+ "author": "Nautobot development team",
330
+ "author_email": "nautobot@example.com",
331
+ "headline": "For testing purposes only",
332
+ "description": "For testing purposes only",
333
+ "version": "1.0.0",
334
+ "home_url": "plugins:example_app:home",
335
+ "config_url": "plugins:example_app:config",
336
+ "docs_url": "plugins:example_app:docs",
337
+ },
338
+ )
339
+
340
+ # With a corresponding entry in marketplace_data
341
+ mock_marketplace_data = {
342
+ "apps": [
343
+ {
344
+ "name": "Nautobot Example App",
345
+ "use_cases": ["Example", "Development"],
346
+ "requires": [],
347
+ "docs": "https://example.com/docs/example_app/",
348
+ "headline": "Demonstrate and test various parts of Nautobot App APIs and functionality.",
349
+ "description": "An example of the kinds of things a Nautobot App can do, including ...",
350
+ "package_name": "example_app",
351
+ "author": "Network to Code (NTC)",
352
+ "availability": "Open Source",
353
+ "icon": "...",
354
+ },
355
+ ],
356
+ }
357
+ self.assertEqual(
358
+ extract_app_data(app_config, mock_marketplace_data),
359
+ {
360
+ "name": "Nautobot Example App",
361
+ "package": "example_app",
362
+ "app_label": "example_app",
363
+ "author": "Network to Code (NTC)",
364
+ "author_email": "nautobot@example.com",
365
+ "availability": "Open Source",
366
+ "headline": "Demonstrate and test various parts of Nautobot App APIs and functionality.",
367
+ "description": "An example of the kinds of things a Nautobot App can do, including ...",
368
+ "version": "1.0.0",
369
+ "home_url": "plugins:example_app:home",
370
+ "config_url": "plugins:example_app:config",
371
+ "docs_url": "plugins:example_app:docs",
372
+ },
373
+ )
374
+
318
375
 
319
376
  class PluginDetailViewTest(TestCase):
320
377
  def test_view_detail_anonymous(self):
@@ -800,10 +857,6 @@ class TestAppCoreViewOverrides(TestCase):
800
857
  )
801
858
 
802
859
 
803
- @skipIf(
804
- "example_plugin" not in settings.PLUGINS,
805
- "example_plugin not in settings.PLUGINS",
806
- )
807
860
  class PluginTemplateExtensionsTest(TestCase):
808
861
  """
809
862
  Test that registered TemplateExtensions inject content as expected
@@ -823,19 +876,19 @@ class PluginTemplateExtensionsTest(TestCase):
823
876
  def test_detail_view_buttons(self):
824
877
  response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
825
878
  response_body = extract_page_body(response.content.decode(response.charset))
826
- self.assertIn("LOCATION CONTENT - BUTTONS", response_body, msg=response_body)
879
+ self.assertIn("APP INJECTED LOCATION CONTENT - BUTTONS", response_body, msg=response_body)
827
880
 
828
881
  def test_detail_view_left_page(self):
829
882
  response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
830
883
  response_body = extract_page_body(response.content.decode(response.charset))
831
- self.assertIn("LOCATION CONTENT - LEFT PAGE", response_body, msg=response_body)
884
+ self.assertIn("App Injected Content - Left", response_body, msg=response_body)
832
885
 
833
886
  def test_detail_view_right_page(self):
834
887
  response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
835
888
  response_body = extract_page_body(response.content.decode(response.charset))
836
- self.assertIn("LOCATION CONTENT - RIGHT PAGE", response_body, msg=response_body)
889
+ self.assertIn("App Injected Content - Right", response_body, msg=response_body)
837
890
 
838
891
  def test_detail_view_full_width_page(self):
839
892
  response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
840
893
  response_body = extract_page_body(response.content.decode(response.charset))
841
- self.assertIn("LOCATION CONTENT - FULL WIDTH PAGE", response_body, msg=response_body)
894
+ self.assertIn("App Injected Content - Full Width", response_body, msg=response_body)