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,11 +1,15 @@
1
+ import graphene
2
+
1
3
  from nautobot.core.graphql.types import OptimizedNautobotObjectType
2
4
  from nautobot.extras.filters import (
3
5
  ContactAssociationFilterSet,
4
6
  DynamicGroupFilterSet,
7
+ JobFilterSet,
8
+ ScheduledJobFilterSet,
5
9
  StatusFilterSet,
6
10
  TagFilterSet,
7
11
  )
8
- from nautobot.extras.models import ContactAssociation, DynamicGroup, Status, Tag
12
+ from nautobot.extras.models import ContactAssociation, DynamicGroup, Job, ScheduledJob, Status, Tag
9
13
 
10
14
 
11
15
  class ContactAssociationType(OptimizedNautobotObjectType):
@@ -24,6 +28,26 @@ class DynamicGroupType(OptimizedNautobotObjectType):
24
28
  filterset_class = DynamicGroupFilterSet
25
29
 
26
30
 
31
+ class JobType(OptimizedNautobotObjectType):
32
+ """Graphql Type Object for Job model."""
33
+
34
+ class Meta:
35
+ model = Job
36
+ filterset_class = JobFilterSet
37
+
38
+ task_queues = graphene.List(graphene.String)
39
+
40
+
41
+ class ScheduledJobType(OptimizedNautobotObjectType):
42
+ """Graphql Type Object for Job model."""
43
+
44
+ class Meta:
45
+ model = ScheduledJob
46
+ filterset_class = ScheduledJobFilterSet
47
+
48
+ queue = graphene.String
49
+
50
+
27
51
  class StatusType(OptimizedNautobotObjectType):
28
52
  """Graphql Type object for `Status` model."""
29
53
 
nautobot/extras/jobs.py CHANGED
@@ -18,6 +18,7 @@ from db_file_storage.form_widgets import DBClearableFileInput
18
18
  from django import forms
19
19
  from django.conf import settings
20
20
  from django.contrib.auth import get_user_model
21
+ from django.core.cache import cache
21
22
  from django.core.exceptions import ObjectDoesNotExist
22
23
  from django.core.files.base import ContentFile
23
24
  from django.core.files.uploadedfile import UploadedFile
@@ -30,25 +31,32 @@ import netaddr
30
31
  import yaml
31
32
 
32
33
  from nautobot.core.celery import import_jobs, nautobot_task
34
+ from nautobot.core.events import publish_event
33
35
  from nautobot.core.forms import (
34
36
  DynamicModelChoiceField,
35
37
  DynamicModelMultipleChoiceField,
36
38
  JSONField,
37
39
  )
38
40
  from nautobot.core.utils.config import get_settings_or_config
41
+ from nautobot.core.utils.logging import sanitize
39
42
  from nautobot.core.utils.lookup import get_model_from_name
40
- from nautobot.extras.choices import JobResultStatusChoices, ObjectChangeActionChoices, ObjectChangeEventContextChoices
43
+ from nautobot.extras.choices import (
44
+ JobResultStatusChoices,
45
+ ObjectChangeActionChoices,
46
+ ObjectChangeEventContextChoices,
47
+ )
41
48
  from nautobot.extras.context_managers import web_request_context
42
49
  from nautobot.extras.forms import JobForm
43
50
  from nautobot.extras.models import (
44
51
  FileProxy,
45
52
  Job as JobModel,
46
53
  JobHook,
54
+ JobQueue,
47
55
  JobResult,
48
56
  ObjectChange,
49
57
  )
50
58
  from nautobot.extras.registry import registry
51
- from nautobot.extras.utils import change_logged_models_queryset, task_queues_as_choices
59
+ from nautobot.extras.utils import change_logged_models_queryset
52
60
  from nautobot.ipam.formfields import IPAddressFormField, IPNetworkFormField
53
61
  from nautobot.ipam.validators import (
54
62
  MaxPrefixLengthValidator,
@@ -60,15 +68,15 @@ User = get_user_model()
60
68
 
61
69
 
62
70
  __all__ = [
63
- "Job",
64
71
  "BooleanVar",
65
72
  "ChoiceVar",
66
73
  "FileVar",
67
- "IntegerVar",
68
74
  "IPAddressVar",
69
75
  "IPAddressWithMaskVar",
70
76
  "IPNetworkVar",
77
+ "IntegerVar",
71
78
  "JSONVar",
79
+ "Job",
72
80
  "MultiChoiceVar",
73
81
  "MultiObjectVar",
74
82
  "ObjectVar",
@@ -107,6 +115,7 @@ class BaseJob:
107
115
  - task_queues (list)
108
116
  - template_name (str)
109
117
  - time_limit (int)
118
+ - is_singleton (bool)
110
119
  """
111
120
 
112
121
  def __init__(self):
@@ -184,6 +193,31 @@ class BaseJob:
184
193
  )
185
194
  raise RunJobTaskFailed(f"Job {self.job_model} is not enabled to be run!")
186
195
 
196
+ ignore_singleton_lock = self.celery_kwargs.get("nautobot_job_ignore_singleton_lock", False)
197
+ if self.job_model.is_singleton:
198
+ is_running = cache.get(self.singleton_cache_key)
199
+ if is_running:
200
+ if ignore_singleton_lock:
201
+ self.logger.info(
202
+ "Job %s is a singleton and already running, but singleton will be ignored because"
203
+ " `ignore_singleton_lock` is set.",
204
+ self.job_model,
205
+ extra={"object": self.job_model, "grouping": "initialization"},
206
+ )
207
+ else:
208
+ self.logger.error(
209
+ "Job %s is a singleton and already running.",
210
+ self.job_model,
211
+ extra={"object": self.job_model, "grouping": "initialization"},
212
+ )
213
+ raise RunJobTaskFailed(f"Job '{self.job_model}' is a singleton and already running.")
214
+ cache_parameters = {
215
+ "key": self.singleton_cache_key,
216
+ "value": 1,
217
+ "timeout": self.job_model.time_limit or settings.CELERY_TASK_TIME_LIMIT,
218
+ }
219
+ cache.set(**cache_parameters)
220
+
187
221
  soft_time_limit = self.job_model.soft_time_limit or settings.CELERY_TASK_SOFT_TIME_LIMIT
188
222
  time_limit = self.job_model.time_limit or settings.CELERY_TASK_TIME_LIMIT
189
223
  if time_limit <= soft_time_limit:
@@ -275,7 +309,15 @@ class BaseJob:
275
309
  self._delete_file_proxies(*file_ids)
276
310
 
277
311
  if status == JobResultStatusChoices.STATUS_SUCCESS:
278
- self.logger.info("Job completed", extra={"grouping": "post_run"})
312
+ self.logger.success("Job completed", extra={"grouping": "post_run"})
313
+
314
+ cache.delete(self.singleton_cache_key)
315
+
316
+ @final
317
+ @classproperty
318
+ def singleton_cache_key(cls) -> str: # pylint: disable=no-self-argument
319
+ """Cache key for singleton jobs."""
320
+ return f"nautobot.extras.jobs.running.{cls.class_path}"
279
321
 
280
322
  @final
281
323
  @classproperty
@@ -396,6 +438,11 @@ class BaseJob:
396
438
  def task_queues(cls) -> list: # pylint: disable=no-self-argument
397
439
  return cls._get_meta_attr_and_assert_type("task_queues", [], expected_type=(list, tuple))
398
440
 
441
+ @final
442
+ @classproperty
443
+ def is_singleton(cls) -> bool: # pylint: disable=no-self-argument
444
+ return cls._get_meta_attr_and_assert_type("is_singleton", False, expected_type=bool)
445
+
399
446
  @final
400
447
  @classproperty
401
448
  def properties_dict(cls) -> dict: # pylint: disable=no-self-argument
@@ -414,6 +461,7 @@ class BaseJob:
414
461
  "time_limit": cls.time_limit,
415
462
  "has_sensitive_variables": cls.has_sensitive_variables,
416
463
  "task_queues": cls.task_queues,
464
+ "is_singleton": cls.is_singleton,
417
465
  }
418
466
 
419
467
  @final
@@ -475,18 +523,41 @@ class BaseJob:
475
523
  """
476
524
 
477
525
  form = cls.as_form_class()(data, files, initial=initial)
478
-
526
+ form.fields["_profile"] = forms.BooleanField(
527
+ required=False,
528
+ initial=False,
529
+ label="Profile job execution",
530
+ help_text="Profiles the job execution using cProfile and outputs a report to /tmp/",
531
+ )
532
+ # If the class already exists there may be overrides, so we have to check this.
479
533
  try:
480
- job_model = JobModel.objects.get_for_class_path(cls.class_path)
481
- dryrun_default = job_model.dryrun_default if job_model.dryrun_default_override else cls.dryrun_default
482
- task_queues = job_model.task_queues if job_model.task_queues_override else cls.task_queues
534
+ job_model = JobModel.objects.get(module_name=cls.__module__, job_class_name=cls.__name__)
535
+ is_singleton = job_model.is_singleton
483
536
  except JobModel.DoesNotExist:
484
- logger.error("No Job instance found in the database corresponding to %s", cls.class_path)
485
- dryrun_default = cls.dryrun_default
486
- task_queues = cls.task_queues
537
+ is_singleton = cls.is_singleton
538
+ if is_singleton:
539
+ form.fields["_ignore_singleton_lock"] = forms.BooleanField(
540
+ required=False,
541
+ initial=False,
542
+ label="Ignore singleton lock",
543
+ help_text="Allow this singleton job to run even when another instance is already running",
544
+ )
487
545
 
488
- # Update task queue choices
489
- form.fields["_task_queue"].choices = task_queues_as_choices(task_queues)
546
+ job_model = JobModel.objects.get_for_class_path(cls.class_path)
547
+ dryrun_default = job_model.dryrun_default if job_model.dryrun_default_override else cls.dryrun_default
548
+ job_queue_queryset = JobQueue.objects.filter(jobs=job_model)
549
+ job_queue_params = {"jobs": [job_model.pk]}
550
+
551
+ # Initialize job_queue choices
552
+ form.fields["_job_queue"] = DynamicModelChoiceField(
553
+ queryset=job_queue_queryset,
554
+ query_params=job_queue_params,
555
+ required=False,
556
+ help_text="The job queue to route this job to",
557
+ label="Job queue",
558
+ )
559
+ # Populate the job queue field on the JobRun Form
560
+ form.fields["_job_queue"].initial = job_model.default_job_queue.pk
490
561
 
491
562
  if cls.supports_dryrun and (not initial or "dryrun" not in initial):
492
563
  # Set initial "dryrun" checkbox state based on the Meta parameter
@@ -503,6 +574,13 @@ class BaseJob:
503
574
  for _, field in form.fields.items():
504
575
  field.disabled = True
505
576
 
577
+ # Ensure non-Job-specific fields are still last after applying field_order
578
+ for field in ["_job_queue", "_profile", "_ignore_singleton_lock"]:
579
+ if field not in form.fields:
580
+ continue
581
+ value = form.fields.pop(field)
582
+ form.fields[field] = value
583
+
506
584
  return form
507
585
 
508
586
  @functools.cached_property
@@ -721,7 +799,7 @@ class BaseJob:
721
799
  """
722
800
  if isinstance(content, str):
723
801
  content = content.encode("utf-8")
724
- max_size = get_settings_or_config("JOB_CREATE_FILE_MAX_SIZE")
802
+ max_size = get_settings_or_config("JOB_CREATE_FILE_MAX_SIZE", fallback=10 << 20)
725
803
  actual_size = len(content)
726
804
  if actual_size > max_size:
727
805
  raise ValueError(f"Provided {actual_size} bytes of content, but JOB_CREATE_FILE_MAX_SIZE is {max_size}")
@@ -1133,16 +1211,32 @@ def run_job(self, job_class_path, *args, **kwargs):
1133
1211
  raise KeyError(f"Job class not found for class path {job_class_path}")
1134
1212
  job = job_class()
1135
1213
  job.request = self.request
1214
+ job_result = JobResult.objects.get(id=self.request.id)
1215
+ payload = {
1216
+ "job_result_id": self.request.id,
1217
+ "job_name": job.name,
1218
+ "user_name": job_result.user.username,
1219
+ }
1220
+ if not job.job_model.has_sensitive_variables:
1221
+ payload["job_kwargs"] = kwargs
1136
1222
  try:
1223
+ publish_event(topic="nautobot.jobs.job.started", payload=payload)
1137
1224
  job.before_start(self.request.id, args, kwargs)
1138
1225
  result = job(*args, **kwargs)
1139
1226
  job.on_success(result, self.request.id, args, kwargs)
1140
1227
  job.after_return(JobResultStatusChoices.STATUS_SUCCESS, result, self.request.id, args, kwargs, None)
1228
+ payload["job_output"] = result
1229
+ publish_event(topic="nautobot.jobs.job.completed", payload=payload)
1141
1230
  return result
1142
1231
  except Exception as exc:
1143
1232
  einfo = ExceptionInfo(sys.exc_info())
1144
1233
  job.on_failure(exc, self.request.id, args, kwargs, einfo)
1145
1234
  job.after_return(JobResultStatusChoices.STATUS_FAILURE, exc, self.request.id, args, kwargs, einfo)
1235
+ payload["einfo"] = {
1236
+ "exc_type": type(exc).__name__,
1237
+ "exc_message": sanitize(str(exc)),
1238
+ }
1239
+ publish_event(topic="nautobot.jobs.job.completed", payload=payload)
1146
1240
  raise
1147
1241
 
1148
1242
 
@@ -28,6 +28,7 @@ STATUS_CHOICESET_MAP = {
28
28
  "dcim.InterfaceRedundancyGroup": dcim_choices.InterfaceRedundancyGroupStatusChoices,
29
29
  "dcim.SoftwareImageFile": dcim_choices.SoftwareImageFileStatusChoices,
30
30
  "dcim.SoftwareVersion": dcim_choices.SoftwareVersionStatusChoices,
31
+ "dcim.VirtualDeviceContext": dcim_choices.VirtualDeviceContextStatusChoices,
31
32
  "extras.ContactAssociation": extras_choices.ContactAssociationStatusChoices,
32
33
  "ipam.IPAddress": ipam_choices.IPAddressStatusChoices,
33
34
  "ipam.Prefix": ipam_choices.PrefixStatusChoices,
@@ -1,14 +1,11 @@
1
- import json
2
1
  import time
3
2
 
4
3
  from django.contrib.auth import get_user_model
5
4
  from django.core.management.base import BaseCommand, CommandError
6
- from django.utils import timezone
7
5
 
8
- from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
9
- from nautobot.extras.jobs import get_job
10
- from nautobot.extras.models import Job, JobLogEntry, JobResult
11
- from nautobot.extras.utils import get_worker_count
6
+ from nautobot.extras.choices import JobResultStatusChoices
7
+ from nautobot.extras.management.utils import report_job_status, validate_job_and_job_data
8
+ from nautobot.extras.models import Job, JobResult
12
9
 
13
10
 
14
11
  class Command(BaseCommand):
@@ -38,42 +35,15 @@ class Command(BaseCommand):
38
35
  def handle(self, *args, **options):
39
36
  if "." not in options["job"]:
40
37
  raise CommandError('Job must be specified in the Python module form: "<module_name>.<JobClassName>"')
41
- job_class = get_job(options["job"])
42
- if not job_class:
43
- raise CommandError(f'Job "{options["job"]}" not found')
44
-
45
38
  User = get_user_model()
46
39
  try:
47
40
  user = User.objects.get(username=options["username"])
48
41
  except User.DoesNotExist as exc:
49
42
  raise CommandError("No such user") from exc
50
43
 
51
- data = {}
52
- try:
53
- if options.get("data"):
54
- data = json.loads(options["data"])
55
- except json.decoder.JSONDecodeError as error:
56
- raise CommandError(f"Invalid JSON data:\n{error!s}") from error
57
-
58
- try:
59
- job_model = Job.objects.get_for_class_path(options["job"])
60
- except Job.DoesNotExist as error:
61
- raise CommandError(f"Job {options['job']} does not exist.") from error
62
-
63
- try:
64
- job_model = Job.objects.restrict(user, "run").get_for_class_path(options["job"])
65
- except Job.DoesNotExist:
66
- raise CommandError(f"User {options['username']} does not have permission to run this Job") from None
67
-
68
- if not job_model.installed or job_model.job_class is None:
69
- raise CommandError("Job is not presently installed")
70
- if not job_model.enabled:
71
- raise CommandError("Job is not presently enabled to be run")
72
- if not options["local"] and not get_worker_count():
73
- raise CommandError("No Celery worker detected. Perhaps you meant to specify --local?")
74
-
75
- # Run the job and create a new JobResult
76
- self.stdout.write(f"[{timezone.now():%H:%M:%S}] Running {options['job']}...")
44
+ job_class_path = options["job"]
45
+ data = validate_job_and_job_data(self, user, job_class_path, options.get("data"))
46
+ job_model = Job.objects.get_for_class_path(job_class_path)
77
47
 
78
48
  if options["local"]:
79
49
  job_result = JobResult.execute_job(job_model, user, profile=options["profile"], **data)
@@ -86,46 +56,4 @@ class Command(BaseCommand):
86
56
  job_result.refresh_from_db()
87
57
 
88
58
  # Report on success/failure
89
- groups = set(JobLogEntry.objects.filter(job_result=job_result).values_list("grouping", flat=True))
90
- for group in sorted(groups):
91
- logs = JobLogEntry.objects.filter(job_result__pk=job_result.pk, grouping=group)
92
- debug_count = logs.filter(log_level=LogLevelChoices.LOG_DEBUG).count()
93
- info_count = logs.filter(log_level=LogLevelChoices.LOG_INFO).count()
94
- warning_count = logs.filter(log_level=LogLevelChoices.LOG_WARNING).count()
95
- error_count = logs.filter(log_level=LogLevelChoices.LOG_ERROR).count()
96
- critical_count = logs.filter(log_level=LogLevelChoices.LOG_CRITICAL).count()
97
-
98
- self.stdout.write(
99
- f"\t{group}: {debug_count} debug, {info_count} info, {warning_count} warning, {error_count} error, {critical_count} critical"
100
- )
101
-
102
- for log_entry in logs:
103
- status = log_entry.log_level
104
- if status == "success":
105
- status = self.style.SUCCESS(status)
106
- elif status == "info":
107
- status = status
108
- elif status == "warning":
109
- status = self.style.WARNING(status)
110
- elif status == "failure":
111
- status = self.style.NOTICE(status)
112
-
113
- if log_entry.log_object:
114
- self.stdout.write(f"\t\t{status}: {log_entry.log_object}: {log_entry.message}")
115
- else:
116
- self.stdout.write(f"\t\t{status}: {log_entry.message}")
117
-
118
- if job_result.result:
119
- self.stdout.write(str(job_result.result))
120
-
121
- if job_result.status == JobResultStatusChoices.STATUS_FAILURE:
122
- status = self.style.ERROR("FAILURE")
123
- elif job_result.status == JobResultStatusChoices.STATUS_SUCCESS:
124
- status = self.style.SUCCESS("SUCCESS")
125
- else:
126
- status = self.style.WARNING(job_result.status)
127
- self.stdout.write(f"[{timezone.now():%H:%M:%S}] {options['job']}: {status}")
128
-
129
- # Wrap things up
130
- self.stdout.write(f"[{timezone.now():%H:%M:%S}] {options['job']}: Duration {job_result.duration}")
131
- self.stdout.write(f"[{timezone.now():%H:%M:%S}] Finished")
59
+ report_job_status(self, job_result)
@@ -0,0 +1,46 @@
1
+ from django.core.management.base import BaseCommand, CommandError
2
+
3
+ from nautobot.extras.choices import JobResultStatusChoices
4
+ from nautobot.extras.management.utils import report_job_status, validate_job_and_job_data
5
+ from nautobot.extras.models import JobResult
6
+
7
+
8
+ class Command(BaseCommand):
9
+ help = "Run a job (script, report) to validate or update data in Nautobot"
10
+
11
+ def add_arguments(self, parser):
12
+ parser.add_argument(
13
+ "job_result",
14
+ help="Pass in an existing job result id from the database to continue executing the job on a local system",
15
+ )
16
+ parser.add_argument(
17
+ "--profile",
18
+ action="store_true",
19
+ help="Run cProfile on the job execution and write the result to the disk under /tmp.",
20
+ )
21
+
22
+ def handle(self, *args, **options):
23
+ job_result = None
24
+ job_result_id = options["job_result"]
25
+ try:
26
+ job_result = JobResult.objects.get(pk=job_result_id)
27
+ except JobResult.DoesNotExist:
28
+ raise CommandError(f"Job result with pk {job_result_id} not found.")
29
+ if job_result.status != JobResultStatusChoices.STATUS_PENDING:
30
+ raise CommandError(
31
+ f"Job result has an invalid status {job_result.status} for this command."
32
+ f" You can only pass in a job result with status {JobResultStatusChoices.STATUS_PENDING}"
33
+ )
34
+
35
+ job_user = job_result.user
36
+ job_model = job_result.job_model
37
+ job_class_path = job_model.class_path
38
+
39
+ data = validate_job_and_job_data(self, job_user, job_class_path, job_result.task_kwargs)
40
+
41
+ # execute_job here implies "--local"
42
+ job_result = JobResult.execute_job(
43
+ job_model, job_user, profile=options["profile"], job_result=job_result, **data
44
+ )
45
+ # Report on success/failure
46
+ report_job_status(self, job_result)
@@ -0,0 +1,87 @@
1
+ import json
2
+
3
+ from django.core.management.base import CommandError
4
+ from django.utils import timezone
5
+
6
+ from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
7
+ from nautobot.extras.jobs import get_job
8
+ from nautobot.extras.models import Job, JobLogEntry
9
+
10
+
11
+ def validate_job_and_job_data(command, user, job_class_path, data=None):
12
+ job_data = {}
13
+ try:
14
+ if data:
15
+ job_data = json.loads(data)
16
+ except json.decoder.JSONDecodeError as error:
17
+ raise CommandError(f"Invalid JSON data:\n{error!s}") from error
18
+
19
+ if not get_job(job_class_path, reload=True):
20
+ raise CommandError(f'Job "{job_class_path}" not found')
21
+
22
+ try:
23
+ job_model = Job.objects.get_for_class_path(job_class_path)
24
+ except Job.DoesNotExist as error:
25
+ raise CommandError(f"Job {job_class_path} does not exist.") from error
26
+
27
+ try:
28
+ job_model = Job.objects.restrict(user, "run").get_for_class_path(job_class_path)
29
+ except Job.DoesNotExist:
30
+ raise CommandError(f"User {user.username} does not have permission to run this Job") from None
31
+
32
+ if not job_model.installed or job_model.job_class is None:
33
+ raise CommandError("Job is not presently installed")
34
+ if not job_model.enabled:
35
+ raise CommandError("Job is not presently enabled to be run")
36
+
37
+ # Run the job and create a new JobResult
38
+ command.stdout.write(f"[{timezone.now():%H:%M:%S}] Running {job_class_path}...")
39
+ return job_data
40
+
41
+
42
+ def report_job_status(command, job_result):
43
+ # Report on success/failure
44
+ job_class_path = job_result.job_model.class_path
45
+ groups = set(JobLogEntry.objects.filter(job_result=job_result).values_list("grouping", flat=True))
46
+ for group in sorted(groups):
47
+ logs = JobLogEntry.objects.filter(job_result__pk=job_result.pk, grouping=group)
48
+ debug_count = logs.filter(log_level=LogLevelChoices.LOG_DEBUG).count()
49
+ info_count = logs.filter(log_level=LogLevelChoices.LOG_INFO).count()
50
+ warning_count = logs.filter(log_level=LogLevelChoices.LOG_WARNING).count()
51
+ error_count = logs.filter(log_level=LogLevelChoices.LOG_ERROR).count()
52
+ critical_count = logs.filter(log_level=LogLevelChoices.LOG_CRITICAL).count()
53
+
54
+ command.stdout.write(
55
+ f"\t{group}: {debug_count} debug, {info_count} info, {warning_count} warning, {error_count} error, {critical_count} critical"
56
+ )
57
+
58
+ for log_entry in logs:
59
+ status = log_entry.log_level
60
+ if status == "success":
61
+ status = command.style.SUCCESS(status)
62
+ elif status == "info":
63
+ status = status
64
+ elif status == "warning":
65
+ status = command.style.WARNING(status)
66
+ elif status == "failure":
67
+ status = command.style.NOTICE(status)
68
+
69
+ if log_entry.log_object:
70
+ command.stdout.write(f"\t\t{status}: {log_entry.log_object}: {log_entry.message}")
71
+ else:
72
+ command.stdout.write(f"\t\t{status}: {log_entry.message}")
73
+
74
+ if job_result.result:
75
+ command.stdout.write(str(job_result.result))
76
+
77
+ if job_result.status == JobResultStatusChoices.STATUS_FAILURE:
78
+ status = command.style.ERROR("FAILURE")
79
+ elif job_result.status == JobResultStatusChoices.STATUS_SUCCESS:
80
+ status = command.style.SUCCESS("SUCCESS")
81
+ else:
82
+ status = command.style.WARNING(job_result.status)
83
+ command.stdout.write(f"[{timezone.now():%H:%M:%S}] {job_class_path}: {status}")
84
+
85
+ # Wrap things up
86
+ command.stdout.write(f"[{timezone.now():%H:%M:%S}] {job_class_path}: Duration {job_result.duration}")
87
+ command.stdout.write(f"[{timezone.now():%H:%M:%S}] Finished")
@@ -0,0 +1,129 @@
1
+ # Generated by Django 4.2.15 on 2024-08-13 20:36
2
+
3
+ import uuid
4
+
5
+ import django.core.serializers.json
6
+ from django.db import migrations, models
7
+ import django.db.models.deletion
8
+
9
+ import nautobot.core.models.fields
10
+ import nautobot.extras.models.mixins
11
+
12
+
13
+ class Migration(migrations.Migration):
14
+ dependencies = [
15
+ ("tenancy", "0009_update_all_charfields_max_length_to_255"),
16
+ ("extras", "0116_fix_dynamic_group_group_type_data_migration"),
17
+ ]
18
+
19
+ operations = [
20
+ migrations.CreateModel(
21
+ name="JobQueue",
22
+ fields=[
23
+ (
24
+ "id",
25
+ models.UUIDField(
26
+ default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
27
+ ),
28
+ ),
29
+ ("created", models.DateTimeField(auto_now_add=True, null=True)),
30
+ ("last_updated", models.DateTimeField(auto_now=True, null=True)),
31
+ (
32
+ "_custom_field_data",
33
+ models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
34
+ ),
35
+ ("name", models.CharField(max_length=255, unique=True)),
36
+ ("description", models.CharField(blank=True, max_length=255)),
37
+ ("queue_type", models.CharField(max_length=50)),
38
+ ("tags", nautobot.core.models.fields.TagsField(through="extras.TaggedItem", to="extras.Tag")),
39
+ (
40
+ "tenant",
41
+ models.ForeignKey(
42
+ blank=True,
43
+ null=True,
44
+ on_delete=django.db.models.deletion.PROTECT,
45
+ related_name="job_queues",
46
+ to="tenancy.tenant",
47
+ ),
48
+ ),
49
+ ],
50
+ options={
51
+ "ordering": ["name"],
52
+ },
53
+ bases=(
54
+ nautobot.extras.models.mixins.DynamicGroupMixin,
55
+ nautobot.extras.models.mixins.NotesMixin,
56
+ models.Model,
57
+ ),
58
+ ),
59
+ migrations.CreateModel(
60
+ name="JobQueueAssignment",
61
+ fields=[
62
+ (
63
+ "id",
64
+ models.UUIDField(
65
+ default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
66
+ ),
67
+ ),
68
+ (
69
+ "job",
70
+ models.ForeignKey(
71
+ on_delete=django.db.models.deletion.CASCADE,
72
+ related_name="job_queue_assignments",
73
+ to="extras.job",
74
+ ),
75
+ ),
76
+ (
77
+ "job_queue",
78
+ models.ForeignKey(
79
+ on_delete=django.db.models.deletion.CASCADE,
80
+ related_name="job_assignments",
81
+ to="extras.jobqueue",
82
+ ),
83
+ ),
84
+ ],
85
+ options={
86
+ "ordering": ["job", "job_queue"],
87
+ "unique_together": {("job", "job_queue")},
88
+ },
89
+ ),
90
+ migrations.AddField(
91
+ model_name="job",
92
+ name="job_queues",
93
+ field=models.ManyToManyField(
94
+ through="extras.JobQueueAssignment", to="extras.jobqueue", related_name="jobs"
95
+ ),
96
+ ),
97
+ migrations.AddField(
98
+ model_name="job",
99
+ name="default_job_queue_override",
100
+ field=models.BooleanField(default=False),
101
+ ),
102
+ migrations.AddField(
103
+ model_name="job",
104
+ name="default_job_queue",
105
+ field=models.ForeignKey(
106
+ blank=True,
107
+ null=True,
108
+ on_delete=django.db.models.deletion.SET_NULL,
109
+ to="extras.jobqueue",
110
+ related_name="default_for_jobs",
111
+ ),
112
+ ),
113
+ migrations.RenameField(
114
+ model_name="job",
115
+ old_name="task_queues_override",
116
+ new_name="job_queues_override",
117
+ ),
118
+ migrations.AddField(
119
+ model_name="scheduledjob",
120
+ name="job_queue",
121
+ field=models.ForeignKey(
122
+ blank=True,
123
+ null=True,
124
+ on_delete=django.db.models.deletion.SET_NULL,
125
+ related_name="scheduled_jobs",
126
+ to="extras.jobqueue",
127
+ ),
128
+ ),
129
+ ]