nautobot 2.0.0a2__py3-none-any.whl → 2.0.0b1__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.
Files changed (1029) hide show
  1. nautobot/__init__.py +1 -5
  2. nautobot/apps/api.py +6 -8
  3. nautobot/apps/forms.py +0 -2
  4. nautobot/apps/ui.py +0 -8
  5. nautobot/circuits/api/serializers.py +9 -119
  6. nautobot/circuits/api/urls.py +1 -1
  7. nautobot/circuits/api/views.py +0 -1
  8. nautobot/circuits/choices.py +0 -2
  9. nautobot/circuits/filters.py +7 -6
  10. nautobot/circuits/forms.py +3 -73
  11. nautobot/circuits/migrations/0001_initial_part_1.py +0 -1
  12. nautobot/circuits/migrations/0002_initial_part_2.py +0 -1
  13. nautobot/circuits/migrations/0003_auto_slug.py +0 -1
  14. nautobot/circuits/migrations/0004_increase_provider_account_length.py +0 -1
  15. nautobot/circuits/migrations/0005_providernetwork.py +0 -1
  16. nautobot/circuits/migrations/0006_cache_circuit_terminations.py +0 -1
  17. nautobot/circuits/migrations/0007_circuitterminations_primary_model.py +0 -1
  18. nautobot/circuits/migrations/0008_add_natural_indexing.py +0 -1
  19. nautobot/circuits/migrations/0009_circuittermination_location.py +0 -1
  20. nautobot/circuits/migrations/0010_rename_foreign_keys_and_related_names.py +0 -1
  21. nautobot/circuits/migrations/0011_remove_site_foreign_key_from_circuit_termination_class.py +0 -1
  22. nautobot/circuits/migrations/0012_created_datetime.py +0 -1
  23. nautobot/circuits/migrations/0013_alter_circuittermination__path.py +0 -1
  24. nautobot/circuits/migrations/0014_related_name_changes.py +1 -2
  25. nautobot/circuits/migrations/0015_remove_circuittype_provider_slug.py +20 -0
  26. nautobot/circuits/migrations/0016_tagsfield.py +34 -0
  27. nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
  28. nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
  29. nautobot/circuits/models.py +3 -93
  30. nautobot/circuits/navigation.py +14 -69
  31. nautobot/circuits/signals.py +0 -2
  32. nautobot/circuits/tables.py +42 -5
  33. nautobot/circuits/templates/circuits/circuit_retrieve.html +1 -1
  34. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -1
  35. nautobot/circuits/templates/circuits/circuittype_retrieve.html +1 -1
  36. nautobot/circuits/templates/circuits/provider_create.html +0 -1
  37. nautobot/circuits/templates/circuits/provider_retrieve.html +1 -1
  38. nautobot/circuits/tests/integration/test_relationships.py +13 -16
  39. nautobot/circuits/tests/test_api.py +13 -43
  40. nautobot/circuits/tests/test_filters.py +20 -15
  41. nautobot/circuits/tests/test_models.py +7 -3
  42. nautobot/circuits/tests/test_views.py +57 -67
  43. nautobot/circuits/views.py +18 -9
  44. nautobot/core/api/__init__.py +8 -2
  45. nautobot/core/api/authentication.py +0 -3
  46. nautobot/core/api/fields.py +15 -6
  47. nautobot/core/api/filter_backends.py +3 -2
  48. nautobot/core/api/metadata.py +237 -30
  49. nautobot/core/api/mixins.py +94 -0
  50. nautobot/core/api/pagination.py +3 -3
  51. nautobot/core/api/parsers.py +154 -0
  52. nautobot/core/api/renderers.py +153 -2
  53. nautobot/core/api/schema.py +47 -3
  54. nautobot/core/api/serializers.py +377 -37
  55. nautobot/core/api/urls.py +11 -3
  56. nautobot/core/api/utils.py +174 -2
  57. nautobot/core/api/versioning.py +32 -10
  58. nautobot/core/api/views.py +266 -75
  59. nautobot/core/apps/__init__.py +138 -221
  60. nautobot/core/celery/__init__.py +112 -41
  61. nautobot/core/celery/backends.py +19 -13
  62. nautobot/core/celery/control.py +46 -0
  63. nautobot/core/celery/encoders.py +53 -0
  64. nautobot/core/celery/log.py +38 -0
  65. nautobot/core/celery/schedulers.py +23 -4
  66. nautobot/core/celery/task.py +1 -16
  67. nautobot/core/checks.py +0 -27
  68. nautobot/core/choices.py +21 -113
  69. nautobot/core/{cli.py → cli/__init__.py} +1 -2
  70. nautobot/core/cli/__main__.py +3 -0
  71. nautobot/core/constants.py +25 -43
  72. nautobot/core/context_processors.py +12 -0
  73. nautobot/core/filters.py +2 -2
  74. nautobot/core/forms/__init__.py +0 -4
  75. nautobot/core/forms/fields.py +39 -68
  76. nautobot/core/forms/forms.py +27 -27
  77. nautobot/core/forms/utils.py +7 -59
  78. nautobot/core/forms/widgets.py +0 -1
  79. nautobot/core/graphql/__init__.py +2 -2
  80. nautobot/core/graphql/schema.py +4 -27
  81. nautobot/core/jobs/__init__.py +75 -0
  82. nautobot/core/management/commands/build_ui.py +255 -0
  83. nautobot/core/management/commands/celery.py +0 -1
  84. nautobot/core/management/commands/generate_test_data.py +18 -13
  85. nautobot/core/management/commands/post_upgrade.py +24 -24
  86. nautobot/core/management/commands/validate_models.py +0 -1
  87. nautobot/core/middleware.py +0 -1
  88. nautobot/core/models/__init__.py +26 -1
  89. nautobot/core/models/fields.py +24 -5
  90. nautobot/core/models/generics.py +2 -46
  91. nautobot/core/models/managers.py +5 -0
  92. nautobot/core/models/name_color_content_types.py +1 -19
  93. nautobot/core/models/tree_queries.py +14 -4
  94. nautobot/core/models/utils.py +9 -10
  95. nautobot/core/models/validators.py +17 -8
  96. nautobot/core/releases.py +8 -10
  97. nautobot/core/settings.py +81 -53
  98. nautobot/core/tables.py +5 -5
  99. nautobot/core/tasks.py +4 -7
  100. nautobot/core/templates/base.html +1 -49
  101. nautobot/core/templates/base_django.html +49 -0
  102. nautobot/core/templates/base_react.html +55 -0
  103. nautobot/core/templates/buttons/export.html +6 -4
  104. nautobot/core/templates/generic/object_bulk_create.html +10 -21
  105. nautobot/core/templates/generic/object_list.html +4 -1
  106. nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
  107. nautobot/core/templates/inc/footer.html +1 -0
  108. nautobot/core/templates/inc/javascript.html +0 -14
  109. nautobot/core/templates/inc/nav_menu.html +28 -33
  110. nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
  111. nautobot/core/templates/inc/relationships_table_rows.html +2 -2
  112. nautobot/core/templates/nautobot_config.py.j2 +8 -25
  113. nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
  114. nautobot/core/templates/rest_framework/api.html +8 -0
  115. nautobot/core/templatetags/buttons.py +32 -29
  116. nautobot/core/templatetags/helpers.py +1 -1
  117. nautobot/core/testing/__init__.py +47 -44
  118. nautobot/core/testing/api.py +365 -47
  119. nautobot/core/testing/filters.py +12 -7
  120. nautobot/core/testing/integration.py +1 -1
  121. nautobot/core/testing/migrations.py +2 -0
  122. nautobot/core/testing/mixins.py +22 -12
  123. nautobot/core/testing/schema.py +2 -1
  124. nautobot/core/testing/views.py +28 -51
  125. nautobot/core/tests/integration/test_filters.py +17 -8
  126. nautobot/core/tests/integration/test_navbar.py +11 -34
  127. nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
  128. nautobot/core/tests/nautobot_config.py +2 -3
  129. nautobot/core/tests/runner.py +0 -1
  130. nautobot/core/tests/test_api.py +290 -24
  131. nautobot/core/tests/test_authentication.py +57 -14
  132. nautobot/core/tests/test_checks.py +0 -7
  133. nautobot/core/tests/test_choices.py +0 -1
  134. nautobot/core/tests/test_filters.py +117 -110
  135. nautobot/core/tests/test_forms.py +47 -110
  136. nautobot/core/tests/test_graphql.py +158 -135
  137. nautobot/core/tests/test_logging.py +4 -1
  138. nautobot/core/tests/test_managers.py +3 -5
  139. nautobot/core/tests/test_models.py +2 -0
  140. nautobot/core/tests/test_ordering.py +0 -2
  141. nautobot/core/tests/test_paginator.py +3 -1
  142. nautobot/core/tests/test_releases.py +12 -12
  143. nautobot/core/tests/test_templatetags_helpers.py +7 -4
  144. nautobot/core/tests/test_utils.py +112 -78
  145. nautobot/core/tests/test_views.py +12 -17
  146. nautobot/core/tests/test_views_utils.py +6 -9
  147. nautobot/core/utils/data.py +17 -0
  148. nautobot/core/utils/deprecation.py +13 -20
  149. nautobot/core/utils/filtering.py +53 -9
  150. nautobot/core/utils/git.py +12 -4
  151. nautobot/core/utils/lookup.py +3 -1
  152. nautobot/core/utils/requests.py +23 -116
  153. nautobot/core/views/__init__.py +1 -2
  154. nautobot/core/views/generic.py +131 -119
  155. nautobot/core/views/mixins.py +53 -62
  156. nautobot/core/views/paginator.py +0 -1
  157. nautobot/core/views/renderers.py +14 -12
  158. nautobot/core/views/utils.py +87 -4
  159. nautobot/dcim/api/serializers.py +160 -672
  160. nautobot/dcim/api/urls.py +1 -1
  161. nautobot/dcim/api/views.py +7 -46
  162. nautobot/dcim/choices.py +2 -25
  163. nautobot/dcim/elevations.py +0 -1
  164. nautobot/dcim/factory.py +15 -4
  165. nautobot/dcim/filters/__init__.py +42 -13
  166. nautobot/dcim/form_mixins.py +1 -27
  167. nautobot/dcim/forms.py +58 -797
  168. nautobot/dcim/management/commands/trace_paths.py +0 -1
  169. nautobot/dcim/migrations/0001_initial_part_1.py +0 -1
  170. nautobot/dcim/migrations/0002_initial_part_2.py +0 -1
  171. nautobot/dcim/migrations/0003_initial_part_3.py +0 -1
  172. nautobot/dcim/migrations/0004_initial_part_4.py +0 -1
  173. nautobot/dcim/migrations/0005_device_local_context_schema.py +0 -1
  174. nautobot/dcim/migrations/0006_auto_slug.py +0 -1
  175. nautobot/dcim/migrations/0007_device_secrets_group.py +0 -1
  176. nautobot/dcim/migrations/0008_increase_all_serial_lengths.py +0 -1
  177. nautobot/dcim/migrations/0009_add_natural_indexing.py +0 -1
  178. nautobot/dcim/migrations/0010_interface_status.py +0 -1
  179. nautobot/dcim/migrations/0011_interface_status_data_migration.py +0 -1
  180. nautobot/dcim/migrations/0012_interface_parent_bridge.py +0 -1
  181. nautobot/dcim/migrations/0013_location_location_type.py +0 -1
  182. nautobot/dcim/migrations/0014_location_status_data_migration.py +0 -1
  183. nautobot/dcim/migrations/0015_device_components__changeloggedmodel.py +0 -1
  184. nautobot/dcim/migrations/0016_device_components__timestamp_data_migration.py +0 -1
  185. nautobot/dcim/migrations/0017_locationtype_nestable.py +0 -1
  186. nautobot/dcim/migrations/0018_device_redundancy_group.py +0 -1
  187. nautobot/dcim/migrations/0019_device_redundancy_group_data_migration.py +0 -1
  188. nautobot/dcim/migrations/0020_move_site_fields_to_location_model.py +0 -1
  189. nautobot/dcim/migrations/0021_mptt_to_tree_queries.py +0 -1
  190. nautobot/dcim/migrations/0022_interface_mac_address_data_migration.py +0 -1
  191. nautobot/dcim/migrations/0023_alter_interface_mac_address.py +0 -1
  192. nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -2
  193. nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -14
  194. nautobot/dcim/migrations/0026_rename_device_and_rack_role.py +0 -1
  195. nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -2
  196. nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -2
  197. nautobot/dcim/migrations/0029_add_tree_managers_and_foreign_keys_pre_data_migration.py +0 -1
  198. nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
  199. nautobot/dcim/migrations/0031_rename_path_end_point_related_name.py +0 -1
  200. nautobot/dcim/migrations/0032_remove_site_foreign_key_from_dcim_models.py +0 -1
  201. nautobot/dcim/migrations/0033_created_datetime.py +0 -1
  202. nautobot/dcim/migrations/0034_fixup_fks_and_related_names.py +0 -1
  203. nautobot/dcim/migrations/0035_related_name_changes.py +1 -2
  204. nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -2
  205. nautobot/dcim/migrations/0037_interface_ip_addresses_m2m.py +0 -1
  206. nautobot/dcim/migrations/0038_alter_location_managers.py +0 -1
  207. nautobot/dcim/migrations/0039_remove_slug.py +24 -0
  208. nautobot/dcim/migrations/0040_tagsfield.py +109 -0
  209. nautobot/dcim/migrations/0041_ipam__namespaces.py +25 -0
  210. nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
  211. nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
  212. nautobot/dcim/models/cables.py +4 -35
  213. nautobot/dcim/models/device_component_templates.py +7 -2
  214. nautobot/dcim/models/device_components.py +26 -203
  215. nautobot/dcim/models/devices.py +30 -152
  216. nautobot/dcim/models/locations.py +3 -64
  217. nautobot/dcim/models/power.py +3 -51
  218. nautobot/dcim/models/racks.py +7 -86
  219. nautobot/dcim/navigation.py +141 -467
  220. nautobot/dcim/signals.py +0 -2
  221. nautobot/dcim/tables/devices.py +8 -5
  222. nautobot/dcim/tables/devicetypes.py +1 -1
  223. nautobot/dcim/tables/locations.py +2 -2
  224. nautobot/dcim/tables/power.py +2 -2
  225. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
  226. nautobot/dcim/templates/dcim/device.html +15 -4
  227. nautobot/dcim/templates/dcim/device_edit.html +6 -0
  228. nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +0 -1
  229. nautobot/dcim/templates/dcim/devicetype.html +2 -2
  230. nautobot/dcim/templates/dcim/interface.html +4 -0
  231. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
  232. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  233. nautobot/dcim/templates/dcim/location.html +16 -1
  234. nautobot/dcim/templates/dcim/locationtype.html +15 -0
  235. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
  236. nautobot/dcim/templates/dcim/rackgroup.html +0 -12
  237. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  238. nautobot/dcim/tests/test_api.py +202 -130
  239. nautobot/dcim/tests/test_cablepaths.py +47 -42
  240. nautobot/dcim/tests/test_filters.py +156 -134
  241. nautobot/dcim/tests/test_forms.py +12 -213
  242. nautobot/dcim/tests/test_graphql.py +8 -3
  243. nautobot/dcim/tests/test_migrations.py +6 -11
  244. nautobot/dcim/tests/test_models.py +208 -158
  245. nautobot/dcim/tests/test_natural_ordering.py +12 -14
  246. nautobot/dcim/tests/test_signals.py +7 -4
  247. nautobot/dcim/tests/test_views.py +270 -264
  248. nautobot/dcim/urls.py +21 -26
  249. nautobot/dcim/views.py +14 -156
  250. nautobot/docs/additional-features/caching.md +6 -87
  251. nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
  252. nautobot/docs/additional-features/jobs.md +179 -197
  253. nautobot/docs/administration/nautobot-server.md +9 -24
  254. nautobot/docs/administration/nautobot-shell.md +6 -6
  255. nautobot/docs/administration/replicating-nautobot.md +0 -10
  256. nautobot/docs/configuration/index.md +9 -9
  257. nautobot/docs/configuration/optional-settings.md +32 -61
  258. nautobot/docs/configuration/required-settings.md +11 -52
  259. nautobot/docs/development/application-registry.md +2 -13
  260. nautobot/docs/development/best-practices.md +2 -1
  261. nautobot/docs/development/docker-compose-advanced-use-cases.md +1 -1
  262. nautobot/docs/development/extending-models.md +15 -17
  263. nautobot/docs/development/generic-views.md +0 -2
  264. nautobot/docs/development/getting-started.md +56 -6
  265. nautobot/docs/development/navigation-menu.md +22 -93
  266. nautobot/docs/development/react-ui.md +105 -0
  267. nautobot/docs/development/release-checklist.md +3 -3
  268. nautobot/docs/development/role-internals.md +1 -3
  269. nautobot/docs/development/style-guide.md +6 -4
  270. nautobot/docs/development/templates.md +2 -1
  271. nautobot/docs/docker/index.md +16 -14
  272. nautobot/docs/index.md +7 -3
  273. nautobot/docs/installation/index.md +4 -1
  274. nautobot/docs/installation/migrating-from-netbox.md +12 -43
  275. nautobot/docs/installation/migrating-from-postgresql.md +1 -1
  276. nautobot/docs/installation/nautobot.md +1 -1
  277. nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  278. nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  279. nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  280. nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  281. nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
  282. nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  283. nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  284. nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  285. nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  286. nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  287. nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  288. nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  289. nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  290. nautobot/docs/installation/upgrading-from-nautobot-v1.md +190 -636
  291. nautobot/docs/installation/upgrading.md +5 -2
  292. nautobot/docs/models/dcim/device.md +3 -0
  293. nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
  294. nautobot/docs/models/extras/computedfield.md +4 -4
  295. nautobot/docs/models/extras/dynamicgroup.md +9 -9
  296. nautobot/docs/models/extras/gitrepository.md +3 -0
  297. nautobot/docs/models/extras/job.md +1 -0
  298. nautobot/docs/models/extras/jobbutton.md +18 -13
  299. nautobot/docs/models/extras/jobhook.md +7 -4
  300. nautobot/docs/models/extras/jobresult.md +6 -2
  301. nautobot/docs/models/extras/relationship.md +2 -2
  302. nautobot/docs/models/extras/status.md +6 -19
  303. nautobot/docs/models/ipam/ipaddress.md +3 -0
  304. nautobot/docs/models/ipam/vrf.md +0 -3
  305. nautobot/docs/models/virtualization/virtualmachine.md +3 -0
  306. nautobot/docs/plugins/development.md +92 -24
  307. nautobot/docs/release-notes/version-1.5.md +96 -0
  308. nautobot/docs/release-notes/version-2.0.md +216 -0
  309. nautobot/docs/requirements.txt +5 -4
  310. nautobot/docs/rest-api/overview.md +384 -215
  311. nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
  312. nautobot/extras/admin.py +3 -5
  313. nautobot/extras/api/customfields.py +15 -39
  314. nautobot/extras/api/fields.py +0 -11
  315. nautobot/extras/api/mixins.py +45 -0
  316. nautobot/extras/api/relationships.py +63 -159
  317. nautobot/extras/api/serializers.py +165 -706
  318. nautobot/extras/api/urls.py +1 -1
  319. nautobot/extras/api/views.py +295 -282
  320. nautobot/extras/apps.py +4 -7
  321. nautobot/extras/choices.py +11 -22
  322. nautobot/extras/constants.py +9 -3
  323. nautobot/extras/datasources/__init__.py +2 -0
  324. nautobot/extras/datasources/git.py +135 -186
  325. nautobot/extras/datasources/registry.py +25 -35
  326. nautobot/extras/factory.py +1 -3
  327. nautobot/extras/filters/__init__.py +49 -47
  328. nautobot/extras/filters/mixins.py +10 -8
  329. nautobot/extras/forms/forms.py +72 -148
  330. nautobot/extras/forms/mixins.py +34 -57
  331. nautobot/extras/health_checks.py +0 -33
  332. nautobot/extras/jobs.py +387 -566
  333. nautobot/extras/management/__init__.py +55 -48
  334. nautobot/extras/management/commands/renaturalize.py +0 -1
  335. nautobot/extras/management/commands/runjob.py +24 -62
  336. nautobot/extras/management/commands/webhook_receiver.py +0 -1
  337. nautobot/extras/managers.py +30 -7
  338. nautobot/extras/migrations/0001_initial_part_1.py +0 -1
  339. nautobot/extras/migrations/0002_initial_part_2.py +0 -1
  340. nautobot/extras/migrations/0003_initial_part_3.py +0 -1
  341. nautobot/extras/migrations/0004_populate_default_status_records.py +0 -1
  342. nautobot/extras/migrations/0005_configcontext_device_types.py +0 -1
  343. nautobot/extras/migrations/0006_graphqlquery.py +0 -1
  344. nautobot/extras/migrations/0007_configcontextschema.py +0 -1
  345. nautobot/extras/migrations/0008_jobresult__custom_field_data.py +0 -1
  346. nautobot/extras/migrations/0009_computedfield.py +0 -1
  347. nautobot/extras/migrations/0010_change_cf_validation_max_min_field_to_bigint.py +0 -1
  348. nautobot/extras/migrations/0011_fileattachment_fileproxy.py +0 -1
  349. nautobot/extras/migrations/0012_healthchecktestmodel.py +0 -1
  350. nautobot/extras/migrations/0013_default_fallback_value_computedfield.py +0 -1
  351. nautobot/extras/migrations/0014_auto_slug.py +0 -1
  352. nautobot/extras/migrations/0015_scheduled_job.py +0 -1
  353. nautobot/extras/migrations/0016_secret.py +0 -1
  354. nautobot/extras/migrations/0017_joblogentry.py +0 -1
  355. nautobot/extras/migrations/0018_joblog_data_migration.py +0 -2
  356. nautobot/extras/migrations/0019_joblogentry__meta_options__related_name.py +0 -1
  357. nautobot/extras/migrations/0020_customfield_changelog.py +0 -1
  358. nautobot/extras/migrations/0021_customfield_changelog_data.py +0 -1
  359. nautobot/extras/migrations/0022_objectchange_object_datav2.py +0 -1
  360. nautobot/extras/migrations/0023_job_model.py +0 -1
  361. nautobot/extras/migrations/0024_job_data_migration.py +0 -1
  362. nautobot/extras/migrations/0025_add_advanced_ui_boolean_to_customfield_conputedfield_and_relationship.py +0 -1
  363. nautobot/extras/migrations/0026_job_add_gitrepository_fk.py +0 -1
  364. nautobot/extras/migrations/0027_job_gitrepository_data_migration.py +0 -1
  365. nautobot/extras/migrations/0028_job_reduce_source.py +0 -1
  366. nautobot/extras/migrations/0029_dynamicgroup.py +0 -1
  367. nautobot/extras/migrations/0030_webhook_alter_unique_together.py +0 -1
  368. nautobot/extras/migrations/0031_tag_content_types.py +0 -1
  369. nautobot/extras/migrations/0032_tag_content_types_data_migration.py +0 -1
  370. nautobot/extras/migrations/0033_add__optimized_indexing.py +0 -1
  371. nautobot/extras/migrations/0034_alter_fileattachment_mimetype.py +0 -1
  372. nautobot/extras/migrations/0035_scheduledjob_crontab.py +0 -1
  373. nautobot/extras/migrations/0036_job_add_has_sensitive_variables.py +0 -1
  374. nautobot/extras/migrations/0037_configcontextschema__remove_name_unique__create_constraint_unique_name_owner.py +0 -1
  375. nautobot/extras/migrations/0038_configcontext_locations.py +0 -1
  376. nautobot/extras/migrations/0039_objectchange__add_change_context.py +0 -1
  377. nautobot/extras/migrations/0040_dynamicgroup__dynamicgroupmembership.py +0 -1
  378. nautobot/extras/migrations/0041_jobresult_job_kwargs.py +0 -1
  379. nautobot/extras/migrations/0042_job__add_is_job_hook_receiver.py +0 -1
  380. nautobot/extras/migrations/0043_note.py +0 -1
  381. nautobot/extras/migrations/0044_add_job_hook.py +0 -1
  382. nautobot/extras/migrations/0045_add_custom_field_slug.py +0 -1
  383. nautobot/extras/migrations/0046_populate_custom_field_slug_label.py +0 -1
  384. nautobot/extras/migrations/0047_enforce_custom_field_slug.py +0 -1
  385. nautobot/extras/migrations/0048_alter_objectchange_change_context_detail.py +0 -1
  386. nautobot/extras/migrations/0049_alter_tag_slug.py +0 -1
  387. nautobot/extras/migrations/0050_customfield_grouping.py +0 -1
  388. nautobot/extras/migrations/0051_add_job_task_queues.py +0 -1
  389. nautobot/extras/migrations/0052_configcontext_device_redundancy_groups.py +0 -1
  390. nautobot/extras/migrations/0053_relationship_required_on.py +0 -1
  391. nautobot/extras/migrations/0054_scheduledjob_kwargs_request_user_change.py +0 -1
  392. nautobot/extras/migrations/0055_configcontext_dynamic_groups.py +0 -1
  393. nautobot/extras/migrations/0056_objectchange_add_reverse_time_idx.py +0 -1
  394. nautobot/extras/migrations/0057_jobbutton.py +0 -1
  395. nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
  396. nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -2
  397. nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -2
  398. nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -8
  399. nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -33
  400. nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -2
  401. nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -2
  402. nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
  403. nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -2
  404. nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -3
  405. nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -2
  406. nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -2
  407. nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
  408. nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -2
  409. nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
  410. nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -10
  411. nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -2
  412. nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
  413. nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
  414. nautobot/extras/migrations/{0076_remove_name_field_and_make_label_field_non_nullable.py → 0077_remove_name_field_and_make_label_field_non_nullable.py} +1 -1
  415. nautobot/extras/migrations/0078_remove_slug.py +45 -0
  416. nautobot/extras/migrations/0079_tagsfield.py +28 -0
  417. nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
  418. nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
  419. nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
  420. nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
  421. nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
  422. nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
  423. nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
  424. nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
  425. nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
  426. nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
  427. nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
  428. nautobot/extras/models/__init__.py +2 -3
  429. nautobot/extras/models/change_logging.py +0 -36
  430. nautobot/extras/models/customfields.py +39 -33
  431. nautobot/extras/models/datasources.py +48 -50
  432. nautobot/extras/models/groups.py +5 -12
  433. nautobot/extras/models/jobs.py +190 -323
  434. nautobot/extras/models/mixins.py +0 -71
  435. nautobot/extras/models/models.py +1 -22
  436. nautobot/extras/models/relationships.py +20 -21
  437. nautobot/extras/models/roles.py +0 -23
  438. nautobot/extras/models/secrets.py +2 -31
  439. nautobot/extras/models/statuses.py +6 -5
  440. nautobot/extras/models/tags.py +2 -17
  441. nautobot/extras/navigation.py +89 -307
  442. nautobot/extras/plugins/__init__.py +3 -121
  443. nautobot/extras/plugins/utils.py +0 -3
  444. nautobot/extras/plugins/validators.py +5 -4
  445. nautobot/extras/plugins/views.py +16 -4
  446. nautobot/extras/querysets.py +1 -7
  447. nautobot/extras/registry.py +3 -0
  448. nautobot/extras/signals.py +26 -60
  449. nautobot/extras/tables.py +42 -49
  450. nautobot/extras/tasks.py +0 -12
  451. nautobot/extras/templates/extras/configcontext.html +1 -1
  452. nautobot/extras/templates/extras/configcontextschema.html +16 -1
  453. nautobot/extras/templates/extras/customfield.html +0 -13
  454. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -1
  455. nautobot/extras/templates/extras/gitrepository.html +3 -3
  456. nautobot/extras/templates/extras/inc/jobresult.html +10 -0
  457. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  458. nautobot/extras/templates/extras/job.html +35 -25
  459. nautobot/extras/templates/extras/job_approval_request.html +15 -30
  460. nautobot/extras/templates/extras/job_detail.html +13 -31
  461. nautobot/extras/templates/extras/job_edit.html +14 -17
  462. nautobot/extras/templates/extras/jobresult.html +24 -6
  463. nautobot/extras/templates/extras/objectchange_list.html +1 -1
  464. nautobot/extras/templates/extras/scheduledjob.html +2 -2
  465. nautobot/extras/templates/extras/secret.html +28 -0
  466. nautobot/extras/templates/extras/secret_edit.html +0 -1
  467. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -1
  468. nautobot/extras/templatetags/custom_links.py +0 -2
  469. nautobot/extras/templatetags/job_buttons.py +1 -0
  470. nautobot/extras/templatetags/plugins.py +0 -1
  471. nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
  472. nautobot/extras/test_jobs/atomic_transaction.py +53 -0
  473. nautobot/extras/test_jobs/dry_run.py +29 -0
  474. nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
  475. nautobot/extras/test_jobs/duplicate_name2.py +9 -0
  476. nautobot/extras/test_jobs/fail.py +23 -0
  477. nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
  478. nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
  479. nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
  480. nautobot/extras/test_jobs/file_upload_pass.py +25 -0
  481. nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
  482. nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
  483. nautobot/extras/test_jobs/job_button_receiver.py +28 -0
  484. nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
  485. nautobot/extras/test_jobs/job_variables.py +88 -0
  486. nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
  487. nautobot/extras/test_jobs/log_redaction.py +20 -0
  488. nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
  489. nautobot/extras/test_jobs/modify_db.py +25 -0
  490. nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
  491. nautobot/extras/test_jobs/object_var_optional.py +21 -0
  492. nautobot/extras/test_jobs/object_var_required.py +21 -0
  493. nautobot/extras/test_jobs/object_vars.py +26 -0
  494. nautobot/extras/test_jobs/pass.py +25 -0
  495. nautobot/extras/test_jobs/profiling.py +32 -0
  496. nautobot/extras/test_jobs/read_only_job.py +15 -0
  497. nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
  498. nautobot/extras/{tests/example_jobs/test_soft_time_limit_greater_than_time_limit.py → test_jobs/soft_time_limit_greater_than_time_limit.py} +5 -1
  499. nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
  500. nautobot/extras/tests/integration/__init__.py +3 -3
  501. nautobot/extras/tests/integration/test_computedfields.py +1 -1
  502. nautobot/extras/tests/integration/test_configcontextschema.py +7 -5
  503. nautobot/extras/tests/integration/test_customfields.py +4 -2
  504. nautobot/extras/tests/integration/test_dynamicgroups.py +2 -2
  505. nautobot/extras/tests/integration/test_jobs.py +25 -27
  506. nautobot/extras/tests/integration/test_notes.py +8 -4
  507. nautobot/extras/tests/integration/test_plugins.py +4 -4
  508. nautobot/extras/tests/integration/test_relationships.py +2 -2
  509. nautobot/extras/tests/test_api.py +371 -381
  510. nautobot/extras/tests/test_changelog.py +17 -16
  511. nautobot/extras/tests/test_context_managers.py +5 -6
  512. nautobot/extras/tests/test_customfields.py +112 -73
  513. nautobot/extras/tests/test_datasources.py +191 -117
  514. nautobot/extras/tests/test_dynamicgroups.py +45 -68
  515. nautobot/extras/tests/test_filters.py +170 -130
  516. nautobot/extras/tests/test_forms.py +107 -109
  517. nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
  518. nautobot/extras/tests/test_jobs.py +271 -273
  519. nautobot/extras/tests/test_management.py +3 -6
  520. nautobot/extras/tests/test_migrations.py +5 -3
  521. nautobot/extras/tests/test_models.py +121 -173
  522. nautobot/extras/tests/test_notes.py +0 -1
  523. nautobot/extras/tests/test_plugins.py +55 -89
  524. nautobot/extras/tests/test_relationships.py +174 -130
  525. nautobot/extras/tests/test_tags.py +6 -12
  526. nautobot/extras/tests/test_utils.py +31 -1
  527. nautobot/extras/tests/test_views.py +223 -184
  528. nautobot/extras/tests/test_webhooks.py +16 -15
  529. nautobot/extras/urls.py +69 -69
  530. nautobot/extras/utils.py +137 -163
  531. nautobot/extras/views.py +81 -153
  532. nautobot/ipam/api/fields.py +17 -0
  533. nautobot/ipam/api/serializers.py +77 -164
  534. nautobot/ipam/api/urls.py +4 -1
  535. nautobot/ipam/api/views.py +28 -19
  536. nautobot/ipam/apps.py +1 -0
  537. nautobot/ipam/choices.py +5 -12
  538. nautobot/ipam/constants.py +1 -0
  539. nautobot/ipam/factory.py +41 -30
  540. nautobot/ipam/filters.py +58 -25
  541. nautobot/ipam/forms.py +82 -211
  542. nautobot/ipam/graphql/types.py +0 -9
  543. nautobot/ipam/lookups.py +13 -8
  544. nautobot/ipam/management/commands/__init__.py +0 -0
  545. nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
  546. nautobot/ipam/migrations/0001_initial_part_1.py +0 -1
  547. nautobot/ipam/migrations/0002_initial_part_2.py +0 -1
  548. nautobot/ipam/migrations/0003_remove_max_length.py +0 -1
  549. nautobot/ipam/migrations/0004_fixup_p2p_broadcast.py +0 -1
  550. nautobot/ipam/migrations/0005_auto_slug.py +0 -1
  551. nautobot/ipam/migrations/0006_ipaddress_nat_outside_list.py +0 -1
  552. nautobot/ipam/migrations/0007_add_natural_indexing.py +0 -1
  553. nautobot/ipam/migrations/0008_prefix_vlan_vlangroup_location.py +0 -1
  554. nautobot/ipam/migrations/0009_alter_vlan_name.py +0 -1
  555. nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -2
  556. nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -39
  557. nautobot/ipam/migrations/0012_rename_ipam_roles.py +0 -1
  558. nautobot/ipam/migrations/0013_delete_role.py +0 -1
  559. nautobot/ipam/migrations/0014_rename_foreign_keys_and_related_names.py +0 -1
  560. nautobot/ipam/migrations/0015_prefix_add_type.py +0 -1
  561. nautobot/ipam/migrations/0016_prefix_type_data_migration.py +0 -3
  562. nautobot/ipam/migrations/0017_prefix_remove_is_pool.py +0 -1
  563. nautobot/ipam/migrations/0018_remove_site_foreign_key_from_ipam_models.py +0 -1
  564. nautobot/ipam/migrations/0019_created_datetime.py +0 -1
  565. nautobot/ipam/migrations/0020_related_name_changes.py +1 -2
  566. nautobot/ipam/migrations/0021_prefix_add_rir_and_date_allocated.py +0 -1
  567. nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +3 -5
  568. nautobot/ipam/migrations/0023_delete_aggregate.py +0 -1
  569. nautobot/ipam/migrations/0024_interface_to_ipaddress_m2m.py +0 -1
  570. nautobot/ipam/migrations/0025_interface_ipaddress_m2m_data_migration.py +0 -1
  571. nautobot/ipam/migrations/0026_ipaddress_remove_assigned_object.py +0 -1
  572. nautobot/ipam/migrations/0027_remove_rir_slug.py +16 -0
  573. nautobot/ipam/migrations/0028_tagsfield.py +44 -0
  574. nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
  575. nautobot/ipam/migrations/0030_ipam__namespaces.py +231 -0
  576. nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
  577. nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
  578. nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
  579. nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
  580. nautobot/ipam/models.py +579 -368
  581. nautobot/ipam/navigation.py +36 -159
  582. nautobot/ipam/querysets.py +117 -90
  583. nautobot/ipam/signals.py +89 -0
  584. nautobot/ipam/tables.py +86 -28
  585. nautobot/ipam/templates/ipam/ipaddress.html +14 -30
  586. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -0
  587. nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
  588. nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
  589. nautobot/ipam/templates/ipam/namespace_retrieve.html +42 -0
  590. nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
  591. nautobot/ipam/templates/ipam/prefix.html +27 -33
  592. nautobot/ipam/templates/ipam/prefix_edit.html +7 -1
  593. nautobot/ipam/templates/ipam/vlangroup.html +0 -13
  594. nautobot/ipam/templates/ipam/vrf.html +6 -4
  595. nautobot/ipam/templates/ipam/vrf_edit.html +20 -2
  596. nautobot/ipam/tests/integration/test_prefixes.py +4 -27
  597. nautobot/ipam/tests/test_api.py +60 -61
  598. nautobot/ipam/tests/test_filters.py +187 -126
  599. nautobot/ipam/tests/test_forms.py +12 -6
  600. nautobot/ipam/tests/test_graphql.py +8 -6
  601. nautobot/ipam/tests/test_migrations.py +8 -13
  602. nautobot/ipam/tests/test_models.py +426 -274
  603. nautobot/ipam/tests/test_ordering.py +6 -3
  604. nautobot/ipam/tests/test_querysets.py +340 -96
  605. nautobot/ipam/tests/test_views.py +100 -55
  606. nautobot/ipam/urls.py +28 -5
  607. nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
  608. nautobot/ipam/utils/migrations.py +713 -0
  609. nautobot/ipam/views.py +237 -122
  610. nautobot/project-static/docs/404.html +1399 -166
  611. nautobot/project-static/docs/additional-features/caching.html +1416 -320
  612. nautobot/project-static/docs/additional-features/change-logging.html +1389 -190
  613. nautobot/project-static/docs/additional-features/config-contexts.html +1389 -190
  614. nautobot/project-static/docs/additional-features/graphql.html +1389 -190
  615. nautobot/project-static/docs/additional-features/healthcheck.html +1389 -190
  616. nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1393 -190
  617. nautobot/project-static/docs/additional-features/jobs.html +1677 -460
  618. nautobot/project-static/docs/additional-features/napalm.html +1389 -190
  619. nautobot/project-static/docs/additional-features/prometheus-metrics.html +1389 -190
  620. nautobot/project-static/docs/additional-features/template-filters.html +1389 -190
  621. nautobot/project-static/docs/administration/celery-queues.html +1389 -190
  622. nautobot/project-static/docs/administration/nautobot-server.html +1553 -375
  623. nautobot/project-static/docs/administration/nautobot-shell.html +1395 -196
  624. nautobot/project-static/docs/administration/permissions.html +1389 -190
  625. nautobot/project-static/docs/administration/replicating-nautobot.html +1387 -207
  626. nautobot/project-static/docs/apps/index.html +1389 -190
  627. nautobot/project-static/docs/apps/nautobot-apps.html +1387 -175
  628. nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js +29 -0
  629. nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js.map +8 -0
  630. nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js → search.208ed371.min.js} +9 -15
  631. nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js.map → search.208ed371.min.js.map} +4 -4
  632. nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css +1 -0
  633. nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css.map +1 -0
  634. nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css +1 -0
  635. nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css.map +1 -0
  636. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1775 -590
  637. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1389 -190
  638. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3588 -1922
  639. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1461 -262
  640. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1401 -170
  641. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1396 -191
  642. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2095 -894
  643. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2357 -1194
  644. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2258 -940
  645. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1389 -190
  646. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1400 -201
  647. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +11068 -7861
  648. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2867 -2224
  649. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1389 -190
  650. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2641 -1573
  651. nautobot/project-static/docs/configuration/authentication/ldap.html +1389 -190
  652. nautobot/project-static/docs/configuration/authentication/remote.html +1389 -190
  653. nautobot/project-static/docs/configuration/authentication/sso.html +1389 -190
  654. nautobot/project-static/docs/configuration/index.html +1398 -199
  655. nautobot/project-static/docs/configuration/optional-settings.html +1418 -274
  656. nautobot/project-static/docs/configuration/required-settings.html +1419 -287
  657. nautobot/project-static/docs/core-functionality/circuits.html +1446 -247
  658. nautobot/project-static/docs/core-functionality/device-types.html +1448 -249
  659. nautobot/project-static/docs/core-functionality/devices.html +1452 -249
  660. nautobot/project-static/docs/core-functionality/ipam.html +1452 -253
  661. nautobot/project-static/docs/core-functionality/power.html +1448 -249
  662. nautobot/project-static/docs/core-functionality/secrets.html +1448 -249
  663. nautobot/project-static/docs/core-functionality/services.html +1448 -249
  664. nautobot/project-static/docs/core-functionality/sites-and-racks.html +1448 -249
  665. nautobot/project-static/docs/core-functionality/tenancy.html +1448 -249
  666. nautobot/project-static/docs/core-functionality/virtualization.html +1452 -249
  667. nautobot/project-static/docs/core-functionality/vlans.html +1448 -249
  668. nautobot/project-static/docs/development/application-registry.html +1393 -214
  669. nautobot/project-static/docs/development/best-practices.html +1392 -192
  670. nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1390 -191
  671. nautobot/project-static/docs/development/extending-models.html +1443 -257
  672. nautobot/project-static/docs/development/generic-views.html +1403 -174
  673. nautobot/project-static/docs/development/getting-started.html +1568 -262
  674. nautobot/project-static/docs/development/homepage.html +1389 -190
  675. nautobot/project-static/docs/development/index.html +1389 -190
  676. nautobot/project-static/docs/development/model-features.html +1389 -190
  677. nautobot/project-static/docs/development/natural-keys.html +1389 -190
  678. nautobot/project-static/docs/development/navigation-menu.html +1451 -330
  679. nautobot/project-static/docs/development/react-ui.html +4199 -0
  680. nautobot/project-static/docs/development/release-checklist.html +1392 -193
  681. nautobot/project-static/docs/development/role-internals.html +1402 -172
  682. nautobot/project-static/docs/development/style-guide.html +1399 -199
  683. nautobot/project-static/docs/development/templates.html +1391 -191
  684. nautobot/project-static/docs/development/testing.html +1389 -190
  685. nautobot/project-static/docs/development/user-preferences.html +1389 -190
  686. nautobot/project-static/docs/docker/index.html +1408 -206
  687. nautobot/project-static/docs/index.html +1397 -180
  688. nautobot/project-static/docs/installation/centos.html +1401 -170
  689. nautobot/project-static/docs/installation/external-authentication.html +1389 -190
  690. nautobot/project-static/docs/installation/http-server.html +1389 -190
  691. nautobot/project-static/docs/installation/index.html +1394 -191
  692. nautobot/project-static/docs/installation/migrating-from-netbox.html +1452 -305
  693. nautobot/project-static/docs/installation/migrating-from-postgresql.html +1390 -191
  694. nautobot/project-static/docs/installation/nautobot.html +1390 -191
  695. nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1389 -190
  696. nautobot/project-static/docs/installation/selinux-troubleshooting.html +1401 -170
  697. nautobot/project-static/docs/installation/services.html +1389 -190
  698. nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  699. nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  700. nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  701. nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  702. nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
  703. nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  704. nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  705. nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  706. nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  707. nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  708. nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  709. nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  710. nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  711. nautobot/project-static/docs/installation/ubuntu.html +1401 -170
  712. nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +4254 -1923
  713. nautobot/project-static/docs/installation/upgrading.html +1395 -192
  714. nautobot/project-static/docs/models/circuits/circuit.html +1427 -174
  715. nautobot/project-static/docs/models/circuits/circuittermination.html +1427 -174
  716. nautobot/project-static/docs/models/circuits/circuittype.html +1427 -174
  717. nautobot/project-static/docs/models/circuits/provider.html +1427 -174
  718. nautobot/project-static/docs/models/circuits/providernetwork.html +1427 -174
  719. nautobot/project-static/docs/models/dcim/cable.html +1458 -174
  720. nautobot/project-static/docs/models/dcim/consoleport.html +1427 -174
  721. nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1427 -174
  722. nautobot/project-static/docs/models/dcim/consoleserverport.html +1427 -174
  723. nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1427 -174
  724. nautobot/project-static/docs/models/dcim/device.html +1431 -174
  725. nautobot/project-static/docs/models/dcim/devicebay.html +1427 -174
  726. nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1427 -174
  727. nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1522 -177
  728. nautobot/project-static/docs/models/dcim/devicetype.html +1427 -174
  729. nautobot/project-static/docs/models/dcim/frontport.html +1427 -174
  730. nautobot/project-static/docs/models/dcim/frontporttemplate.html +1427 -174
  731. nautobot/project-static/docs/models/dcim/interface.html +1427 -174
  732. nautobot/project-static/docs/models/dcim/interfacetemplate.html +1427 -174
  733. nautobot/project-static/docs/models/dcim/inventoryitem.html +1427 -174
  734. nautobot/project-static/docs/models/dcim/location.html +1427 -174
  735. nautobot/project-static/docs/models/dcim/locationtype.html +1427 -174
  736. nautobot/project-static/docs/models/dcim/manufacturer.html +1427 -174
  737. nautobot/project-static/docs/models/dcim/platform.html +1427 -174
  738. nautobot/project-static/docs/models/dcim/powerfeed.html +1425 -172
  739. nautobot/project-static/docs/models/dcim/poweroutlet.html +1427 -174
  740. nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1427 -174
  741. nautobot/project-static/docs/models/dcim/powerpanel.html +1425 -172
  742. nautobot/project-static/docs/models/dcim/powerport.html +1427 -174
  743. nautobot/project-static/docs/models/dcim/powerporttemplate.html +1427 -174
  744. nautobot/project-static/docs/models/dcim/rack.html +1427 -174
  745. nautobot/project-static/docs/models/dcim/rackgroup.html +1427 -174
  746. nautobot/project-static/docs/models/dcim/rackreservation.html +1427 -174
  747. nautobot/project-static/docs/models/dcim/rearport.html +1427 -174
  748. nautobot/project-static/docs/models/dcim/rearporttemplate.html +1427 -174
  749. nautobot/project-static/docs/models/dcim/region.html +1401 -170
  750. nautobot/project-static/docs/models/dcim/site.html +1401 -170
  751. nautobot/project-static/docs/models/dcim/virtualchassis.html +1425 -172
  752. nautobot/project-static/docs/models/extras/computedfield.html +1393 -194
  753. nautobot/project-static/docs/models/extras/configcontext.html +1465 -174
  754. nautobot/project-static/docs/models/extras/configcontextschema.html +1421 -168
  755. nautobot/project-static/docs/models/extras/customfield.html +1389 -190
  756. nautobot/project-static/docs/models/extras/customlink.html +1389 -190
  757. nautobot/project-static/docs/models/extras/dynamicgroup.html +1398 -199
  758. nautobot/project-static/docs/models/extras/exporttemplate.html +1389 -190
  759. nautobot/project-static/docs/models/extras/gitrepository.html +1393 -190
  760. nautobot/project-static/docs/models/extras/graphqlquery.html +1469 -171
  761. nautobot/project-static/docs/models/extras/imageattachment.html +1434 -181
  762. nautobot/project-static/docs/models/extras/job.html +1411 -157
  763. nautobot/project-static/docs/models/extras/jobbutton.html +1410 -207
  764. nautobot/project-static/docs/models/extras/jobhook.html +1397 -194
  765. nautobot/project-static/docs/models/extras/joblogentry.html +1408 -155
  766. nautobot/project-static/docs/models/extras/jobresult.html +1417 -159
  767. nautobot/project-static/docs/models/extras/note.html +1389 -190
  768. nautobot/project-static/docs/models/extras/relationship.html +1391 -192
  769. nautobot/project-static/docs/models/extras/role.html +1495 -198
  770. nautobot/project-static/docs/models/extras/secret.html +1492 -201
  771. nautobot/project-static/docs/models/extras/secretsgroup.html +1410 -157
  772. nautobot/project-static/docs/models/extras/status.html +1381 -221
  773. nautobot/project-static/docs/models/extras/tag.html +1389 -190
  774. nautobot/project-static/docs/models/extras/webhook.html +1389 -190
  775. nautobot/project-static/docs/models/ipam/ipaddress.html +1488 -200
  776. nautobot/project-static/docs/models/ipam/prefix.html +1410 -157
  777. nautobot/project-static/docs/models/ipam/rir.html +1410 -157
  778. nautobot/project-static/docs/models/ipam/routetarget.html +1410 -157
  779. nautobot/project-static/docs/models/ipam/service.html +1410 -157
  780. nautobot/project-static/docs/models/ipam/vlan.html +1410 -157
  781. nautobot/project-static/docs/models/ipam/vlangroup.html +1410 -157
  782. nautobot/project-static/docs/models/ipam/vrf.html +1410 -161
  783. nautobot/project-static/docs/models/tenancy/tenant.html +1412 -159
  784. nautobot/project-static/docs/models/tenancy/tenantgroup.html +1412 -159
  785. nautobot/project-static/docs/models/users/objectpermission.html +1462 -171
  786. nautobot/project-static/docs/models/users/token.html +1410 -157
  787. nautobot/project-static/docs/models/virtualization/cluster.html +1410 -157
  788. nautobot/project-static/docs/models/virtualization/clustergroup.html +1410 -157
  789. nautobot/project-static/docs/models/virtualization/clustertype.html +1410 -157
  790. nautobot/project-static/docs/models/virtualization/virtualmachine.html +1414 -157
  791. nautobot/project-static/docs/models/virtualization/vminterface.html +1410 -157
  792. nautobot/project-static/docs/objects.inv +0 -0
  793. nautobot/project-static/docs/plugins/development.html +1916 -646
  794. nautobot/project-static/docs/plugins/index.html +1389 -190
  795. nautobot/project-static/docs/plugins/porting-from-netbox.html +1389 -190
  796. nautobot/project-static/docs/release-notes/index.html +1389 -190
  797. nautobot/project-static/docs/release-notes/version-1.0.html +1389 -190
  798. nautobot/project-static/docs/release-notes/version-1.1.html +1389 -190
  799. nautobot/project-static/docs/release-notes/version-1.2.html +1389 -190
  800. nautobot/project-static/docs/release-notes/version-1.3.html +1389 -190
  801. nautobot/project-static/docs/release-notes/version-1.4.html +1389 -190
  802. nautobot/project-static/docs/release-notes/version-1.5.html +2016 -397
  803. nautobot/project-static/docs/release-notes/version-2.0.html +1935 -287
  804. nautobot/project-static/docs/requirements.txt +5 -4
  805. nautobot/project-static/docs/rest-api/authentication.html +1389 -190
  806. nautobot/project-static/docs/rest-api/filtering.html +1389 -190
  807. nautobot/project-static/docs/rest-api/overview.html +2002 -576
  808. nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
  809. nautobot/project-static/docs/search/search_index.json +1 -1
  810. nautobot/project-static/docs/sitemap.xml +197 -187
  811. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  812. nautobot/project-static/docs/user-guides/custom-fields.html +1390 -191
  813. nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1392 -193
  814. nautobot/project-static/docs/user-guides/getting-started/index.html +1388 -189
  815. nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1388 -189
  816. nautobot/project-static/docs/user-guides/getting-started/ipam.html +1386 -187
  817. nautobot/project-static/docs/user-guides/getting-started/platforms.html +1448 -249
  818. nautobot/project-static/docs/user-guides/getting-started/regions.html +1411 -212
  819. nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1395 -196
  820. nautobot/project-static/docs/user-guides/getting-started/tenants.html +1448 -249
  821. nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1448 -249
  822. nautobot/project-static/docs/user-guides/git-data-source.html +1405 -206
  823. nautobot/project-static/docs/user-guides/graphql.html +1402 -203
  824. nautobot/project-static/docs/user-guides/relationships.html +1448 -249
  825. nautobot/project-static/docs/user-guides/s3-django-storage.html +1448 -249
  826. nautobot/project-static/js/forms.js +16 -9
  827. nautobot/project-static/js/theme.js +5 -0
  828. nautobot/tenancy/api/serializers.py +4 -34
  829. nautobot/tenancy/api/urls.py +1 -1
  830. nautobot/tenancy/filters/__init__.py +9 -7
  831. nautobot/tenancy/filters/mixins.py +3 -2
  832. nautobot/tenancy/forms.py +3 -36
  833. nautobot/tenancy/migrations/0001_initial.py +0 -1
  834. nautobot/tenancy/migrations/0002_auto_slug.py +0 -1
  835. nautobot/tenancy/migrations/0003_mptt_to_tree_queries.py +0 -1
  836. nautobot/tenancy/migrations/0004_change_tree_manager_on_tree_models.py +0 -1
  837. nautobot/tenancy/migrations/0005_rename_foreign_keys_and_related_names.py +0 -1
  838. nautobot/tenancy/migrations/0006_created_datetime.py +0 -1
  839. nautobot/tenancy/migrations/0007_remove_tenant_tenantgroup_slug.py +20 -0
  840. nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
  841. nautobot/tenancy/models.py +0 -30
  842. nautobot/tenancy/navigation.py +6 -39
  843. nautobot/tenancy/tables.py +4 -4
  844. nautobot/tenancy/templates/tenancy/tenant.html +12 -12
  845. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -1
  846. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  847. nautobot/tenancy/tests/test_api.py +1 -12
  848. nautobot/tenancy/tests/test_filters.py +20 -12
  849. nautobot/tenancy/tests/test_views.py +11 -29
  850. nautobot/tenancy/urls.py +10 -10
  851. nautobot/tenancy/views.py +0 -3
  852. nautobot/ui/.eslintignore +6 -0
  853. nautobot/ui/.gitignore +10 -0
  854. nautobot/ui/.prettierignore +9 -0
  855. nautobot/ui/.prettierrc +4 -0
  856. nautobot/ui/README.md +33 -0
  857. nautobot/ui/app_imports.js.j2 +7 -0
  858. nautobot/ui/craco.config.js +46 -0
  859. nautobot/ui/jsconfig-base.json +11 -0
  860. nautobot/ui/jsconfig.json +5 -0
  861. nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
  862. nautobot/ui/package-lock.json +21451 -0
  863. nautobot/ui/package.json +70 -0
  864. nautobot/ui/public/index.html +47 -0
  865. nautobot/ui/public/logo192.png +0 -0
  866. nautobot/ui/public/logo512.png +0 -0
  867. nautobot/ui/public/manifest.json +25 -0
  868. nautobot/ui/public/nautobot_logo.svg +131 -0
  869. nautobot/ui/public/robots.txt +3 -0
  870. nautobot/ui/src/App.js +71 -0
  871. nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
  872. nautobot/ui/src/components/AppTab.js +40 -0
  873. nautobot/ui/src/components/Apps.js +60 -0
  874. nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
  875. nautobot/ui/src/components/HomePanel.js +58 -0
  876. nautobot/ui/src/components/JobHistoryTable.js +78 -0
  877. nautobot/ui/src/components/Layout.js +53 -0
  878. nautobot/ui/src/components/LoadingWidget.js +25 -0
  879. nautobot/ui/src/components/Navbar.js +116 -0
  880. nautobot/ui/src/components/NotificationPopover.js +27 -0
  881. nautobot/ui/src/components/ObjectListTable.js +209 -0
  882. nautobot/ui/src/components/ReferenceDataTag.js +35 -0
  883. nautobot/ui/src/components/RouterButton.js +10 -0
  884. nautobot/ui/src/components/RouterLink.js +10 -0
  885. nautobot/ui/src/components/SidebarNav.js +147 -0
  886. nautobot/ui/src/components/Table.js +48 -0
  887. nautobot/ui/src/components/TableItem.js +71 -0
  888. nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
  889. nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
  890. nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
  891. nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
  892. nautobot/ui/src/components/__tests__/Table.test.js +36 -0
  893. nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
  894. nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
  895. nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
  896. nautobot/ui/src/components/pagination.js +93 -0
  897. nautobot/ui/src/components/paginator.js +79 -0
  898. nautobot/ui/src/components/paginator_form.js +43 -0
  899. nautobot/ui/src/components/usePagination.js +57 -0
  900. nautobot/ui/src/constants/apiPath.js +10 -0
  901. nautobot/ui/src/constants/icons.js +15 -0
  902. nautobot/ui/src/constants/size.js +15 -0
  903. nautobot/ui/src/index.js +65 -0
  904. nautobot/ui/src/reportWebVitals.js +15 -0
  905. nautobot/ui/src/router.js +77 -0
  906. nautobot/ui/src/utils/api.js +131 -0
  907. nautobot/ui/src/utils/app-import.js +15 -0
  908. nautobot/ui/src/utils/color.js +15 -0
  909. nautobot/ui/src/utils/date.js +14 -0
  910. nautobot/ui/src/utils/index.js +15 -0
  911. nautobot/ui/src/utils/navigation.js +32 -0
  912. nautobot/ui/src/utils/session.js +64 -0
  913. nautobot/ui/src/utils/store.js +242 -0
  914. nautobot/ui/src/utils/string.js +6 -0
  915. nautobot/ui/src/utils/url.js +4 -0
  916. nautobot/ui/src/views/Home.js +138 -0
  917. nautobot/ui/src/views/InstalledApps.js +80 -0
  918. nautobot/ui/src/views/Login.js +48 -0
  919. nautobot/ui/src/views/Logout.js +20 -0
  920. nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
  921. nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
  922. nautobot/ui/src/views/__tests__/Login.test.js +15 -0
  923. nautobot/ui/src/views/generic/GenericView.js +142 -0
  924. nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
  925. nautobot/ui/src/views/generic/ObjectList.js +127 -0
  926. nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
  927. nautobot/users/admin.py +1 -1
  928. nautobot/users/api/serializers.py +51 -61
  929. nautobot/users/api/urls.py +1 -1
  930. nautobot/users/api/views.py +53 -2
  931. nautobot/users/migrations/0001_initial.py +0 -1
  932. nautobot/users/migrations/0002_token_ordering_by_created.py +0 -1
  933. nautobot/users/migrations/0003_alter_user_options.py +0 -1
  934. nautobot/users/migrations/0004_alter_user_managers.py +0 -1
  935. nautobot/users/tests/test_api.py +109 -28
  936. nautobot/users/tests/test_filters.py +0 -4
  937. nautobot/users/tests/test_models.py +0 -1
  938. nautobot/users/views.py +0 -7
  939. nautobot/virtualization/api/serializers.py +18 -132
  940. nautobot/virtualization/api/urls.py +1 -1
  941. nautobot/virtualization/api/views.py +1 -22
  942. nautobot/virtualization/choices.py +0 -2
  943. nautobot/virtualization/filters.py +12 -7
  944. nautobot/virtualization/forms.py +21 -117
  945. nautobot/virtualization/migrations/0001_initial.py +0 -1
  946. nautobot/virtualization/migrations/0002_virtualmachine_local_context_schema.py +0 -1
  947. nautobot/virtualization/migrations/0003_vminterface_verbose_name.py +0 -1
  948. nautobot/virtualization/migrations/0004_auto_slug.py +0 -1
  949. nautobot/virtualization/migrations/0005_add_natural_indexing.py +0 -1
  950. nautobot/virtualization/migrations/0006_vminterface_status.py +0 -1
  951. nautobot/virtualization/migrations/0007_vminterface_status_data_migration.py +0 -1
  952. nautobot/virtualization/migrations/0008_vminterface_parent.py +0 -1
  953. nautobot/virtualization/migrations/0009_cluster_location.py +0 -1
  954. nautobot/virtualization/migrations/0010_vminterface_mac_address_data_migration.py +0 -1
  955. nautobot/virtualization/migrations/0011_alter_vminterface_mac_address.py +0 -1
  956. nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -2
  957. nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -12
  958. nautobot/virtualization/migrations/0014_rename_virtualmachine_roles.py +0 -1
  959. nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -2
  960. nautobot/virtualization/migrations/0016_remove_site_foreign_key_from_cluster_class.py +0 -1
  961. nautobot/virtualization/migrations/0017_created_datetime.py +0 -1
  962. nautobot/virtualization/migrations/0018_related_name_changes.py +1 -2
  963. nautobot/virtualization/migrations/0019_vminterface_ip_addresses_m2m.py +0 -1
  964. nautobot/virtualization/migrations/0020_remove_clustergroup_clustertype_slug.py +20 -0
  965. nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
  966. nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
  967. nautobot/virtualization/migrations/0023_ipam__namespaces.py +25 -0
  968. nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
  969. nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
  970. nautobot/virtualization/models.py +39 -131
  971. nautobot/virtualization/navigation.py +18 -99
  972. nautobot/virtualization/tables.py +4 -4
  973. nautobot/virtualization/templates/virtualization/virtualmachine.html +13 -2
  974. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
  975. nautobot/virtualization/tests/test_api.py +42 -52
  976. nautobot/virtualization/tests/test_filters.py +98 -75
  977. nautobot/virtualization/tests/test_models.py +36 -13
  978. nautobot/virtualization/tests/test_views.py +68 -73
  979. nautobot/virtualization/urls.py +10 -10
  980. nautobot/virtualization/views.py +8 -14
  981. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +15 -22
  982. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +987 -834
  983. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
  984. nautobot/circuits/api/nested_serializers.py +0 -69
  985. nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
  986. nautobot/dcim/api/nested_serializers.py +0 -356
  987. nautobot/dcim/templates/dcim/device_import.html +0 -5
  988. nautobot/dcim/templates/dcim/device_import_child.html +0 -5
  989. nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
  990. nautobot/extras/api/nested_serializers.py +0 -353
  991. nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -42
  992. nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -47
  993. nautobot/extras/reports.py +0 -60
  994. nautobot/extras/scripts.py +0 -72
  995. nautobot/extras/tests/example_jobs/script_variables.py +0 -67
  996. nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
  997. nautobot/extras/tests/example_jobs/test_fail.py +0 -16
  998. nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
  999. nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
  1000. nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
  1001. nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
  1002. nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
  1003. nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
  1004. nautobot/extras/tests/example_jobs/test_modify_db.py +0 -19
  1005. nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
  1006. nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
  1007. nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
  1008. nautobot/extras/tests/example_jobs/test_pass.py +0 -19
  1009. nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
  1010. nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
  1011. nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
  1012. nautobot/ipam/api/nested_serializers.py +0 -143
  1013. nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js +0 -29
  1014. nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js.map +0 -8
  1015. nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js +0 -18
  1016. nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js.map +0 -8
  1017. nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css +0 -1
  1018. nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css.map +0 -1
  1019. nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css +0 -1
  1020. nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css.map +0 -1
  1021. nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css +0 -1
  1022. nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css.map +0 -1
  1023. nautobot/tenancy/api/nested_serializers.py +0 -31
  1024. nautobot/users/api/nested_serializers.py +0 -67
  1025. nautobot/virtualization/api/nested_serializers.py +0 -65
  1026. /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
  1027. /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
  1028. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
  1029. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -1,33 +1,38 @@
1
1
  # Data models relating to Jobs
2
2
 
3
+ import contextlib
3
4
  from datetime import timedelta
4
5
  import logging
5
- import os
6
- import uuid
7
6
 
8
7
  from celery import schedules
8
+ from celery.utils.log import get_logger, LoggingProxy
9
9
  from django.conf import settings
10
10
  from django.contrib.contenttypes.models import ContentType
11
- from django.core.exceptions import ObjectDoesNotExist, ValidationError
11
+ from django.core.exceptions import ValidationError
12
12
  from django.core.serializers.json import DjangoJSONEncoder
13
13
  from django.core.validators import MinValueValidator
14
14
  from django.db import models, transaction
15
15
  from django.db.models import signals
16
- from django.urls import reverse
17
16
  from django.utils import timezone
17
+ from django.utils.functional import cached_property
18
18
  from django_celery_beat.clockedschedule import clocked
19
19
  from prometheus_client import Histogram
20
20
 
21
- from nautobot.core.celery import NautobotKombuJSONEncoder
21
+ from nautobot.core.celery import (
22
+ add_nautobot_log_handler,
23
+ app,
24
+ NautobotKombuJSONEncoder,
25
+ setup_nautobot_job_logging,
26
+ )
27
+ from nautobot.core.celery.control import refresh_git_repository
22
28
  from nautobot.core.models import BaseManager, BaseModel
23
- from nautobot.core.models.fields import AutoSlugField, JSONArrayField
29
+ from nautobot.core.models.fields import JSONArrayField
24
30
  from nautobot.core.models.generics import OrganizationalModel, PrimaryModel
25
31
  from nautobot.core.utils.logging import sanitize
26
32
  from nautobot.extras.choices import (
27
33
  ButtonClassChoices,
28
34
  JobExecutionType,
29
35
  JobResultStatusChoices,
30
- JobSourceChoices,
31
36
  LogLevelChoices,
32
37
  )
33
38
  from nautobot.extras.constants import (
@@ -36,20 +41,15 @@ from nautobot.extras.constants import (
36
41
  JOB_LOG_MAX_LOG_OBJECT_LENGTH,
37
42
  JOB_MAX_GROUPING_LENGTH,
38
43
  JOB_MAX_NAME_LENGTH,
39
- JOB_MAX_SOURCE_LENGTH,
40
44
  JOB_OVERRIDABLE_FIELDS,
41
45
  )
42
- from nautobot.extras.models import ChangeLoggedModel
46
+ from nautobot.extras.models import ChangeLoggedModel, GitRepository
43
47
  from nautobot.extras.models.mixins import NotesMixin
44
- from nautobot.extras.plugins.utils import import_object
45
48
  from nautobot.extras.managers import JobResultManager, ScheduledJobsManager
46
49
  from nautobot.extras.querysets import JobQuerySet, ScheduledJobExtendedQuerySet
47
50
  from nautobot.extras.utils import (
48
51
  ChangeLoggedModelsQuery,
49
- FeatureQuery,
50
52
  extras_features,
51
- get_job_content_type,
52
- jobs_in_directory,
53
53
  )
54
54
 
55
55
  from .customfields import CustomFieldModel
@@ -86,23 +86,6 @@ class Job(PrimaryModel):
86
86
  """
87
87
 
88
88
  # Information used to locate the Job source code
89
- source = models.CharField(
90
- max_length=JOB_MAX_SOURCE_LENGTH,
91
- choices=JobSourceChoices,
92
- editable=False,
93
- db_index=True,
94
- help_text="Source of the Python code for this job - local, Git repository, or plugins",
95
- )
96
- git_repository = models.ForeignKey(
97
- to="extras.GitRepository",
98
- blank=True,
99
- null=True,
100
- default=None,
101
- on_delete=models.SET_NULL,
102
- db_index=True,
103
- related_name="jobs",
104
- help_text="Git repository that provides this job",
105
- )
106
89
  module_name = models.CharField(
107
90
  max_length=JOB_MAX_NAME_LENGTH,
108
91
  editable=False,
@@ -116,11 +99,6 @@ class Job(PrimaryModel):
116
99
  help_text="Name of the Python class providing this job",
117
100
  )
118
101
 
119
- slug = AutoSlugField(
120
- max_length=JOB_MAX_NAME_LENGTH,
121
- populate_from="name",
122
- )
123
-
124
102
  # Human-readable information, potentially inherited from the source code
125
103
  # See also the docstring of nautobot.extras.jobs.BaseJob.Meta.
126
104
  grouping = models.CharField(
@@ -161,17 +139,17 @@ class Job(PrimaryModel):
161
139
  approval_required = models.BooleanField(
162
140
  default=False, help_text="Whether the job requires approval from another user before running"
163
141
  )
164
- commit_default = models.BooleanField(
165
- default=True, help_text="Whether the job defaults to committing changes when run, or defaults to a dry-run"
166
- )
167
142
  hidden = models.BooleanField(
168
143
  default=False,
169
144
  db_index=True,
170
145
  help_text="Whether the job defaults to not being shown in the UI",
171
146
  )
172
147
  # Job.Meta.field_order is not overridable in this model
148
+ dryrun_default = models.BooleanField(
149
+ default=False, help_text="Whether the job defaults to running with dryrun argument set to true"
150
+ )
173
151
  read_only = models.BooleanField(
174
- default=False, help_text="Whether the job is prevented from making lasting changes to the database"
152
+ default=False, editable=False, help_text="Set to true if the job does not make any changes to the environment"
175
153
  )
176
154
  soft_time_limit = models.FloatField(
177
155
  default=0,
@@ -191,6 +169,11 @@ class Job(PrimaryModel):
191
169
  blank=True,
192
170
  help_text="Comma separated list of task queues that this job can run on. A blank list will use the default queue",
193
171
  )
172
+ supports_dryrun = models.BooleanField(
173
+ default=False,
174
+ editable=False,
175
+ help_text="If supported, allows the job to bypass approval when running with dryrun argument set to true",
176
+ )
194
177
 
195
178
  # Flags to indicate whether the above properties are inherited from the source code or overridden by the database
196
179
  grouping_override = models.BooleanField(
@@ -205,12 +188,11 @@ class Job(PrimaryModel):
205
188
  default=False,
206
189
  help_text="If set, the configured description will remain even if the underlying Job source code changes",
207
190
  )
208
-
209
191
  approval_required_override = models.BooleanField(
210
192
  default=False,
211
193
  help_text="If set, the configured value will remain even if the underlying Job source code changes",
212
194
  )
213
- commit_default_override = models.BooleanField(
195
+ dryrun_default_override = models.BooleanField(
214
196
  default=False,
215
197
  help_text="If set, the configured value will remain even if the underlying Job source code changes",
216
198
  )
@@ -218,10 +200,6 @@ class Job(PrimaryModel):
218
200
  default=False,
219
201
  help_text="If set, the configured value will remain even if the underlying Job source code changes",
220
202
  )
221
- read_only_override = models.BooleanField(
222
- default=False,
223
- help_text="If set, the configured value will remain even if the underlying Job source code changes",
224
- )
225
203
  soft_time_limit_override = models.BooleanField(
226
204
  default=False,
227
205
  help_text="If set, the configured value will remain even if the underlying Job source code changes",
@@ -244,90 +222,29 @@ class Job(PrimaryModel):
244
222
  class Meta:
245
223
  managed = True
246
224
  ordering = ["grouping", "name"]
247
- unique_together = ["source", "git_repository", "module_name", "job_class_name"]
225
+ unique_together = ["module_name", "job_class_name"]
248
226
 
249
227
  def __init__(self, *args, **kwargs):
250
228
  super().__init__(*args, **kwargs)
251
- self._job_class = None
252
229
  self._latest_result = None
253
230
 
254
231
  def __str__(self):
255
232
  return self.name
256
233
 
257
- def validate_unique(self, exclude=None):
258
- """
259
- Check for duplicate (source, module_name, job_class_name) in the case where git_repository is None.
260
-
261
- This is needed because NULL != NULL and so the unique_together constraint will not flag this case.
262
- """
263
- if self.git_repository is None:
264
- if Job.objects.exclude(pk=self.pk).filter(
265
- source=self.source, module_name=self.module_name, job_class_name=self.job_class_name
266
- ):
267
- raise ValidationError(
268
- {"job_class_name": "A Job already exists with this source, module_name, and job_class_name"}
269
- )
270
-
271
- super().validate_unique(exclude=exclude)
272
-
273
- @property
234
+ @cached_property
274
235
  def job_class(self):
275
236
  """Get the Job class (source code) associated with this Job model."""
276
237
  if not self.installed:
277
238
  return None
278
- if self._job_class is None:
279
- if self.source == JobSourceChoices.SOURCE_LOCAL:
280
- path = settings.JOBS_ROOT
281
- for job_info in jobs_in_directory(settings.JOBS_ROOT, module_name=self.module_name):
282
- if job_info.job_class_name == self.job_class_name:
283
- self._job_class = job_info.job_class
284
- break
285
- else:
286
- logger.warning("Module %s job class %s not found!", self.module_name, self.job_class_name)
287
- elif self.source == JobSourceChoices.SOURCE_GIT:
288
- from nautobot.extras.datasources.git import ensure_git_repository
289
-
290
- if self.git_repository is None:
291
- logger.warning("Job %s %s has no associated Git repository", self.module_name, self.job_class_name)
292
- return None
293
- try:
294
- # In the case where we have multiple Nautobot instances, or multiple worker instances,
295
- # they are not required to share a common filesystem; therefore, we may need to refresh our local
296
- # clone of the Git repository to ensure that it is in sync with the latest repository clone
297
- # from any instance.
298
- ensure_git_repository(
299
- self.git_repository,
300
- head=self.git_repository.current_head,
301
- logger=logger,
302
- )
303
- path = os.path.join(self.git_repository.filesystem_path, "jobs")
304
- for job_info in jobs_in_directory(path, module_name=self.module_name):
305
- if job_info.job_class_name == self.job_class_name:
306
- self._job_class = job_info.job_class
307
- break
308
- else:
309
- logger.warning(
310
- "Module %s job class %s not found in repository %s",
311
- self.module_name,
312
- self.job_class_name,
313
- self.git_repository,
314
- )
315
- except ObjectDoesNotExist:
316
- return None
317
- except Exception as exc:
318
- logger.error(f"Error during local clone/refresh of Git repository {self.git_repository}: {exc}")
319
- return None
320
- elif self.source == JobSourceChoices.SOURCE_PLUGIN:
321
- # pkgutil.resolve_name is only available in Python 3.9 and later
322
- self._job_class = import_object(f"{self.module_name}.{self.job_class_name}")
323
-
324
- return self._job_class
239
+ try:
240
+ return self.job_task.__class__
241
+ except Exception as exc:
242
+ logger.error(str(exc))
243
+ return None
325
244
 
326
245
  @property
327
246
  def class_path(self):
328
- if self.git_repository is not None:
329
- return f"{self.source}.{self.git_repository.slug}/{self.module_name}/{self.job_class_name}"
330
- return f"{self.source}/{self.module_name}/{self.job_class_name}"
247
+ return f"{self.module_name}.{self.job_class_name}"
331
248
 
332
249
  @property
333
250
  def latest_result(self):
@@ -341,12 +258,25 @@ class Job(PrimaryModel):
341
258
 
342
259
  @property
343
260
  def runnable(self):
344
- return (
345
- self.enabled
346
- and self.installed
347
- and self.job_class is not None
348
- and not (self.has_sensitive_variables and self.approval_required)
349
- )
261
+ return self.enabled and self.installed and not (self.has_sensitive_variables and self.approval_required)
262
+
263
+ @cached_property
264
+ def git_repository(self):
265
+ """GitRepository record, if any, that owns this Job."""
266
+ try:
267
+ return GitRepository.objects.get(slug=self.module_name.split(".")[0])
268
+ except GitRepository.DoesNotExist:
269
+ return None
270
+
271
+ @property
272
+ def job_task(self):
273
+ """Get the registered Celery task, refreshing it if necessary."""
274
+ if self.git_repository is not None:
275
+ # If this Job comes from a Git repository, make sure we have the correct version of said code.
276
+ refresh_git_repository(
277
+ state=None, repository_pk=self.git_repository.pk, head=self.git_repository.current_head
278
+ )
279
+ return app.tasks[f"{self.module_name}.{self.job_class_name}"]
350
280
 
351
281
  def clean(self):
352
282
  """For any non-overridden fields, make sure they get reset to the actual underlying class value if known."""
@@ -355,12 +285,7 @@ class Job(PrimaryModel):
355
285
  if not getattr(self, f"{field_name}_override", False):
356
286
  setattr(self, field_name, getattr(self.job_class, field_name))
357
287
 
358
- if self.git_repository is not None and self.source != JobSourceChoices.SOURCE_GIT:
359
- raise ValidationError('A Git repository may only be specified when the source is "git"')
360
-
361
288
  # Protect against invalid input when auto-creating Job records
362
- if len(self.source) > JOB_MAX_SOURCE_LENGTH:
363
- raise ValidationError(f"Source may not exceed {JOB_MAX_SOURCE_LENGTH} characters in length")
364
289
  if len(self.module_name) > JOB_MAX_NAME_LENGTH:
365
290
  raise ValidationError(f"Module name may not exceed {JOB_MAX_NAME_LENGTH} characters in length")
366
291
  if len(self.job_class_name) > JOB_MAX_NAME_LENGTH:
@@ -369,17 +294,12 @@ class Job(PrimaryModel):
369
294
  raise ValidationError(f"Grouping may not exceed {JOB_MAX_GROUPING_LENGTH} characters in length")
370
295
  if len(self.name) > JOB_MAX_NAME_LENGTH:
371
296
  raise ValidationError(f"Name may not exceed {JOB_MAX_NAME_LENGTH} characters in length")
372
- if len(self.slug) > JOB_MAX_NAME_LENGTH:
373
- raise ValidationError(f"Slug may not exceed {JOB_MAX_NAME_LENGTH} characters in length")
374
297
 
375
298
  if self.has_sensitive_variables is True and self.approval_required is True:
376
299
  raise ValidationError(
377
300
  {"approval_required": "A job that may have sensitive variables cannot be marked as requiring approval"}
378
301
  )
379
302
 
380
- def get_absolute_url(self):
381
- return reverse("extras:job_detail", kwargs={"slug": self.slug})
382
-
383
303
 
384
304
  @extras_features("graphql")
385
305
  class JobHook(OrganizationalModel):
@@ -406,7 +326,6 @@ class JobHook(OrganizationalModel):
406
326
  limit_choices_to={"is_job_hook_receiver": True},
407
327
  )
408
328
  name = models.CharField(max_length=100, unique=True)
409
- slug = AutoSlugField(populate_from="name")
410
329
  type_create = models.BooleanField(default=False, help_text="Call this job hook when a matching object is created.")
411
330
  type_delete = models.BooleanField(default=False, help_text="Call this job hook when a matching object is deleted.")
412
331
  type_update = models.BooleanField(default=False, help_text="Call this job hook when a matching object is updated.")
@@ -424,9 +343,6 @@ class JobHook(OrganizationalModel):
424
343
  if not self.type_create and not self.type_delete and not self.type_update:
425
344
  raise ValidationError("You must select at least one type: create, update, and/or delete.")
426
345
 
427
- def get_absolute_url(self):
428
- return reverse("extras:jobhook", kwargs={"slug": self.slug})
429
-
430
346
  @classmethod
431
347
  def check_for_conflicts(
432
348
  cls, instance=None, content_types=None, job=None, type_create=None, type_update=None, type_delete=None
@@ -485,7 +401,7 @@ class JobLogEntry(BaseModel):
485
401
 
486
402
  job_result = models.ForeignKey(to="extras.JobResult", on_delete=models.CASCADE, related_name="job_log_entries")
487
403
  log_level = models.CharField(
488
- max_length=32, choices=LogLevelChoices, default=LogLevelChoices.LOG_DEFAULT, db_index=True
404
+ max_length=32, choices=LogLevelChoices, default=LogLevelChoices.LOG_INFO, db_index=True
489
405
  )
490
406
  grouping = models.CharField(max_length=JOB_LOG_MAX_GROUPING_LENGTH, default="main")
491
407
  message = models.TextField(blank=True)
@@ -497,8 +413,6 @@ class JobLogEntry(BaseModel):
497
413
  log_object = models.CharField(max_length=JOB_LOG_MAX_LOG_OBJECT_LENGTH, blank=True, default="")
498
414
  absolute_url = models.CharField(max_length=JOB_LOG_MAX_ABSOLUTE_URL_LENGTH, blank=True, default="")
499
415
 
500
- csv_headers = ["created", "grouping", "log_level", "log_object", "message"]
501
-
502
416
  def __str__(self):
503
417
  return self.message
504
418
 
@@ -507,10 +421,6 @@ class JobLogEntry(BaseModel):
507
421
  get_latest_by = "created"
508
422
  verbose_name_plural = "job log entries"
509
423
 
510
- def to_csv(self):
511
- """Indicates model fields to return as csv."""
512
- return (str(self.created), self.grouping, self.log_level, self.log_object, self.message)
513
-
514
424
 
515
425
  #
516
426
  # Job results
@@ -539,18 +449,8 @@ class JobResult(BaseModel, CustomFieldModel):
539
449
  db_index=True,
540
450
  help_text="Registered name of the Celery task for this job. Internal use only.",
541
451
  )
542
- obj_type = models.ForeignKey(
543
- to=ContentType,
544
- related_name="job_results",
545
- verbose_name="Object types",
546
- limit_choices_to=FeatureQuery("job_results"),
547
- help_text="The object type to which this job result applies",
548
- on_delete=models.CASCADE,
549
- null=True,
550
- blank=True,
551
- )
552
- date_created = models.DateTimeField(auto_now_add=True)
553
- date_done = models.DateTimeField(null=True, blank=True)
452
+ date_created = models.DateTimeField(auto_now_add=True, db_index=True)
453
+ date_done = models.DateTimeField(null=True, blank=True, db_index=True)
554
454
  user = models.ForeignKey(
555
455
  to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name="+", blank=True, null=True
556
456
  )
@@ -564,23 +464,16 @@ class JobResult(BaseModel, CustomFieldModel):
564
464
  data = models.JSONField(encoder=DjangoJSONEncoder, null=True, blank=True)
565
465
  """
566
466
  Although "data" is technically an unstructured field, we have a standard structure that we try to adhere to.
567
-
568
467
  This structure is created loosely as a superset of the formats used by Scripts and Reports in NetBox 2.10.
569
-
570
468
  Log Messages now go to their own object, the JobLogEntry.
571
-
572
469
  data = {
573
470
  "output": <optional string, such as captured stdout/stderr>,
574
471
  }
575
472
  """
576
- periodic_task_name = models.CharField(
577
- null=True,
578
- max_length=255,
579
- help_text="Registered name of the Celery periodic task for this job. Internal use only.",
580
- )
581
473
  worker = models.CharField(max_length=100, default=None, null=True)
582
- task_args = models.JSONField(blank=True, null=True, encoder=NautobotKombuJSONEncoder)
583
- task_kwargs = models.JSONField(blank=True, null=True, encoder=NautobotKombuJSONEncoder)
474
+ task_args = models.JSONField(blank=True, default=list, encoder=NautobotKombuJSONEncoder)
475
+ task_kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
476
+ celery_kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
584
477
  # TODO(jathan): This field is currently unused for Jobs, but we should coerce it to a JSONField
585
478
  # and set a contract that anything returned from a Job task MUST be JSON. In DCR core it is
586
479
  # expected to be encoded/decoded using `content_type` and `content_encoding` which we have
@@ -596,8 +489,6 @@ class JobResult(BaseModel, CustomFieldModel):
596
489
  meta = models.JSONField(null=True, default=None, editable=False)
597
490
  scheduled_job = models.ForeignKey(to="extras.ScheduledJob", on_delete=models.SET_NULL, null=True, blank=True)
598
491
 
599
- task_id = models.UUIDField(unique=True)
600
-
601
492
  objects = JobResultManager()
602
493
 
603
494
  def __init__(self, *args, **kwargs):
@@ -607,14 +498,34 @@ class JobResult(BaseModel, CustomFieldModel):
607
498
  class Meta:
608
499
  ordering = ["-date_created"]
609
500
  get_latest_by = "date_created"
501
+ indexes = [
502
+ models.Index(
503
+ name="extras_jobresult_rcreated_idx",
504
+ fields=["-date_created"],
505
+ ),
506
+ models.Index(
507
+ name="extras_jr_rdone_idx",
508
+ fields=["-date_done"],
509
+ ),
510
+ models.Index(
511
+ name="extras_jr_statrcreate_idx",
512
+ fields=["status", "-date_created"],
513
+ ),
514
+ models.Index(
515
+ name="extras_jr_statrdone_idx",
516
+ fields=["status", "-date_done"],
517
+ ),
518
+ ]
519
+
520
+ natural_key_field_names = ["id"]
610
521
 
611
522
  def __str__(self):
612
- return str(self.task_id)
523
+ return f"{self.name} started at {self.date_created} ({self.status})"
613
524
 
614
525
  def as_dict(self):
615
526
  """This is required by the django-celery-results DB backend."""
616
527
  return {
617
- "task_id": self.task_id,
528
+ "id": self.id,
618
529
  "task_name": self.task_name,
619
530
  "task_args": self.task_args,
620
531
  "task_kwargs": self.task_kwargs,
@@ -636,92 +547,8 @@ class JobResult(BaseModel, CustomFieldModel):
636
547
 
637
548
  return f"{int(minutes)} minutes, {seconds:.2f} seconds"
638
549
 
639
- @property
640
- def related_object(self):
641
- """Get the related object, if any, identified by the `obj_type`, `name`, and/or `task_id` fields.
642
-
643
- If `obj_type` is extras.Job, then the `name` is used to look up an extras.jobs.Job subclass based on the
644
- `class_path` of the Job subclass.
645
- Note that this is **not** the extras.models.Job model class nor an instance thereof.
646
-
647
- Else, if the the model class referenced by `obj_type` has a `name` field, our `name` field will be used
648
- to look up a corresponding model instance. This is used, for example, to look up a related `GitRepository`;
649
- more generally it can be used by any model that 1) has a unique `name` field and 2) needs to have a many-to-one
650
- relationship between JobResults and model instances.
651
-
652
- Else, the `obj_type` and `task_id` will be used together as a quasi-GenericForeignKey to look up a model
653
- instance whose PK corresponds to the `task_id`. This behavior is currently unused in the Nautobot core,
654
- but may be of use to plugin developers wishing to create JobResults that have a one-to-one relationship
655
- to plugin model instances.
656
-
657
- This method is potentially rather slow as get_job() may need to actually load the Job class from disk;
658
- consider carefully whether you actually need to use it.
659
- """
660
- from nautobot.extras.jobs import get_job # needed here to avoid a circular import issue
661
-
662
- if self.obj_type == get_job_content_type():
663
- # Related object is an extras.Job subclass, our `name` matches its `class_path`
664
- return get_job(self.name)
665
-
666
- if self.obj_type:
667
- model_class = self.obj_type.model_class()
668
- else:
669
- model_class = None
670
-
671
- if model_class is not None:
672
- if hasattr(model_class, "name"):
673
- # See if we have a many-to-one relationship from JobResult to model_class record, based on `name`
674
- try:
675
- return model_class.objects.get(name=self.name)
676
- except model_class.DoesNotExist:
677
- pass
678
-
679
- # See if we have a one-to-one relationship from JobResult to model_class record based on `task_id`
680
- try:
681
- return model_class.objects.get(id=self.task_id)
682
- except model_class.DoesNotExist:
683
- pass
684
-
685
- return None
686
-
687
- @property
688
- def related_name(self):
689
- """
690
- Similar to self.name, but if there's an appropriate `related_object`, use its name instead.
691
-
692
- Since this calls related_object, the same potential performance concerns exist. Use with caution.
693
- """
694
- related_object = self.related_object
695
- if not related_object:
696
- return self.name
697
- if hasattr(related_object, "name"):
698
- return related_object.name
699
- return str(related_object)
700
-
701
- @property
702
- def linked_record(self):
703
- """
704
- A newer alternative to self.related_object that looks up an extras.models.Job instead of an extras.jobs.Job.
705
- """
706
- if self.job_model is not None:
707
- return self.job_model
708
- model_class = self.obj_type.model_class()
709
- if model_class is not None:
710
- if hasattr(model_class, "name"):
711
- try:
712
- return model_class.objects.get(name=self.name)
713
- except model_class.DoesNotExist:
714
- pass
715
- if hasattr(model_class, "class_path"):
716
- try:
717
- return model_class.objects.get(class_path=self.name)
718
- except model_class.DoesNotExist:
719
- pass
720
- return None
721
-
722
- def get_absolute_url(self):
723
- return reverse("extras:jobresult", kwargs={"pk": self.pk})
724
-
550
+ # FIXME(jathan): This needs to go away. Need to think about that the impact
551
+ # will be in the JOB_RESULT_METRIC and how to compensate for it.
725
552
  def set_status(self, status):
726
553
  """
727
554
  Helper method to change the status of the job result. If the target status is terminal, the completion
@@ -739,69 +566,118 @@ class JobResult(BaseModel, CustomFieldModel):
739
566
  )
740
567
 
741
568
  @classmethod
742
- def enqueue_job(cls, func, name, obj_type, user, *args, celery_kwargs=None, schedule=None, **kwargs):
569
+ def execute_job(cls, job_model, user, *job_args, celery_kwargs=None, profile=False, **job_kwargs):
743
570
  """
744
- Create a JobResult instance and enqueue a job using the given callable
745
-
746
- func: The callable object to be enqueued for execution
747
- name: Name for the JobResult instance - corresponds to the desired Job class's "class_path" attribute,
748
- if obj_type is extras.Job; for other funcs and obj_types it may differ.
749
- obj_type: ContentType to link to the JobResult instance obj_type
750
- user: User object to link to the JobResult instance
751
- celery_kwargs: Dictionary of kwargs to pass as **kwargs to Celery when job is queued
752
- args: additional args passed to the callable
753
- schedule: Optional ScheduledJob instance to link to the JobResult
754
- kwargs: additional kwargs passed to the callable
571
+ Create a JobResult instance and run a job in the current process, blocking until the job finishes.
572
+
573
+ Running tasks synchronously in celery is *NOT* supported and if possible `enqueue_job` with synchronous=False
574
+ should be used instead.
575
+
576
+ Args:
577
+ job_model (Job): The Job to be enqueued for execution
578
+ user (User): User object to link to the JobResult instance
579
+ celery_kwargs (dict, optional): Dictionary of kwargs to pass as **kwargs to Celery when job is run
580
+ profile (bool, optional): Whether to run cProfile on the job execution
581
+ *job_args: positional args passed to the job task
582
+ **job_kwargs: keyword args passed to the job task
583
+
584
+ Returns:
585
+ JobResult instance
586
+ """
587
+ return cls.enqueue_job(
588
+ job_model, user, *job_args, celery_kwargs=celery_kwargs, profile=profile, synchronous=True, **job_kwargs
589
+ )
590
+
591
+ @classmethod
592
+ def enqueue_job(
593
+ cls,
594
+ job_model,
595
+ user,
596
+ *job_args,
597
+ celery_kwargs=None,
598
+ profile=False,
599
+ schedule=None,
600
+ task_queue=None,
601
+ synchronous=False,
602
+ **job_kwargs,
603
+ ):
604
+ """Create a JobResult instance and enqueue a job to be executed asynchronously by a Celery worker.
605
+
606
+ Args:
607
+ job_model (Job): The Job to be enqueued for execution.
608
+ user (User): User object to link to the JobResult instance.
609
+ celery_kwargs (dict, optional): Dictionary of kwargs to pass as **kwargs to `apply_async()`/`apply()` when job is run.
610
+ profile (bool, optional): If True, dump cProfile stats on the job execution.
611
+ schedule (ScheduledJob, optional): ScheduledJob instance to link to the JobResult. Cannot be used with synchronous=True.
612
+ task_queue (str, optional): The celery queue to send the job to. If not set, use the default celery queue.
613
+ synchronous (bool, optional): If True, run the job in the current process, blocking until the job completes.
614
+ *job_args: positional args passed to the job task
615
+ **job_kwargs: keyword args passed to the job task
616
+
617
+ Returns:
618
+ JobResult instance
755
619
  """
756
- # Discard "request" parameter from the kwargs that we save in the job_result, as it's not relevant to re-runs,
757
- # and will likely go away in the future.
758
- job_result_kwargs = {key: value for key, value in kwargs.items() if key != "request"}
620
+ if schedule is not None and synchronous:
621
+ raise ValueError("Scheduled jobs cannot be run synchronously")
622
+
759
623
  job_result = cls.objects.create(
760
- name=name,
761
- obj_type=obj_type,
762
- user=user,
763
- task_id=uuid.uuid4(),
624
+ name=job_model.name,
625
+ job_model=job_model,
764
626
  scheduled_job=schedule,
627
+ user=user,
765
628
  )
766
629
 
767
- kwargs["job_result_pk"] = job_result.pk
630
+ if task_queue is None:
631
+ task_queue = settings.CELERY_TASK_DEFAULT_QUEUE
768
632
 
769
- # Prepare kwargs that will be sent to Celery
770
- if celery_kwargs is None:
771
- celery_kwargs = {}
633
+ job_celery_kwargs = {
634
+ "nautobot_job_job_model_id": job_model.id,
635
+ "nautobot_job_profile": profile,
636
+ "nautobot_job_user_id": user.id,
637
+ "queue": task_queue,
638
+ }
772
639
 
773
- if obj_type.app_label == "extras" and obj_type.model.lower() == "job":
774
- try:
775
- job_model = Job.objects.get_for_class_path(name)
776
- if job_model.soft_time_limit > 0:
777
- celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
778
- if job_model.time_limit > 0:
779
- celery_kwargs["time_limit"] = job_model.time_limit
780
- if not job_model.has_sensitive_variables:
781
- job_result.task_kwargs = job_result_kwargs
782
- job_result.job_model = job_model
783
- job_result.save()
784
- except Job.DoesNotExist:
785
- # 2.0 TODO: remove this fallback logic, database records should always exist
786
- from nautobot.extras.jobs import get_job # needed here to avoid a circular import issue
787
-
788
- job_class = get_job(name)
789
- if job_class is not None:
790
- logger.error("No Job instance found in the database corresponding to %s", name)
791
- if hasattr(job_class.Meta, "soft_time_limit"):
792
- celery_kwargs["soft_time_limit"] = job_class.Meta.soft_time_limit
793
- if hasattr(job_class.Meta, "time_limit"):
794
- celery_kwargs["time_limit"] = job_class.Meta.time_limit
795
- if not job_class.has_sensitive_variables:
796
- job_result.task_kwargs = job_result_kwargs
797
- job_result.save()
798
- else:
799
- logger.error("Neither a Job database record nor a Job source class were found for %s", name)
800
-
801
- # Jobs queued inside of a transaction need to run after the transaction completes and the JobResult is saved to the database
802
- transaction.on_commit(
803
- lambda: func.apply_async(args=args, kwargs=kwargs, task_id=str(job_result.task_id), **celery_kwargs)
804
- )
640
+ if schedule is not None:
641
+ job_celery_kwargs["nautobot_job_schedule_id"] = schedule.id
642
+ if job_model.soft_time_limit > 0:
643
+ job_celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
644
+ if job_model.time_limit > 0:
645
+ job_celery_kwargs["time_limit"] = job_model.time_limit
646
+
647
+ if celery_kwargs is not None:
648
+ job_celery_kwargs.update(celery_kwargs)
649
+
650
+ if synchronous:
651
+ # synchronous tasks are run before the JobResult is saved, so any fields required by
652
+ # the job must be added before calling `apply()`
653
+ job_result.celery_kwargs = job_celery_kwargs
654
+ job_result.save()
655
+
656
+ # setup synchronous task logging
657
+ redirect_logger = get_logger("celery.redirected")
658
+ add_nautobot_log_handler(redirect_logger)
659
+ setup_nautobot_job_logging(None, None, app.conf)
660
+
661
+ # redirect stdout/stderr to logger and run task
662
+ proxy = LoggingProxy(redirect_logger, app.conf.worker_redirect_stdouts_level)
663
+ with contextlib.redirect_stdout(proxy), contextlib.redirect_stderr(proxy):
664
+ eager_result = job_model.job_task.apply(
665
+ args=job_args, kwargs=job_kwargs, task_id=str(job_result.id), **job_celery_kwargs
666
+ )
667
+
668
+ # copy fields from eager result to job result
669
+ job_result.refresh_from_db()
670
+ for field in ["status", "result", "traceback", "worker"]:
671
+ setattr(job_result, field, getattr(eager_result, field, None))
672
+ job_result.date_done = timezone.now()
673
+ job_result.save()
674
+ else:
675
+ # Jobs queued inside of a transaction need to run after the transaction completes and the JobResult is saved to the database
676
+ transaction.on_commit(
677
+ lambda: job_model.job_task.apply_async(
678
+ args=job_args, kwargs=job_kwargs, task_id=str(job_result.id), **job_celery_kwargs
679
+ )
680
+ )
805
681
 
806
682
  return job_result
807
683
 
@@ -809,7 +685,7 @@ class JobResult(BaseModel, CustomFieldModel):
809
685
  self,
810
686
  message,
811
687
  obj=None,
812
- level_choice=LogLevelChoices.LOG_DEFAULT,
688
+ level_choice=LogLevelChoices.LOG_INFO,
813
689
  grouping="main",
814
690
  logger=None, # pylint: disable=redefined-outer-name
815
691
  ):
@@ -823,7 +699,7 @@ class JobResult(BaseModel, CustomFieldModel):
823
699
  logger (logging.logger): Optional logger to also output the message to
824
700
  """
825
701
  if level_choice not in LogLevelChoices.as_dict():
826
- raise Exception(f"Unknown logging level: {level_choice}")
702
+ raise ValueError(f"Unknown logging level: {level_choice}")
827
703
 
828
704
  message = sanitize(str(message))
829
705
 
@@ -849,12 +725,7 @@ class JobResult(BaseModel, CustomFieldModel):
849
725
  log.save(using=JOB_LOGS)
850
726
 
851
727
  if logger:
852
- if level_choice == LogLevelChoices.LOG_FAILURE:
853
- log_level = logging.ERROR
854
- elif level_choice == LogLevelChoices.LOG_WARNING:
855
- log_level = logging.WARNING
856
- else:
857
- log_level = logging.INFO
728
+ log_level = getattr(logging, level_choice.upper(), logging.INFO)
858
729
  logger.log(log_level, message)
859
730
 
860
731
 
@@ -909,9 +780,6 @@ class JobButton(BaseModel, ChangeLoggedModel, NotesMixin):
909
780
  def __str__(self):
910
781
  return self.name
911
782
 
912
- def get_absolute_url(self):
913
- return reverse("extras:jobbutton", kwargs={"pk": self.pk})
914
-
915
783
 
916
784
  class ScheduledJobs(models.Model):
917
785
  """Helper table for tracking updates to scheduled tasks.
@@ -977,6 +845,7 @@ class ScheduledJob(BaseModel):
977
845
  interval = models.CharField(choices=JobExecutionType, max_length=255)
978
846
  args = models.JSONField(blank=True, default=list, encoder=NautobotKombuJSONEncoder)
979
847
  kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
848
+ celery_kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
980
849
  queue = models.CharField(
981
850
  max_length=200,
982
851
  blank=True,
@@ -1062,13 +931,11 @@ class ScheduledJob(BaseModel):
1062
931
  def __str__(self):
1063
932
  return f"{self.name}: {self.interval}"
1064
933
 
1065
- def get_absolute_url(self):
1066
- return reverse("extras:scheduledjob", kwargs={"pk": self.pk})
934
+ # TODO: there's currently no natural key for ScheduledJob
935
+ natural_key_field_names = ["id"]
1067
936
 
1068
937
  def save(self, *args, **kwargs):
1069
938
  self.queue = self.queue or ""
1070
- # pass pk to worker task in kwargs, celery doesn't provide the full object to the worker
1071
- self.kwargs["scheduled_job_pk"] = self.pk
1072
939
  # make sure non-valid crontab doesn't get saved
1073
940
  if self.interval == JobExecutionType.TYPE_CUSTOM:
1074
941
  try: