nautobot 2.0.0a3__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 (780) hide show
  1. nautobot/apps/api.py +6 -8
  2. nautobot/apps/forms.py +0 -2
  3. nautobot/apps/ui.py +0 -8
  4. nautobot/circuits/api/serializers.py +9 -117
  5. nautobot/circuits/api/urls.py +1 -1
  6. nautobot/circuits/api/views.py +0 -1
  7. nautobot/circuits/forms.py +0 -65
  8. nautobot/circuits/migrations/0014_related_name_changes.py +1 -1
  9. nautobot/circuits/migrations/0016_tagsfield.py +34 -0
  10. nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
  11. nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
  12. nautobot/circuits/models.py +3 -87
  13. nautobot/circuits/navigation.py +14 -69
  14. nautobot/circuits/signals.py +0 -2
  15. nautobot/circuits/tables.py +39 -1
  16. nautobot/circuits/tests/integration/test_relationships.py +9 -9
  17. nautobot/circuits/tests/test_api.py +4 -8
  18. nautobot/circuits/tests/test_filters.py +10 -4
  19. nautobot/circuits/tests/test_models.py +5 -1
  20. nautobot/circuits/tests/test_views.py +27 -5
  21. nautobot/circuits/views.py +18 -10
  22. nautobot/core/api/__init__.py +8 -2
  23. nautobot/core/api/fields.py +15 -6
  24. nautobot/core/api/filter_backends.py +3 -2
  25. nautobot/core/api/metadata.py +237 -30
  26. nautobot/core/api/mixins.py +94 -0
  27. nautobot/core/api/pagination.py +4 -0
  28. nautobot/core/api/parsers.py +154 -0
  29. nautobot/core/api/renderers.py +153 -2
  30. nautobot/core/api/schema.py +46 -2
  31. nautobot/core/api/serializers.py +377 -35
  32. nautobot/core/api/urls.py +11 -3
  33. nautobot/core/api/utils.py +174 -2
  34. nautobot/core/api/versioning.py +32 -10
  35. nautobot/core/api/views.py +266 -72
  36. nautobot/core/apps/__init__.py +138 -220
  37. nautobot/core/celery/__init__.py +112 -41
  38. nautobot/core/celery/backends.py +19 -12
  39. nautobot/core/celery/control.py +46 -0
  40. nautobot/core/celery/encoders.py +53 -0
  41. nautobot/core/celery/log.py +38 -0
  42. nautobot/core/celery/schedulers.py +23 -4
  43. nautobot/core/celery/task.py +1 -16
  44. nautobot/core/checks.py +0 -27
  45. nautobot/core/choices.py +0 -113
  46. nautobot/core/{cli.py → cli/__init__.py} +1 -1
  47. nautobot/core/cli/__main__.py +3 -0
  48. nautobot/core/constants.py +0 -24
  49. nautobot/core/context_processors.py +12 -0
  50. nautobot/core/filters.py +2 -2
  51. nautobot/core/forms/__init__.py +0 -4
  52. nautobot/core/forms/fields.py +38 -65
  53. nautobot/core/forms/forms.py +4 -1
  54. nautobot/core/forms/utils.py +0 -52
  55. nautobot/core/graphql/schema.py +4 -27
  56. nautobot/core/jobs/__init__.py +75 -0
  57. nautobot/core/management/commands/build_ui.py +255 -0
  58. nautobot/core/management/commands/generate_test_data.py +3 -2
  59. nautobot/core/management/commands/post_upgrade.py +24 -24
  60. nautobot/core/models/__init__.py +26 -1
  61. nautobot/core/models/fields.py +24 -5
  62. nautobot/core/models/generics.py +2 -42
  63. nautobot/core/models/managers.py +5 -0
  64. nautobot/core/models/name_color_content_types.py +0 -14
  65. nautobot/core/models/tree_queries.py +14 -4
  66. nautobot/core/models/utils.py +5 -6
  67. nautobot/core/models/validators.py +17 -8
  68. nautobot/core/releases.py +8 -10
  69. nautobot/core/settings.py +80 -42
  70. nautobot/core/tables.py +5 -5
  71. nautobot/core/tasks.py +4 -7
  72. nautobot/core/templates/base.html +1 -49
  73. nautobot/core/templates/base_django.html +49 -0
  74. nautobot/core/templates/base_react.html +55 -0
  75. nautobot/core/templates/buttons/export.html +6 -4
  76. nautobot/core/templates/generic/object_bulk_create.html +10 -21
  77. nautobot/core/templates/generic/object_list.html +3 -1
  78. nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
  79. nautobot/core/templates/inc/footer.html +1 -0
  80. nautobot/core/templates/inc/javascript.html +0 -14
  81. nautobot/core/templates/inc/nav_menu.html +28 -33
  82. nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
  83. nautobot/core/templates/inc/relationships_table_rows.html +2 -2
  84. nautobot/core/templates/nautobot_config.py.j2 +8 -20
  85. nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
  86. nautobot/core/templates/rest_framework/api.html +8 -0
  87. nautobot/core/templatetags/buttons.py +32 -28
  88. nautobot/core/testing/__init__.py +47 -44
  89. nautobot/core/testing/api.py +362 -47
  90. nautobot/core/testing/filters.py +1 -1
  91. nautobot/core/testing/migrations.py +2 -0
  92. nautobot/core/testing/mixins.py +22 -9
  93. nautobot/core/testing/schema.py +2 -1
  94. nautobot/core/testing/views.py +21 -46
  95. nautobot/core/tests/integration/test_filters.py +17 -8
  96. nautobot/core/tests/integration/test_navbar.py +11 -34
  97. nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
  98. nautobot/core/tests/nautobot_config.py +2 -3
  99. nautobot/core/tests/test_api.py +290 -21
  100. nautobot/core/tests/test_checks.py +0 -7
  101. nautobot/core/tests/test_filters.py +107 -59
  102. nautobot/core/tests/test_forms.py +26 -92
  103. nautobot/core/tests/test_graphql.py +110 -77
  104. nautobot/core/tests/test_logging.py +4 -0
  105. nautobot/core/tests/test_managers.py +3 -1
  106. nautobot/core/tests/test_models.py +2 -0
  107. nautobot/core/tests/test_paginator.py +3 -1
  108. nautobot/core/tests/test_releases.py +12 -12
  109. nautobot/core/tests/test_templatetags_helpers.py +4 -4
  110. nautobot/core/tests/test_utils.py +32 -68
  111. nautobot/core/tests/test_views.py +12 -15
  112. nautobot/core/utils/data.py +17 -0
  113. nautobot/core/utils/deprecation.py +9 -6
  114. nautobot/core/utils/filtering.py +8 -3
  115. nautobot/core/utils/git.py +12 -4
  116. nautobot/core/utils/lookup.py +3 -1
  117. nautobot/core/utils/requests.py +1 -104
  118. nautobot/core/views/__init__.py +1 -0
  119. nautobot/core/views/generic.py +75 -110
  120. nautobot/core/views/mixins.py +52 -61
  121. nautobot/core/views/renderers.py +6 -7
  122. nautobot/core/views/utils.py +80 -0
  123. nautobot/dcim/api/serializers.py +160 -667
  124. nautobot/dcim/api/urls.py +1 -1
  125. nautobot/dcim/api/views.py +7 -44
  126. nautobot/dcim/choices.py +2 -0
  127. nautobot/dcim/filters/__init__.py +21 -0
  128. nautobot/dcim/form_mixins.py +1 -27
  129. nautobot/dcim/forms.py +19 -765
  130. nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -1
  131. nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -13
  132. nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -1
  133. nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -1
  134. nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
  135. nautobot/dcim/migrations/0035_related_name_changes.py +1 -1
  136. nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -1
  137. nautobot/dcim/migrations/0040_tagsfield.py +109 -0
  138. nautobot/dcim/migrations/{0040_ipam__namespaces.py → 0041_ipam__namespaces.py} +1 -1
  139. nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
  140. nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
  141. nautobot/dcim/models/cables.py +3 -33
  142. nautobot/dcim/models/device_component_templates.py +6 -0
  143. nautobot/dcim/models/device_components.py +12 -198
  144. nautobot/dcim/models/devices.py +30 -143
  145. nautobot/dcim/models/locations.py +3 -64
  146. nautobot/dcim/models/power.py +3 -50
  147. nautobot/dcim/models/racks.py +7 -84
  148. nautobot/dcim/navigation.py +141 -467
  149. nautobot/dcim/signals.py +0 -2
  150. nautobot/dcim/tables/locations.py +2 -2
  151. nautobot/dcim/tables/power.py +1 -2
  152. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
  153. nautobot/dcim/templates/dcim/devicetype.html +2 -2
  154. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
  155. nautobot/dcim/templates/dcim/location.html +16 -1
  156. nautobot/dcim/templates/dcim/locationtype.html +15 -0
  157. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
  158. nautobot/dcim/templates/dcim/rackgroup.html +0 -12
  159. nautobot/dcim/tests/test_api.py +166 -81
  160. nautobot/dcim/tests/test_cablepaths.py +41 -35
  161. nautobot/dcim/tests/test_filters.py +67 -23
  162. nautobot/dcim/tests/test_forms.py +5 -205
  163. nautobot/dcim/tests/test_graphql.py +7 -2
  164. nautobot/dcim/tests/test_migrations.py +6 -11
  165. nautobot/dcim/tests/test_models.py +182 -110
  166. nautobot/dcim/tests/test_natural_ordering.py +11 -8
  167. nautobot/dcim/tests/test_signals.py +6 -3
  168. nautobot/dcim/tests/test_views.py +197 -175
  169. nautobot/dcim/urls.py +11 -16
  170. nautobot/dcim/views.py +7 -134
  171. nautobot/docs/additional-features/caching.md +6 -87
  172. nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
  173. nautobot/docs/additional-features/jobs.md +177 -195
  174. nautobot/docs/administration/nautobot-server.md +6 -21
  175. nautobot/docs/administration/replicating-nautobot.md +0 -10
  176. nautobot/docs/configuration/optional-settings.md +32 -41
  177. nautobot/docs/configuration/required-settings.md +11 -52
  178. nautobot/docs/development/application-registry.md +2 -13
  179. nautobot/docs/development/extending-models.md +15 -17
  180. nautobot/docs/development/generic-views.md +0 -2
  181. nautobot/docs/development/getting-started.md +55 -5
  182. nautobot/docs/development/navigation-menu.md +22 -93
  183. nautobot/docs/development/react-ui.md +105 -0
  184. nautobot/docs/development/role-internals.md +1 -3
  185. nautobot/docs/development/style-guide.md +6 -4
  186. nautobot/docs/index.md +3 -2
  187. nautobot/docs/installation/migrating-from-netbox.md +11 -42
  188. nautobot/docs/installation/nautobot.md +1 -1
  189. nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  190. nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  191. nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  192. nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  193. nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
  194. nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  195. nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  196. nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  197. nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  198. nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  199. nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  200. nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  201. nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  202. nautobot/docs/installation/upgrading-from-nautobot-v1.md +170 -747
  203. nautobot/docs/models/dcim/device.md +3 -0
  204. nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
  205. nautobot/docs/models/extras/computedfield.md +4 -4
  206. nautobot/docs/models/extras/gitrepository.md +3 -0
  207. nautobot/docs/models/extras/job.md +1 -0
  208. nautobot/docs/models/extras/jobbutton.md +18 -13
  209. nautobot/docs/models/extras/jobhook.md +7 -4
  210. nautobot/docs/models/extras/jobresult.md +6 -2
  211. nautobot/docs/models/extras/relationship.md +2 -2
  212. nautobot/docs/models/extras/status.md +6 -19
  213. nautobot/docs/models/ipam/ipaddress.md +3 -0
  214. nautobot/docs/models/virtualization/virtualmachine.md +3 -0
  215. nautobot/docs/plugins/development.md +83 -21
  216. nautobot/docs/release-notes/version-1.5.md +53 -0
  217. nautobot/docs/release-notes/version-2.0.md +180 -0
  218. nautobot/docs/requirements.txt +1 -0
  219. nautobot/docs/rest-api/overview.md +384 -215
  220. nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
  221. nautobot/extras/admin.py +3 -5
  222. nautobot/extras/api/customfields.py +15 -39
  223. nautobot/extras/api/fields.py +0 -11
  224. nautobot/extras/api/mixins.py +45 -0
  225. nautobot/extras/api/relationships.py +63 -158
  226. nautobot/extras/api/serializers.py +165 -700
  227. nautobot/extras/api/urls.py +1 -1
  228. nautobot/extras/api/views.py +294 -280
  229. nautobot/extras/apps.py +4 -7
  230. nautobot/extras/choices.py +11 -9
  231. nautobot/extras/constants.py +9 -3
  232. nautobot/extras/datasources/__init__.py +2 -0
  233. nautobot/extras/datasources/git.py +135 -186
  234. nautobot/extras/datasources/registry.py +25 -35
  235. nautobot/extras/filters/__init__.py +20 -19
  236. nautobot/extras/filters/mixins.py +4 -4
  237. nautobot/extras/forms/forms.py +63 -127
  238. nautobot/extras/forms/mixins.py +23 -51
  239. nautobot/extras/health_checks.py +0 -33
  240. nautobot/extras/jobs.py +387 -565
  241. nautobot/extras/management/commands/runjob.py +24 -62
  242. nautobot/extras/managers.py +30 -7
  243. nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
  244. nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -1
  245. nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -1
  246. nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -7
  247. nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -32
  248. nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -1
  249. nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -1
  250. nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
  251. nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -1
  252. nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -2
  253. nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -1
  254. nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -1
  255. nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
  256. nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -1
  257. nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
  258. nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -9
  259. nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -1
  260. nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
  261. nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
  262. 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
  263. nautobot/extras/migrations/{0077_remove_slug.py → 0078_remove_slug.py} +1 -5
  264. nautobot/extras/migrations/0079_tagsfield.py +28 -0
  265. nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
  266. nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
  267. nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
  268. nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
  269. nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
  270. nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
  271. nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
  272. nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
  273. nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
  274. nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
  275. nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
  276. nautobot/extras/models/__init__.py +2 -3
  277. nautobot/extras/models/change_logging.py +0 -36
  278. nautobot/extras/models/customfields.py +39 -33
  279. nautobot/extras/models/datasources.py +48 -50
  280. nautobot/extras/models/groups.py +5 -6
  281. nautobot/extras/models/jobs.py +189 -321
  282. nautobot/extras/models/mixins.py +0 -71
  283. nautobot/extras/models/models.py +0 -19
  284. nautobot/extras/models/relationships.py +19 -13
  285. nautobot/extras/models/roles.py +0 -34
  286. nautobot/extras/models/secrets.py +2 -26
  287. nautobot/extras/models/statuses.py +6 -5
  288. nautobot/extras/models/tags.py +2 -17
  289. nautobot/extras/navigation.py +89 -307
  290. nautobot/extras/plugins/__init__.py +3 -120
  291. nautobot/extras/plugins/utils.py +0 -3
  292. nautobot/extras/plugins/validators.py +5 -4
  293. nautobot/extras/plugins/views.py +16 -3
  294. nautobot/extras/querysets.py +1 -7
  295. nautobot/extras/registry.py +3 -0
  296. nautobot/extras/signals.py +26 -60
  297. nautobot/extras/tables.py +34 -40
  298. nautobot/extras/tasks.py +0 -12
  299. nautobot/extras/templates/extras/configcontext.html +1 -1
  300. nautobot/extras/templates/extras/configcontextschema.html +16 -1
  301. nautobot/extras/templates/extras/customfield.html +0 -13
  302. nautobot/extras/templates/extras/gitrepository.html +3 -3
  303. nautobot/extras/templates/extras/inc/jobresult.html +10 -0
  304. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  305. nautobot/extras/templates/extras/job.html +35 -25
  306. nautobot/extras/templates/extras/job_approval_request.html +15 -30
  307. nautobot/extras/templates/extras/job_detail.html +13 -31
  308. nautobot/extras/templates/extras/job_edit.html +15 -17
  309. nautobot/extras/templates/extras/jobresult.html +24 -6
  310. nautobot/extras/templates/extras/scheduledjob.html +2 -2
  311. nautobot/extras/templates/extras/secret.html +28 -0
  312. nautobot/extras/templatetags/job_buttons.py +1 -0
  313. nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
  314. nautobot/extras/test_jobs/atomic_transaction.py +53 -0
  315. nautobot/extras/test_jobs/dry_run.py +29 -0
  316. nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
  317. nautobot/extras/test_jobs/duplicate_name2.py +9 -0
  318. nautobot/extras/test_jobs/fail.py +23 -0
  319. nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
  320. nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
  321. nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
  322. nautobot/extras/test_jobs/file_upload_pass.py +25 -0
  323. nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
  324. nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
  325. nautobot/extras/test_jobs/job_button_receiver.py +28 -0
  326. nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
  327. nautobot/extras/test_jobs/job_variables.py +88 -0
  328. nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
  329. nautobot/extras/test_jobs/log_redaction.py +20 -0
  330. nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
  331. nautobot/extras/test_jobs/modify_db.py +25 -0
  332. nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
  333. nautobot/extras/test_jobs/object_var_optional.py +21 -0
  334. nautobot/extras/test_jobs/object_var_required.py +21 -0
  335. nautobot/extras/test_jobs/object_vars.py +26 -0
  336. nautobot/extras/test_jobs/pass.py +25 -0
  337. nautobot/extras/test_jobs/profiling.py +32 -0
  338. nautobot/extras/test_jobs/read_only_job.py +15 -0
  339. nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
  340. 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
  341. nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
  342. nautobot/extras/tests/integration/test_computedfields.py +1 -1
  343. nautobot/extras/tests/integration/test_configcontextschema.py +5 -3
  344. nautobot/extras/tests/integration/test_customfields.py +4 -2
  345. nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
  346. nautobot/extras/tests/integration/test_jobs.py +25 -27
  347. nautobot/extras/tests/integration/test_notes.py +8 -4
  348. nautobot/extras/tests/integration/test_relationships.py +2 -2
  349. nautobot/extras/tests/test_api.py +649 -642
  350. nautobot/extras/tests/test_changelog.py +3 -3
  351. nautobot/extras/tests/test_context_managers.py +5 -3
  352. nautobot/extras/tests/test_customfields.py +92 -50
  353. nautobot/extras/tests/test_datasources.py +189 -112
  354. nautobot/extras/tests/test_dynamicgroups.py +7 -8
  355. nautobot/extras/tests/test_filters.py +137 -89
  356. nautobot/extras/tests/test_forms.py +73 -75
  357. nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
  358. nautobot/extras/tests/test_jobs.py +262 -263
  359. nautobot/extras/tests/test_migrations.py +4 -3
  360. nautobot/extras/tests/test_models.py +116 -161
  361. nautobot/extras/tests/test_plugins.py +38 -60
  362. nautobot/extras/tests/test_relationships.py +167 -120
  363. nautobot/extras/tests/test_tags.py +6 -11
  364. nautobot/extras/tests/test_utils.py +31 -1
  365. nautobot/extras/tests/test_views.py +201 -145
  366. nautobot/extras/tests/test_webhooks.py +6 -2
  367. nautobot/extras/urls.py +42 -42
  368. nautobot/extras/utils.py +137 -163
  369. nautobot/extras/views.py +78 -152
  370. nautobot/ipam/api/fields.py +17 -0
  371. nautobot/ipam/api/serializers.py +58 -164
  372. nautobot/ipam/api/urls.py +1 -1
  373. nautobot/ipam/api/views.py +3 -2
  374. nautobot/ipam/apps.py +1 -2
  375. nautobot/ipam/filters.py +1 -10
  376. nautobot/ipam/forms.py +4 -177
  377. nautobot/ipam/lookups.py +1 -0
  378. nautobot/ipam/management/commands/__init__.py +0 -0
  379. nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
  380. nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -1
  381. nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -38
  382. nautobot/ipam/migrations/0020_related_name_changes.py +1 -1
  383. nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +2 -2
  384. nautobot/ipam/migrations/0028_tagsfield.py +44 -0
  385. nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
  386. nautobot/ipam/migrations/{0028_ipam__namespaces.py → 0030_ipam__namespaces.py} +77 -28
  387. nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
  388. nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
  389. nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
  390. nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
  391. nautobot/ipam/models.py +100 -236
  392. nautobot/ipam/navigation.py +36 -181
  393. nautobot/ipam/querysets.py +20 -25
  394. nautobot/ipam/signals.py +49 -6
  395. nautobot/ipam/tables.py +10 -3
  396. nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
  397. nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
  398. nautobot/ipam/templates/ipam/namespace_retrieve.html +17 -4
  399. nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
  400. nautobot/ipam/templates/ipam/prefix.html +1 -1
  401. nautobot/ipam/templates/ipam/vlangroup.html +0 -13
  402. nautobot/ipam/templates/ipam/vrf_edit.html +6 -0
  403. nautobot/ipam/tests/integration/test_prefixes.py +3 -26
  404. nautobot/ipam/tests/test_api.py +22 -19
  405. nautobot/ipam/tests/test_filters.py +59 -23
  406. nautobot/ipam/tests/test_migrations.py +6 -10
  407. nautobot/ipam/tests/test_models.py +323 -198
  408. nautobot/ipam/tests/test_ordering.py +2 -2
  409. nautobot/ipam/tests/test_querysets.py +44 -24
  410. nautobot/ipam/tests/test_views.py +73 -26
  411. nautobot/ipam/urls.py +16 -0
  412. nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
  413. nautobot/ipam/utils/migrations.py +713 -0
  414. nautobot/ipam/views.py +137 -20
  415. nautobot/project-static/docs/404.html +1178 -10
  416. nautobot/project-static/docs/additional-features/caching.html +1224 -159
  417. nautobot/project-static/docs/additional-features/change-logging.html +1180 -12
  418. nautobot/project-static/docs/additional-features/config-contexts.html +1180 -12
  419. nautobot/project-static/docs/additional-features/graphql.html +1179 -11
  420. nautobot/project-static/docs/additional-features/healthcheck.html +1180 -12
  421. nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1184 -12
  422. nautobot/project-static/docs/additional-features/jobs.html +1514 -328
  423. nautobot/project-static/docs/additional-features/napalm.html +1180 -12
  424. nautobot/project-static/docs/additional-features/prometheus-metrics.html +1180 -12
  425. nautobot/project-static/docs/additional-features/template-filters.html +1180 -12
  426. nautobot/project-static/docs/administration/celery-queues.html +1178 -10
  427. nautobot/project-static/docs/administration/nautobot-server.html +1451 -304
  428. nautobot/project-static/docs/administration/nautobot-shell.html +1178 -10
  429. nautobot/project-static/docs/administration/permissions.html +1178 -10
  430. nautobot/project-static/docs/administration/replicating-nautobot.html +1262 -113
  431. nautobot/project-static/docs/apps/index.html +1178 -10
  432. nautobot/project-static/docs/apps/nautobot-apps.html +1178 -10
  433. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1580 -426
  434. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1178 -10
  435. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3481 -1838
  436. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1178 -10
  437. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1178 -10
  438. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1185 -11
  439. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1719 -551
  440. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2062 -930
  441. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1946 -659
  442. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1180 -12
  443. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1189 -21
  444. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9283 -6218
  445. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2734 -2122
  446. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1178 -10
  447. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2337 -1300
  448. nautobot/project-static/docs/configuration/authentication/ldap.html +1178 -10
  449. nautobot/project-static/docs/configuration/authentication/remote.html +1178 -10
  450. nautobot/project-static/docs/configuration/authentication/sso.html +1178 -10
  451. nautobot/project-static/docs/configuration/index.html +1178 -10
  452. nautobot/project-static/docs/configuration/optional-settings.html +1311 -160
  453. nautobot/project-static/docs/configuration/required-settings.html +1312 -211
  454. nautobot/project-static/docs/core-functionality/circuits.html +1178 -10
  455. nautobot/project-static/docs/core-functionality/device-types.html +1178 -10
  456. nautobot/project-static/docs/core-functionality/devices.html +1182 -10
  457. nautobot/project-static/docs/core-functionality/ipam.html +1182 -10
  458. nautobot/project-static/docs/core-functionality/power.html +1178 -10
  459. nautobot/project-static/docs/core-functionality/secrets.html +1178 -10
  460. nautobot/project-static/docs/core-functionality/services.html +1178 -10
  461. nautobot/project-static/docs/core-functionality/sites-and-racks.html +1178 -10
  462. nautobot/project-static/docs/core-functionality/tenancy.html +1178 -10
  463. nautobot/project-static/docs/core-functionality/virtualization.html +1182 -10
  464. nautobot/project-static/docs/core-functionality/vlans.html +1179 -11
  465. nautobot/project-static/docs/development/application-registry.html +1190 -42
  466. nautobot/project-static/docs/development/best-practices.html +1178 -10
  467. nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1178 -10
  468. nautobot/project-static/docs/development/extending-models.html +1238 -83
  469. nautobot/project-static/docs/development/generic-views.html +1180 -14
  470. nautobot/project-static/docs/development/getting-started.html +1365 -90
  471. nautobot/project-static/docs/development/homepage.html +1178 -10
  472. nautobot/project-static/docs/development/index.html +1178 -10
  473. nautobot/project-static/docs/development/model-features.html +1178 -10
  474. nautobot/project-static/docs/development/natural-keys.html +1178 -10
  475. nautobot/project-static/docs/development/navigation-menu.html +1215 -125
  476. nautobot/project-static/docs/development/react-ui.html +4199 -0
  477. nautobot/project-static/docs/development/release-checklist.html +1178 -10
  478. nautobot/project-static/docs/development/role-internals.html +1179 -12
  479. nautobot/project-static/docs/development/style-guide.html +1188 -19
  480. nautobot/project-static/docs/development/templates.html +1178 -10
  481. nautobot/project-static/docs/development/testing.html +1178 -10
  482. nautobot/project-static/docs/development/user-preferences.html +1178 -10
  483. nautobot/project-static/docs/docker/index.html +1178 -10
  484. nautobot/project-static/docs/index.html +1183 -12
  485. nautobot/project-static/docs/installation/centos.html +1178 -10
  486. nautobot/project-static/docs/installation/external-authentication.html +1178 -10
  487. nautobot/project-static/docs/installation/http-server.html +1178 -10
  488. nautobot/project-static/docs/installation/index.html +1178 -10
  489. nautobot/project-static/docs/installation/migrating-from-netbox.html +1305 -189
  490. nautobot/project-static/docs/installation/migrating-from-postgresql.html +1178 -10
  491. nautobot/project-static/docs/installation/nautobot.html +1179 -11
  492. nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1178 -10
  493. nautobot/project-static/docs/installation/selinux-troubleshooting.html +1178 -10
  494. nautobot/project-static/docs/installation/services.html +1178 -10
  495. nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  496. nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  497. nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  498. nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  499. nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
  500. nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  501. nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  502. nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  503. nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  504. nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  505. nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  506. nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  507. nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  508. nautobot/project-static/docs/installation/ubuntu.html +1178 -10
  509. nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +3823 -2152
  510. nautobot/project-static/docs/installation/upgrading.html +1178 -10
  511. nautobot/project-static/docs/models/circuits/circuit.html +1293 -103
  512. nautobot/project-static/docs/models/circuits/circuittermination.html +1293 -103
  513. nautobot/project-static/docs/models/circuits/circuittype.html +1293 -103
  514. nautobot/project-static/docs/models/circuits/provider.html +1293 -103
  515. nautobot/project-static/docs/models/circuits/providernetwork.html +1293 -103
  516. nautobot/project-static/docs/models/dcim/cable.html +1324 -103
  517. nautobot/project-static/docs/models/dcim/consoleport.html +1293 -103
  518. nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1293 -103
  519. nautobot/project-static/docs/models/dcim/consoleserverport.html +1293 -103
  520. nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1293 -103
  521. nautobot/project-static/docs/models/dcim/device.html +1326 -132
  522. nautobot/project-static/docs/models/dcim/devicebay.html +1293 -103
  523. nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1293 -103
  524. nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1379 -97
  525. nautobot/project-static/docs/models/dcim/devicetype.html +1293 -103
  526. nautobot/project-static/docs/models/dcim/frontport.html +1293 -103
  527. nautobot/project-static/docs/models/dcim/frontporttemplate.html +1293 -103
  528. nautobot/project-static/docs/models/dcim/interface.html +1293 -103
  529. nautobot/project-static/docs/models/dcim/interfacetemplate.html +1293 -103
  530. nautobot/project-static/docs/models/dcim/inventoryitem.html +1293 -103
  531. nautobot/project-static/docs/models/dcim/location.html +1293 -103
  532. nautobot/project-static/docs/models/dcim/locationtype.html +1293 -103
  533. nautobot/project-static/docs/models/dcim/manufacturer.html +1292 -102
  534. nautobot/project-static/docs/models/dcim/platform.html +1272 -82
  535. nautobot/project-static/docs/models/dcim/powerfeed.html +1270 -80
  536. nautobot/project-static/docs/models/dcim/poweroutlet.html +1272 -82
  537. nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1272 -82
  538. nautobot/project-static/docs/models/dcim/powerpanel.html +1270 -80
  539. nautobot/project-static/docs/models/dcim/powerport.html +1272 -82
  540. nautobot/project-static/docs/models/dcim/powerporttemplate.html +1272 -82
  541. nautobot/project-static/docs/models/dcim/rack.html +1272 -82
  542. nautobot/project-static/docs/models/dcim/rackgroup.html +1272 -82
  543. nautobot/project-static/docs/models/dcim/rackreservation.html +1272 -82
  544. nautobot/project-static/docs/models/dcim/rearport.html +1286 -96
  545. nautobot/project-static/docs/models/dcim/rearporttemplate.html +1286 -96
  546. nautobot/project-static/docs/models/dcim/region.html +1178 -10
  547. nautobot/project-static/docs/models/dcim/site.html +1178 -10
  548. nautobot/project-static/docs/models/dcim/virtualchassis.html +1284 -94
  549. nautobot/project-static/docs/models/extras/computedfield.html +1184 -16
  550. nautobot/project-static/docs/models/extras/configcontext.html +1314 -86
  551. nautobot/project-static/docs/models/extras/configcontextschema.html +1276 -86
  552. nautobot/project-static/docs/models/extras/customfield.html +1180 -12
  553. nautobot/project-static/docs/models/extras/customlink.html +1180 -12
  554. nautobot/project-static/docs/models/extras/dynamicgroup.html +1180 -12
  555. nautobot/project-static/docs/models/extras/exporttemplate.html +1180 -12
  556. nautobot/project-static/docs/models/extras/gitrepository.html +1184 -12
  557. nautobot/project-static/docs/models/extras/graphqlquery.html +1321 -86
  558. nautobot/project-static/docs/models/extras/imageattachment.html +1276 -86
  559. nautobot/project-static/docs/models/extras/job.html +1277 -86
  560. nautobot/project-static/docs/models/extras/jobbutton.html +1201 -29
  561. nautobot/project-static/docs/models/extras/jobhook.html +1188 -16
  562. nautobot/project-static/docs/models/extras/joblogentry.html +1274 -84
  563. nautobot/project-static/docs/models/extras/jobresult.html +1364 -169
  564. nautobot/project-static/docs/models/extras/note.html +1180 -12
  565. nautobot/project-static/docs/models/extras/relationship.html +1182 -14
  566. nautobot/project-static/docs/models/extras/role.html +1320 -86
  567. nautobot/project-static/docs/models/extras/secret.html +1314 -86
  568. nautobot/project-static/docs/models/extras/secretsgroup.html +1276 -86
  569. nautobot/project-static/docs/models/extras/status.html +1188 -59
  570. nautobot/project-static/docs/models/extras/tag.html +1180 -12
  571. nautobot/project-static/docs/models/extras/webhook.html +1180 -12
  572. nautobot/project-static/docs/models/ipam/ipaddress.html +1327 -102
  573. nautobot/project-static/docs/models/ipam/prefix.html +1276 -86
  574. nautobot/project-static/docs/models/ipam/rir.html +1276 -86
  575. nautobot/project-static/docs/models/ipam/routetarget.html +1276 -86
  576. nautobot/project-static/docs/models/ipam/service.html +1276 -86
  577. nautobot/project-static/docs/models/ipam/vlan.html +1276 -86
  578. nautobot/project-static/docs/models/ipam/vlangroup.html +1276 -86
  579. nautobot/project-static/docs/models/ipam/vrf.html +1276 -86
  580. nautobot/project-static/docs/models/tenancy/tenant.html +1276 -86
  581. nautobot/project-static/docs/models/tenancy/tenantgroup.html +1276 -86
  582. nautobot/project-static/docs/models/users/objectpermission.html +1314 -86
  583. nautobot/project-static/docs/models/users/token.html +1276 -86
  584. nautobot/project-static/docs/models/virtualization/cluster.html +1276 -86
  585. nautobot/project-static/docs/models/virtualization/clustergroup.html +1276 -86
  586. nautobot/project-static/docs/models/virtualization/clustertype.html +1276 -86
  587. nautobot/project-static/docs/models/virtualization/virtualmachine.html +1321 -127
  588. nautobot/project-static/docs/models/virtualization/vminterface.html +1276 -86
  589. nautobot/project-static/docs/objects.inv +0 -0
  590. nautobot/project-static/docs/plugins/development.html +1726 -495
  591. nautobot/project-static/docs/plugins/index.html +1178 -10
  592. nautobot/project-static/docs/plugins/porting-from-netbox.html +1178 -10
  593. nautobot/project-static/docs/release-notes/index.html +1178 -10
  594. nautobot/project-static/docs/release-notes/version-1.0.html +1178 -10
  595. nautobot/project-static/docs/release-notes/version-1.1.html +1178 -10
  596. nautobot/project-static/docs/release-notes/version-1.2.html +1178 -10
  597. nautobot/project-static/docs/release-notes/version-1.3.html +1178 -10
  598. nautobot/project-static/docs/release-notes/version-1.4.html +1178 -10
  599. nautobot/project-static/docs/release-notes/version-1.5.html +1608 -225
  600. nautobot/project-static/docs/release-notes/version-2.0.html +1547 -47
  601. nautobot/project-static/docs/requirements.txt +1 -0
  602. nautobot/project-static/docs/rest-api/authentication.html +1179 -11
  603. nautobot/project-static/docs/rest-api/filtering.html +1178 -10
  604. nautobot/project-static/docs/rest-api/overview.html +1841 -446
  605. nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
  606. nautobot/project-static/docs/search/search_index.json +1 -1
  607. nautobot/project-static/docs/sitemap.xml +197 -187
  608. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  609. nautobot/project-static/docs/user-guides/custom-fields.html +1178 -10
  610. nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1178 -10
  611. nautobot/project-static/docs/user-guides/getting-started/index.html +1178 -10
  612. nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1178 -10
  613. nautobot/project-static/docs/user-guides/getting-started/ipam.html +1178 -10
  614. nautobot/project-static/docs/user-guides/getting-started/platforms.html +1178 -10
  615. nautobot/project-static/docs/user-guides/getting-started/regions.html +1178 -10
  616. nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1178 -10
  617. nautobot/project-static/docs/user-guides/getting-started/tenants.html +1178 -10
  618. nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1178 -10
  619. nautobot/project-static/docs/user-guides/git-data-source.html +1178 -10
  620. nautobot/project-static/docs/user-guides/graphql.html +1178 -10
  621. nautobot/project-static/docs/user-guides/relationships.html +1178 -10
  622. nautobot/project-static/docs/user-guides/s3-django-storage.html +1178 -10
  623. nautobot/project-static/js/forms.js +16 -9
  624. nautobot/project-static/js/theme.js +5 -0
  625. nautobot/tenancy/api/serializers.py +4 -32
  626. nautobot/tenancy/api/urls.py +1 -1
  627. nautobot/tenancy/forms.py +0 -28
  628. nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
  629. nautobot/tenancy/models.py +0 -25
  630. nautobot/tenancy/navigation.py +6 -39
  631. nautobot/tenancy/templates/tenancy/tenant.html +12 -12
  632. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  633. nautobot/tenancy/tests/test_api.py +1 -3
  634. nautobot/tenancy/tests/test_filters.py +10 -5
  635. nautobot/tenancy/views.py +0 -2
  636. nautobot/ui/.eslintignore +6 -0
  637. nautobot/ui/.gitignore +10 -0
  638. nautobot/ui/.prettierignore +9 -0
  639. nautobot/ui/.prettierrc +4 -0
  640. nautobot/ui/README.md +33 -0
  641. nautobot/ui/app_imports.js.j2 +7 -0
  642. nautobot/ui/craco.config.js +46 -0
  643. nautobot/ui/jsconfig-base.json +11 -0
  644. nautobot/ui/jsconfig.json +5 -0
  645. nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
  646. nautobot/ui/package-lock.json +21451 -0
  647. nautobot/ui/package.json +70 -0
  648. nautobot/ui/public/index.html +47 -0
  649. nautobot/ui/public/logo192.png +0 -0
  650. nautobot/ui/public/logo512.png +0 -0
  651. nautobot/ui/public/manifest.json +25 -0
  652. nautobot/ui/public/nautobot_logo.svg +131 -0
  653. nautobot/ui/public/robots.txt +3 -0
  654. nautobot/ui/src/App.js +71 -0
  655. nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
  656. nautobot/ui/src/components/AppTab.js +40 -0
  657. nautobot/ui/src/components/Apps.js +60 -0
  658. nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
  659. nautobot/ui/src/components/HomePanel.js +58 -0
  660. nautobot/ui/src/components/JobHistoryTable.js +78 -0
  661. nautobot/ui/src/components/Layout.js +53 -0
  662. nautobot/ui/src/components/LoadingWidget.js +25 -0
  663. nautobot/ui/src/components/Navbar.js +116 -0
  664. nautobot/ui/src/components/NotificationPopover.js +27 -0
  665. nautobot/ui/src/components/ObjectListTable.js +209 -0
  666. nautobot/ui/src/components/ReferenceDataTag.js +35 -0
  667. nautobot/ui/src/components/RouterButton.js +10 -0
  668. nautobot/ui/src/components/RouterLink.js +10 -0
  669. nautobot/ui/src/components/SidebarNav.js +147 -0
  670. nautobot/ui/src/components/Table.js +48 -0
  671. nautobot/ui/src/components/TableItem.js +71 -0
  672. nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
  673. nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
  674. nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
  675. nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
  676. nautobot/ui/src/components/__tests__/Table.test.js +36 -0
  677. nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
  678. nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
  679. nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
  680. nautobot/ui/src/components/pagination.js +93 -0
  681. nautobot/ui/src/components/paginator.js +79 -0
  682. nautobot/ui/src/components/paginator_form.js +43 -0
  683. nautobot/ui/src/components/usePagination.js +57 -0
  684. nautobot/ui/src/constants/apiPath.js +10 -0
  685. nautobot/ui/src/constants/icons.js +15 -0
  686. nautobot/ui/src/constants/size.js +15 -0
  687. nautobot/ui/src/index.js +65 -0
  688. nautobot/ui/src/reportWebVitals.js +15 -0
  689. nautobot/ui/src/router.js +77 -0
  690. nautobot/ui/src/utils/api.js +131 -0
  691. nautobot/ui/src/utils/app-import.js +15 -0
  692. nautobot/ui/src/utils/color.js +15 -0
  693. nautobot/ui/src/utils/date.js +14 -0
  694. nautobot/ui/src/utils/index.js +15 -0
  695. nautobot/ui/src/utils/navigation.js +32 -0
  696. nautobot/ui/src/utils/session.js +64 -0
  697. nautobot/ui/src/utils/store.js +242 -0
  698. nautobot/ui/src/utils/string.js +6 -0
  699. nautobot/ui/src/utils/url.js +4 -0
  700. nautobot/ui/src/views/Home.js +138 -0
  701. nautobot/ui/src/views/InstalledApps.js +80 -0
  702. nautobot/ui/src/views/Login.js +48 -0
  703. nautobot/ui/src/views/Logout.js +20 -0
  704. nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
  705. nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
  706. nautobot/ui/src/views/__tests__/Login.test.js +15 -0
  707. nautobot/ui/src/views/generic/GenericView.js +142 -0
  708. nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
  709. nautobot/ui/src/views/generic/ObjectList.js +127 -0
  710. nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
  711. nautobot/users/admin.py +1 -1
  712. nautobot/users/api/serializers.py +51 -61
  713. nautobot/users/api/urls.py +1 -1
  714. nautobot/users/api/views.py +53 -2
  715. nautobot/users/tests/test_api.py +110 -25
  716. nautobot/virtualization/api/serializers.py +18 -130
  717. nautobot/virtualization/api/urls.py +1 -1
  718. nautobot/virtualization/api/views.py +1 -22
  719. nautobot/virtualization/forms.py +13 -99
  720. nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -1
  721. nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -11
  722. nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -1
  723. nautobot/virtualization/migrations/0018_related_name_changes.py +1 -1
  724. nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
  725. nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
  726. nautobot/virtualization/migrations/{0021_ipam__namespaces.py → 0023_ipam__namespaces.py} +2 -2
  727. nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
  728. nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
  729. nautobot/virtualization/models.py +31 -123
  730. nautobot/virtualization/navigation.py +18 -99
  731. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -1
  732. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
  733. nautobot/virtualization/tests/test_api.py +25 -26
  734. nautobot/virtualization/tests/test_filters.py +41 -15
  735. nautobot/virtualization/tests/test_models.py +31 -7
  736. nautobot/virtualization/tests/test_views.py +42 -25
  737. nautobot/virtualization/views.py +7 -6
  738. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +3 -7
  739. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +744 -602
  740. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
  741. nautobot/circuits/api/nested_serializers.py +0 -69
  742. nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
  743. nautobot/dcim/api/nested_serializers.py +0 -356
  744. nautobot/dcim/templates/dcim/device_import.html +0 -5
  745. nautobot/dcim/templates/dcim/device_import_child.html +0 -5
  746. nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
  747. nautobot/extras/api/nested_serializers.py +0 -353
  748. nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -41
  749. nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -46
  750. nautobot/extras/reports.py +0 -60
  751. nautobot/extras/scripts.py +0 -72
  752. nautobot/extras/tests/example_jobs/script_variables.py +0 -67
  753. nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
  754. nautobot/extras/tests/example_jobs/test_fail.py +0 -16
  755. nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
  756. nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
  757. nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
  758. nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
  759. nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
  760. nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
  761. nautobot/extras/tests/example_jobs/test_modify_db.py +0 -18
  762. nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
  763. nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
  764. nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
  765. nautobot/extras/tests/example_jobs/test_pass.py +0 -19
  766. nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
  767. nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
  768. nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
  769. nautobot/ipam/api/nested_serializers.py +0 -159
  770. nautobot/ipam/migrations/0029_ipam__prefix__add_parent.py +0 -31
  771. nautobot/ipam/migrations/0030_ipam__prefix__data_migration.py +0 -13
  772. nautobot/ipam/migrations/0031_ipam__ipaddress__add_parent.py +0 -41
  773. nautobot/ipam/migrations/0032_ipam__ipaddress__data_migration.py +0 -11
  774. nautobot/tenancy/api/nested_serializers.py +0 -31
  775. nautobot/users/api/nested_serializers.py +0 -67
  776. nautobot/virtualization/api/nested_serializers.py +0 -65
  777. /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
  778. /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
  779. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
  780. {nautobot-2.0.0a3.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):
@@ -423,9 +343,6 @@ class JobHook(OrganizationalModel):
423
343
  if not self.type_create and not self.type_delete and not self.type_update:
424
344
  raise ValidationError("You must select at least one type: create, update, and/or delete.")
425
345
 
426
- def get_absolute_url(self):
427
- return reverse("extras:jobhook", kwargs={"pk": self.pk})
428
-
429
346
  @classmethod
430
347
  def check_for_conflicts(
431
348
  cls, instance=None, content_types=None, job=None, type_create=None, type_update=None, type_delete=None
@@ -484,7 +401,7 @@ class JobLogEntry(BaseModel):
484
401
 
485
402
  job_result = models.ForeignKey(to="extras.JobResult", on_delete=models.CASCADE, related_name="job_log_entries")
486
403
  log_level = models.CharField(
487
- 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
488
405
  )
489
406
  grouping = models.CharField(max_length=JOB_LOG_MAX_GROUPING_LENGTH, default="main")
490
407
  message = models.TextField(blank=True)
@@ -496,8 +413,6 @@ class JobLogEntry(BaseModel):
496
413
  log_object = models.CharField(max_length=JOB_LOG_MAX_LOG_OBJECT_LENGTH, blank=True, default="")
497
414
  absolute_url = models.CharField(max_length=JOB_LOG_MAX_ABSOLUTE_URL_LENGTH, blank=True, default="")
498
415
 
499
- csv_headers = ["created", "grouping", "log_level", "log_object", "message"]
500
-
501
416
  def __str__(self):
502
417
  return self.message
503
418
 
@@ -506,10 +421,6 @@ class JobLogEntry(BaseModel):
506
421
  get_latest_by = "created"
507
422
  verbose_name_plural = "job log entries"
508
423
 
509
- def to_csv(self):
510
- """Indicates model fields to return as csv."""
511
- return (str(self.created), self.grouping, self.log_level, self.log_object, self.message)
512
-
513
424
 
514
425
  #
515
426
  # Job results
@@ -538,18 +449,8 @@ class JobResult(BaseModel, CustomFieldModel):
538
449
  db_index=True,
539
450
  help_text="Registered name of the Celery task for this job. Internal use only.",
540
451
  )
541
- obj_type = models.ForeignKey(
542
- to=ContentType,
543
- related_name="job_results",
544
- verbose_name="Object types",
545
- limit_choices_to=FeatureQuery("job_results"),
546
- help_text="The object type to which this job result applies",
547
- on_delete=models.CASCADE,
548
- null=True,
549
- blank=True,
550
- )
551
- date_created = models.DateTimeField(auto_now_add=True)
552
- 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)
553
454
  user = models.ForeignKey(
554
455
  to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name="+", blank=True, null=True
555
456
  )
@@ -563,23 +464,16 @@ class JobResult(BaseModel, CustomFieldModel):
563
464
  data = models.JSONField(encoder=DjangoJSONEncoder, null=True, blank=True)
564
465
  """
565
466
  Although "data" is technically an unstructured field, we have a standard structure that we try to adhere to.
566
-
567
467
  This structure is created loosely as a superset of the formats used by Scripts and Reports in NetBox 2.10.
568
-
569
468
  Log Messages now go to their own object, the JobLogEntry.
570
-
571
469
  data = {
572
470
  "output": <optional string, such as captured stdout/stderr>,
573
471
  }
574
472
  """
575
- periodic_task_name = models.CharField(
576
- null=True,
577
- max_length=255,
578
- help_text="Registered name of the Celery periodic task for this job. Internal use only.",
579
- )
580
473
  worker = models.CharField(max_length=100, default=None, null=True)
581
- task_args = models.JSONField(blank=True, null=True, encoder=NautobotKombuJSONEncoder)
582
- 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)
583
477
  # TODO(jathan): This field is currently unused for Jobs, but we should coerce it to a JSONField
584
478
  # and set a contract that anything returned from a Job task MUST be JSON. In DCR core it is
585
479
  # expected to be encoded/decoded using `content_type` and `content_encoding` which we have
@@ -595,8 +489,6 @@ class JobResult(BaseModel, CustomFieldModel):
595
489
  meta = models.JSONField(null=True, default=None, editable=False)
596
490
  scheduled_job = models.ForeignKey(to="extras.ScheduledJob", on_delete=models.SET_NULL, null=True, blank=True)
597
491
 
598
- task_id = models.UUIDField(unique=True)
599
-
600
492
  objects = JobResultManager()
601
493
 
602
494
  def __init__(self, *args, **kwargs):
@@ -606,14 +498,34 @@ class JobResult(BaseModel, CustomFieldModel):
606
498
  class Meta:
607
499
  ordering = ["-date_created"]
608
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"]
609
521
 
610
522
  def __str__(self):
611
- return str(self.task_id)
523
+ return f"{self.name} started at {self.date_created} ({self.status})"
612
524
 
613
525
  def as_dict(self):
614
526
  """This is required by the django-celery-results DB backend."""
615
527
  return {
616
- "task_id": self.task_id,
528
+ "id": self.id,
617
529
  "task_name": self.task_name,
618
530
  "task_args": self.task_args,
619
531
  "task_kwargs": self.task_kwargs,
@@ -635,92 +547,8 @@ class JobResult(BaseModel, CustomFieldModel):
635
547
 
636
548
  return f"{int(minutes)} minutes, {seconds:.2f} seconds"
637
549
 
638
- @property
639
- def related_object(self):
640
- """Get the related object, if any, identified by the `obj_type`, `name`, and/or `task_id` fields.
641
-
642
- If `obj_type` is extras.Job, then the `name` is used to look up an extras.jobs.Job subclass based on the
643
- `class_path` of the Job subclass.
644
- Note that this is **not** the extras.models.Job model class nor an instance thereof.
645
-
646
- Else, if the the model class referenced by `obj_type` has a `name` field, our `name` field will be used
647
- to look up a corresponding model instance. This is used, for example, to look up a related `GitRepository`;
648
- 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
649
- relationship between JobResults and model instances.
650
-
651
- Else, the `obj_type` and `task_id` will be used together as a quasi-GenericForeignKey to look up a model
652
- instance whose PK corresponds to the `task_id`. This behavior is currently unused in the Nautobot core,
653
- but may be of use to plugin developers wishing to create JobResults that have a one-to-one relationship
654
- to plugin model instances.
655
-
656
- This method is potentially rather slow as get_job() may need to actually load the Job class from disk;
657
- consider carefully whether you actually need to use it.
658
- """
659
- from nautobot.extras.jobs import get_job # needed here to avoid a circular import issue
660
-
661
- if self.obj_type == get_job_content_type():
662
- # Related object is an extras.Job subclass, our `name` matches its `class_path`
663
- return get_job(self.name)
664
-
665
- if self.obj_type:
666
- model_class = self.obj_type.model_class()
667
- else:
668
- model_class = None
669
-
670
- if model_class is not None:
671
- if hasattr(model_class, "name"):
672
- # See if we have a many-to-one relationship from JobResult to model_class record, based on `name`
673
- try:
674
- return model_class.objects.get(name=self.name)
675
- except model_class.DoesNotExist:
676
- pass
677
-
678
- # See if we have a one-to-one relationship from JobResult to model_class record based on `task_id`
679
- try:
680
- return model_class.objects.get(id=self.task_id)
681
- except model_class.DoesNotExist:
682
- pass
683
-
684
- return None
685
-
686
- @property
687
- def related_name(self):
688
- """
689
- Similar to self.name, but if there's an appropriate `related_object`, use its name instead.
690
-
691
- Since this calls related_object, the same potential performance concerns exist. Use with caution.
692
- """
693
- related_object = self.related_object
694
- if not related_object:
695
- return self.name
696
- if hasattr(related_object, "name"):
697
- return related_object.name
698
- return str(related_object)
699
-
700
- @property
701
- def linked_record(self):
702
- """
703
- A newer alternative to self.related_object that looks up an extras.models.Job instead of an extras.jobs.Job.
704
- """
705
- if self.job_model is not None:
706
- return self.job_model
707
- model_class = self.obj_type.model_class()
708
- if model_class is not None:
709
- if hasattr(model_class, "name"):
710
- try:
711
- return model_class.objects.get(name=self.name)
712
- except model_class.DoesNotExist:
713
- pass
714
- if hasattr(model_class, "class_path"):
715
- try:
716
- return model_class.objects.get(class_path=self.name)
717
- except model_class.DoesNotExist:
718
- pass
719
- return None
720
-
721
- def get_absolute_url(self):
722
- return reverse("extras:jobresult", kwargs={"pk": self.pk})
723
-
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.
724
552
  def set_status(self, status):
725
553
  """
726
554
  Helper method to change the status of the job result. If the target status is terminal, the completion
@@ -738,69 +566,118 @@ class JobResult(BaseModel, CustomFieldModel):
738
566
  )
739
567
 
740
568
  @classmethod
741
- 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):
742
570
  """
743
- Create a JobResult instance and enqueue a job using the given callable
744
-
745
- func: The callable object to be enqueued for execution
746
- name: Name for the JobResult instance - corresponds to the desired Job class's "class_path" attribute,
747
- if obj_type is extras.Job; for other funcs and obj_types it may differ.
748
- obj_type: ContentType to link to the JobResult instance obj_type
749
- user: User object to link to the JobResult instance
750
- celery_kwargs: Dictionary of kwargs to pass as **kwargs to Celery when job is queued
751
- args: additional args passed to the callable
752
- schedule: Optional ScheduledJob instance to link to the JobResult
753
- 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
754
619
  """
755
- # Discard "request" parameter from the kwargs that we save in the job_result, as it's not relevant to re-runs,
756
- # and will likely go away in the future.
757
- 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
+
758
623
  job_result = cls.objects.create(
759
- name=name,
760
- obj_type=obj_type,
761
- user=user,
762
- task_id=uuid.uuid4(),
624
+ name=job_model.name,
625
+ job_model=job_model,
763
626
  scheduled_job=schedule,
627
+ user=user,
764
628
  )
765
629
 
766
- kwargs["job_result_pk"] = job_result.pk
630
+ if task_queue is None:
631
+ task_queue = settings.CELERY_TASK_DEFAULT_QUEUE
767
632
 
768
- # Prepare kwargs that will be sent to Celery
769
- if celery_kwargs is None:
770
- 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
+ }
771
639
 
772
- if obj_type.app_label == "extras" and obj_type.model.lower() == "job":
773
- try:
774
- job_model = Job.objects.get_for_class_path(name)
775
- if job_model.soft_time_limit > 0:
776
- celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
777
- if job_model.time_limit > 0:
778
- celery_kwargs["time_limit"] = job_model.time_limit
779
- if not job_model.has_sensitive_variables:
780
- job_result.task_kwargs = job_result_kwargs
781
- job_result.job_model = job_model
782
- job_result.save()
783
- except Job.DoesNotExist:
784
- # 2.0 TODO: remove this fallback logic, database records should always exist
785
- from nautobot.extras.jobs import get_job # needed here to avoid a circular import issue
786
-
787
- job_class = get_job(name)
788
- if job_class is not None:
789
- logger.error("No Job instance found in the database corresponding to %s", name)
790
- if hasattr(job_class.Meta, "soft_time_limit"):
791
- celery_kwargs["soft_time_limit"] = job_class.Meta.soft_time_limit
792
- if hasattr(job_class.Meta, "time_limit"):
793
- celery_kwargs["time_limit"] = job_class.Meta.time_limit
794
- if not job_class.has_sensitive_variables:
795
- job_result.task_kwargs = job_result_kwargs
796
- job_result.save()
797
- else:
798
- logger.error("Neither a Job database record nor a Job source class were found for %s", name)
799
-
800
- # Jobs queued inside of a transaction need to run after the transaction completes and the JobResult is saved to the database
801
- transaction.on_commit(
802
- lambda: func.apply_async(args=args, kwargs=kwargs, task_id=str(job_result.task_id), **celery_kwargs)
803
- )
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
+ )
804
681
 
805
682
  return job_result
806
683
 
@@ -808,7 +685,7 @@ class JobResult(BaseModel, CustomFieldModel):
808
685
  self,
809
686
  message,
810
687
  obj=None,
811
- level_choice=LogLevelChoices.LOG_DEFAULT,
688
+ level_choice=LogLevelChoices.LOG_INFO,
812
689
  grouping="main",
813
690
  logger=None, # pylint: disable=redefined-outer-name
814
691
  ):
@@ -848,12 +725,7 @@ class JobResult(BaseModel, CustomFieldModel):
848
725
  log.save(using=JOB_LOGS)
849
726
 
850
727
  if logger:
851
- if level_choice == LogLevelChoices.LOG_FAILURE:
852
- log_level = logging.ERROR
853
- elif level_choice == LogLevelChoices.LOG_WARNING:
854
- log_level = logging.WARNING
855
- else:
856
- log_level = logging.INFO
728
+ log_level = getattr(logging, level_choice.upper(), logging.INFO)
857
729
  logger.log(log_level, message)
858
730
 
859
731
 
@@ -908,9 +780,6 @@ class JobButton(BaseModel, ChangeLoggedModel, NotesMixin):
908
780
  def __str__(self):
909
781
  return self.name
910
782
 
911
- def get_absolute_url(self):
912
- return reverse("extras:jobbutton", kwargs={"pk": self.pk})
913
-
914
783
 
915
784
  class ScheduledJobs(models.Model):
916
785
  """Helper table for tracking updates to scheduled tasks.
@@ -976,6 +845,7 @@ class ScheduledJob(BaseModel):
976
845
  interval = models.CharField(choices=JobExecutionType, max_length=255)
977
846
  args = models.JSONField(blank=True, default=list, encoder=NautobotKombuJSONEncoder)
978
847
  kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
848
+ celery_kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
979
849
  queue = models.CharField(
980
850
  max_length=200,
981
851
  blank=True,
@@ -1061,13 +931,11 @@ class ScheduledJob(BaseModel):
1061
931
  def __str__(self):
1062
932
  return f"{self.name}: {self.interval}"
1063
933
 
1064
- def get_absolute_url(self):
1065
- return reverse("extras:scheduledjob", kwargs={"pk": self.pk})
934
+ # TODO: there's currently no natural key for ScheduledJob
935
+ natural_key_field_names = ["id"]
1066
936
 
1067
937
  def save(self, *args, **kwargs):
1068
938
  self.queue = self.queue or ""
1069
- # pass pk to worker task in kwargs, celery doesn't provide the full object to the worker
1070
- self.kwargs["scheduled_job_pk"] = self.pk
1071
939
  # make sure non-valid crontab doesn't get saved
1072
940
  if self.interval == JobExecutionType.TYPE_CUSTOM:
1073
941
  try: