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
@@ -3,7 +3,7 @@ from django.conf import settings
3
3
  from django.contrib.contenttypes.models import ContentType
4
4
  from django.forms import ValidationError as FormsValidationError
5
5
  from django.http import Http404
6
- from django.shortcuts import get_object_or_404
6
+ from django.shortcuts import get_object_or_404, render
7
7
  from django.utils import timezone
8
8
  from drf_spectacular.types import OpenApiTypes
9
9
  from drf_spectacular.utils import extend_schema, OpenApiParameter
@@ -12,6 +12,7 @@ from graphql import GraphQLError
12
12
  from rest_framework import status
13
13
  from rest_framework.decorators import action
14
14
  from rest_framework.exceptions import MethodNotAllowed, PermissionDenied, ValidationError
15
+ from rest_framework.filters import OrderingFilter
15
16
  from rest_framework.parsers import JSONParser, MultiPartParser
16
17
  from rest_framework.permissions import IsAuthenticated
17
18
  from rest_framework.response import Response
@@ -20,7 +21,7 @@ from rest_framework import mixins, viewsets
20
21
 
21
22
  from nautobot.core.api.authentication import TokenPermissions
22
23
  from nautobot.core.api.filter_backends import NautobotFilterBackend
23
- from nautobot.core.api.utils import get_serializer_for_model
24
+ from nautobot.core.api.utils import get_data_for_serializer_parameter, get_serializer_for_model
24
25
  from nautobot.core.api.views import (
25
26
  BulkDestroyModelMixin,
26
27
  BulkUpdateModelMixin,
@@ -30,10 +31,9 @@ from nautobot.core.api.views import (
30
31
  from nautobot.core.exceptions import CeleryWorkerNotRunningException
31
32
  from nautobot.core.graphql import execute_saved_query
32
33
  from nautobot.core.models.querysets import count_related
33
- from nautobot.core.utils.requests import copy_safe_request
34
+ from nautobot.core.utils.lookup import get_table_for_model
34
35
  from nautobot.extras import filters
35
- from nautobot.extras.choices import JobExecutionType, JobResultStatusChoices
36
- from nautobot.extras.datasources import enqueue_pull_git_repository_and_refresh_data
36
+ from nautobot.extras.choices import JobExecutionType
37
37
  from nautobot.extras.filters import RoleFilterSet
38
38
  from nautobot.extras.models import (
39
39
  ComputedField,
@@ -66,9 +66,9 @@ from nautobot.extras.models import (
66
66
  Webhook,
67
67
  )
68
68
  from nautobot.extras.models import CustomField, CustomFieldChoice
69
- from nautobot.extras.jobs import run_job
70
- from nautobot.extras.utils import get_job_content_type, get_worker_count
71
- from . import nested_serializers, serializers
69
+ from nautobot.extras.secrets.exceptions import SecretError
70
+ from nautobot.extras.utils import get_worker_count
71
+ from . import serializers
72
72
 
73
73
 
74
74
  class ExtrasRootView(APIRootView):
@@ -112,6 +112,84 @@ class NotesViewSetMixin:
112
112
  return self.get_paginated_response(serializer.data)
113
113
 
114
114
 
115
+ # TODO: This is part of the drf-react-template work towards auto-generating create/edit form UI from the REST API.
116
+ # TODO: Why is this in extras instead of core?
117
+ class FormFieldsViewSetMixin:
118
+ """TODO: docstring needed."""
119
+
120
+ # TODO: shouldn't this function generally be named "get_field_groups" not "get_field_group"?
121
+ def get_field_group(self):
122
+ return []
123
+
124
+ # TODO: schema doesn't *look* correct to me based on a reading of the below code. Should this even be in the schema?
125
+ @extend_schema(
126
+ responses={
127
+ 200: {
128
+ "type": "array",
129
+ "items": {
130
+ "type": "object",
131
+ "properties": {
132
+ "type": {"type": "string"},
133
+ "label": {"type": "string"},
134
+ "required": {"type": "string"},
135
+ "help_text": {"type": "string"},
136
+ "choices": {"type": "array", "items": {"type": "array"}},
137
+ },
138
+ },
139
+ }
140
+ }
141
+ )
142
+ @action(detail=False, url_path="form-fields", methods=["get"])
143
+ def form_fields(self, request):
144
+ groups = self.get_field_group()
145
+ model = self.queryset.model
146
+ fields = get_data_for_serializer_parameter(model)
147
+ if groups:
148
+ data = {
149
+ group_name: [fields.get(field) for field in group_fields] for group_name, group_fields in groups.items()
150
+ }
151
+ else:
152
+ model_name = model._meta.model_name
153
+ data = {model_name.capitalize(): fields.values()}
154
+
155
+ return Response(data)
156
+
157
+
158
+ # TODO: This is part of the drf-react-template work towards auto-generating create/edit form UI from the REST API.
159
+ # TODO: Why is this in extras instead of core?
160
+ class TableFieldsViewSetMixin:
161
+ """TODO: docstring needed."""
162
+
163
+ # TODO: this schema is definitely incorrect. Should this view even be in the schema?
164
+ @extend_schema(
165
+ responses={
166
+ 200: {
167
+ "type": "array",
168
+ "items": {
169
+ "type": "object",
170
+ "properties": {
171
+ "type": {"type": "string"},
172
+ "label": {"type": "string"},
173
+ "required": {"type": "string"},
174
+ "help_text": {"type": "string"},
175
+ "choices": {"type": "array", "items": {"type": "array"}},
176
+ },
177
+ },
178
+ }
179
+ }
180
+ )
181
+ @action(detail=False, url_path="table-fields", methods=["get"])
182
+ def table_fields(self, request):
183
+ table = get_table_for_model(self.queryset.model)
184
+ table_instance = table(user=request.user, data=[])
185
+ data = [
186
+ {"name": item[0], "label": item[1]}
187
+ for item in table_instance.configurable_columns
188
+ if item[0] in table_instance.visible_columns
189
+ ]
190
+ return Response({"data": data})
191
+
192
+
115
193
  #
116
194
  # Computed Fields
117
195
  #
@@ -155,20 +233,20 @@ class ConfigContextQuerySetMixin:
155
233
  data annotation or not.
156
234
  """
157
235
 
158
- filter_backends = [ConfigContextFilterBackend]
236
+ filter_backends = [ConfigContextFilterBackend, OrderingFilter]
159
237
 
160
238
  def get_queryset(self):
161
239
  """
162
240
  Build the proper queryset based on the request context
163
241
 
164
- If the `brief` query param equates to True or the `exclude` query param
242
+ If the `exclude` query param
165
243
  includes `config_context` as a value, return the base queryset.
166
244
 
167
245
  Else, return the queryset annotated with config context data
168
246
  """
169
247
  queryset = super().get_queryset()
170
248
  request = self.get_serializer_context()["request"]
171
- if self.brief or (request is not None and "config_context" in request.query_params.get("exclude", [])):
249
+ if request is not None and "config_context" in request.query_params.get("exclude", []):
172
250
  return queryset
173
251
  return queryset.annotate_config_context_data()
174
252
 
@@ -249,12 +327,23 @@ class CustomFieldModelViewSet(ModelViewSet):
249
327
  return context
250
328
 
251
329
 
252
- class NautobotModelViewSet(CustomFieldModelViewSet, NotesViewSetMixin):
330
+ class NautobotModelViewSet(CustomFieldModelViewSet, NotesViewSetMixin, FormFieldsViewSetMixin, TableFieldsViewSetMixin):
253
331
  """Base class to use for API ViewSets based on OrganizationalModel or PrimaryModel.
254
332
 
255
333
  Can also be used for models derived from BaseModel, so long as they support Notes.
256
334
  """
257
335
 
336
+ # TODO: this currently throws a 500 error in drf_react_template because it's returning an HttpResponse but
337
+ # drf_react_template thinks it's a REST endpoint that should be returning a JsonResponse
338
+ @action(detail=True, url_path="app_full_width_fragment")
339
+ def app_full_width_fragment(self, request, pk):
340
+ """
341
+ Return html fragment from a plugin.
342
+ """
343
+ obj = get_object_or_404(self.queryset, pk=pk)
344
+
345
+ return render(request, "generic/object_retrieve_plugin_full_width.html", {"object": obj})
346
+
258
347
 
259
348
  #
260
349
  # Custom Links
@@ -349,7 +438,7 @@ class GitRepositoryViewSet(NautobotModelViewSet):
349
438
  raise CeleryWorkerNotRunningException()
350
439
 
351
440
  repository = get_object_or_404(GitRepository, id=pk)
352
- enqueue_pull_git_repository_and_refresh_data(repository, request)
441
+ repository.sync(user=request.user)
353
442
  return Response({"message": f"Repository {repository} sync job added to queue."})
354
443
 
355
444
 
@@ -397,25 +486,16 @@ class ImageAttachmentViewSet(ModelViewSet):
397
486
  #
398
487
 
399
488
 
400
- def _create_schedule(serializer, data, commit, job, job_model, request, celery_kwargs=dict, task_queue=None):
489
+ def _create_schedule(serializer, data, job_model, user, approval_required, task_queue=None):
401
490
  """
402
491
  This is an internal function to create a scheduled job from API data.
403
492
  It has to handle both once-offs (i.e. of type TYPE_FUTURE) and interval
404
493
  jobs.
405
494
  """
406
- task_kwargs = {
407
- "data": data,
408
- "request": copy_safe_request(request),
409
- "user": request.user.pk,
410
- "commit": commit,
411
- "name": job.class_path,
412
- "celery_kwargs": celery_kwargs,
413
- "task_queue": task_queue,
414
- }
415
495
  type_ = serializer["interval"]
416
496
  if type_ == JobExecutionType.TYPE_IMMEDIATELY:
417
497
  time = timezone.now()
418
- name = serializer.get("name") or f"{job.name} - {time}"
498
+ name = serializer.get("name") or f"{job_model.name} - {time}"
419
499
  elif type_ == JobExecutionType.TYPE_CUSTOM:
420
500
  time = serializer.get("start_time") # doing .get("key", "default") returns None instead of "default"
421
501
  if time is None:
@@ -428,6 +508,11 @@ def _create_schedule(serializer, data, commit, job, job_model, request, celery_k
428
508
  name = serializer["name"]
429
509
  crontab = serializer.get("crontab", "")
430
510
 
511
+ celery_kwargs = {
512
+ "nautobot_job_profile": False,
513
+ "queue": task_queue,
514
+ }
515
+
431
516
  # 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
432
517
  #
433
518
  # We pass in job_class and job_model here partly for forward/backward compatibility logic, and
@@ -437,16 +522,17 @@ def _create_schedule(serializer, data, commit, job, job_model, request, celery_k
437
522
  # scheduled for.
438
523
  scheduled_job = ScheduledJob(
439
524
  name=name,
440
- task="nautobot.extras.jobs.scheduled_job_handler",
441
- job_class=job.class_path,
525
+ task=job_model.job_class.registered_name,
526
+ job_class=job_model.class_path,
442
527
  job_model=job_model,
443
528
  start_time=time,
444
- description=f"Nautobot job {name} scheduled by {request.user} on {time}",
445
- kwargs=task_kwargs,
529
+ description=f"Nautobot job {name} scheduled by {user} for {time}",
530
+ kwargs=data,
531
+ celery_kwargs=celery_kwargs,
446
532
  interval=type_,
447
533
  one_off=(type_ == JobExecutionType.TYPE_FUTURE),
448
- user=request.user,
449
- approval_required=job_model.approval_required,
534
+ user=user,
535
+ approval_required=approval_required,
450
536
  crontab=crontab,
451
537
  queue=task_queue,
452
538
  )
@@ -454,178 +540,6 @@ def _create_schedule(serializer, data, commit, job, job_model, request, celery_k
454
540
  return scheduled_job
455
541
 
456
542
 
457
- def _run_job(request, job_model, legacy_response=False):
458
- """An internal function providing logic shared between JobModelViewSet.run() and JobViewSet.run()."""
459
- if not request.user.has_perm("extras.run_job"):
460
- raise PermissionDenied("This user does not have permission to run jobs.")
461
- if not job_model.enabled:
462
- raise PermissionDenied("This job is not enabled to be run.")
463
- if not job_model.installed:
464
- raise MethodNotAllowed(request.method, detail="This job is not presently installed and cannot be run")
465
- if job_model.has_sensitive_variables:
466
- if (
467
- "schedule" in request.data
468
- and "interval" in request.data["schedule"]
469
- and request.data["schedule"]["interval"] != JobExecutionType.TYPE_IMMEDIATELY
470
- ):
471
- raise ValidationError(
472
- {"schedule": {"interval": ["Unable to schedule job: Job may have sensitive input variables"]}}
473
- )
474
- if job_model.approval_required:
475
- raise ValidationError(
476
- "Unable to run or schedule job: "
477
- "This job is flagged as possibly having sensitive variables but is also flagged as requiring approval."
478
- "One of these two flags must be removed before this job can be scheduled or run."
479
- )
480
-
481
- job_class = job_model.job_class
482
- if job_class is None:
483
- raise MethodNotAllowed(request.method, detail="This job's source code could not be located and cannot be run")
484
- job = job_class()
485
-
486
- valid_queues = job_model.task_queues if job_model.task_queues else [settings.CELERY_TASK_DEFAULT_QUEUE]
487
- # Get a default queue from either the job model's specified task queue or system default to fall back on if request doesn't provide one
488
- default_valid_queue = valid_queues[0]
489
-
490
- # We need to call request.data for both cases as this is what pulls and caches the request data
491
- data = request.data
492
- files = None
493
- schedule_data = None
494
-
495
- # We must extract from the request:
496
- # - Job Form data (for submission to the job itself)
497
- # - Schedule data
498
- # - Commit flag state
499
- # - Desired task queue
500
- # Depending on request content type (largely for backwards compatibility) the keys at which these are found are different
501
- if "multipart/form-data" in request.content_type:
502
- data = request._data.dict() # .data will return data and files, we just want the data
503
- files = request.FILES
504
-
505
- # JobMultiPartInputSerializer is a "flattened" version of JobInputSerializer
506
- input_serializer = serializers.JobMultiPartInputSerializer(data=data, context={"request": request})
507
- input_serializer.is_valid(raise_exception=True)
508
-
509
- commit = input_serializer.validated_data.get("_commit", None)
510
- task_queue = input_serializer.validated_data.get("_task_queue", default_valid_queue)
511
-
512
- # JobMultiPartInputSerializer only has keys for executing job (commit, task_queue, etc),
513
- # everything else is a candidate for the job form's data.
514
- # job_class.validate_data will throw an error for any unexpected key/value pairs.
515
- non_job_keys = input_serializer.validated_data.keys()
516
- for non_job_key in non_job_keys:
517
- data.pop(non_job_key, None)
518
-
519
- # List of keys in serializer that are effectively exploded versions of the schedule dictionary from JobInputSerializer
520
- schedule_keys = ("_schedule_name", "_schedule_start_time", "_schedule_interval", "_schedule_crontab")
521
-
522
- # Assign the key from the validated_data output to dictionary without prefixed "_schedule_"
523
- # For all the keys that are schedule keys
524
- # Assign only if the key is in the output since we don't want None's if not provided
525
- if any(schedule_key in non_job_keys for schedule_key in schedule_keys):
526
- schedule_data = {
527
- k.replace("_schedule_", ""): input_serializer.validated_data[k]
528
- for k in schedule_keys
529
- if k in input_serializer.validated_data
530
- }
531
-
532
- else:
533
- input_serializer = serializers.JobInputSerializer(data=data, context={"request": request})
534
- input_serializer.is_valid(raise_exception=True)
535
-
536
- data = input_serializer.validated_data.get("data", {})
537
- commit = input_serializer.validated_data.get("commit", None)
538
- task_queue = input_serializer.validated_data.get("task_queue", default_valid_queue)
539
- schedule_data = input_serializer.validated_data.get("schedule", None)
540
-
541
- if commit is None:
542
- commit = job_model.commit_default
543
-
544
- if task_queue not in valid_queues:
545
- raise ValidationError({"task_queue": [f'"{task_queue}" is not a valid choice.']})
546
-
547
- cleaned_data = None
548
- try:
549
- cleaned_data = job.validate_data(data, files=files)
550
- cleaned_data.pop(
551
- "_commit", None
552
- ) # We don't get commit from the form, instead it's part of the serializer's validated data
553
-
554
- except FormsValidationError as e:
555
- # message_dict can only be accessed if ValidationError got a dict
556
- # in the constructor (saved as error_dict). Otherwise we get a list
557
- # of errors under messages
558
- return Response({"errors": e.message_dict if hasattr(e, "error_dict") else e.messages}, status=400)
559
-
560
- if not get_worker_count(queue=task_queue):
561
- raise CeleryWorkerNotRunningException(queue=task_queue)
562
-
563
- job_content_type = get_job_content_type()
564
-
565
- # Default to a null JobResult.
566
- job_result = None
567
-
568
- # Assert that a job with `approval_required=True` has a schedule that enforces approval and
569
- # executes immediately.
570
- if schedule_data is None and job_model.approval_required:
571
- schedule_data = {"interval": JobExecutionType.TYPE_IMMEDIATELY}
572
-
573
- # Skip creating a ScheduledJob when job can be executed immediately
574
- elif (
575
- schedule_data
576
- and schedule_data["interval"] == JobExecutionType.TYPE_IMMEDIATELY
577
- and not job_model.approval_required
578
- ):
579
- schedule_data = None
580
-
581
- # Try to create a ScheduledJob, or...
582
- if schedule_data:
583
- schedule = _create_schedule(
584
- schedule_data,
585
- job_class.serialize_data(cleaned_data),
586
- commit,
587
- job,
588
- job_model,
589
- request,
590
- celery_kwargs={"queue": task_queue},
591
- task_queue=input_serializer.validated_data.get("task_queue", None),
592
- )
593
- else:
594
- schedule = None
595
-
596
- # ... If we can't create one, create a JobResult instead.
597
- if schedule is None:
598
- job_result = JobResult.enqueue_job(
599
- run_job,
600
- job.class_path,
601
- job_content_type,
602
- request.user,
603
- celery_kwargs={"queue": task_queue},
604
- data=job_class.serialize_data(cleaned_data),
605
- request=copy_safe_request(request),
606
- commit=commit,
607
- task_queue=input_serializer.validated_data.get("task_queue", None),
608
- )
609
- job.result = job_result
610
-
611
- if legacy_response:
612
- # Old-style JobViewSet response - serialize the Job class in the response for some reason?
613
- serializer = serializers.JobClassDetailSerializer(job, context={"request": request})
614
- return Response(serializer.data)
615
- else:
616
- # New-style JobModelViewSet response - serialize the schedule or job_result as appropriate
617
- data = {"scheduled_job": None, "job_result": None}
618
- if schedule:
619
- data["scheduled_job"] = nested_serializers.NestedScheduledJobSerializer(
620
- schedule, context={"request": request}
621
- ).data
622
- if job_result:
623
- data["job_result"] = nested_serializers.NestedJobResultSerializer(
624
- job_result, context={"request": request}
625
- ).data
626
- return Response(data, status=status.HTTP_201_CREATED)
627
-
628
-
629
543
  class JobViewSet(
630
544
  # DRF mixins:
631
545
  # note no CreateModelMixin
@@ -642,43 +556,6 @@ class JobViewSet(
642
556
  serializer_class = serializers.JobSerializer
643
557
  filterset_class = filters.JobFilterSet
644
558
 
645
- @extend_schema(
646
- deprecated=True,
647
- operation_id="extras_jobs_read_deprecated",
648
- responses={"200": serializers.JobClassDetailSerializer()},
649
- )
650
- @action(
651
- detail=False, # a /jobs/... URL, not a /jobs/<pk>/... URL
652
- methods=["get"],
653
- url_path="(?P<class_path>[^/]+/[^/]+/[^/]+)", # /api/extras/jobs/<class_path>/
654
- url_name="detail",
655
- )
656
- def retrieve_deprecated(self, request, class_path):
657
- """
658
- Get details of a Job as identified by its class-path.
659
-
660
- This API endpoint is deprecated; it is recommended to use the extras_jobs_read endpoint instead.
661
- """
662
- if not request.user.has_perm("extras.view_job"):
663
- raise PermissionDenied("This user does not have permission to view jobs.")
664
- try:
665
- job_model = Job.objects.restrict(request.user, "view").get_for_class_path(class_path)
666
- except Job.DoesNotExist:
667
- raise Http404
668
- if not job_model.installed or job_model.job_class is None:
669
- raise Http404
670
- job_content_type = get_job_content_type()
671
- job = job_model.job_class() # TODO: why do we need to instantiate the job_class?
672
- job.result = JobResult.objects.filter(
673
- obj_type=job_content_type,
674
- name=job.class_path,
675
- status__in=JobResultStatusChoices.READY_STATES,
676
- ).first()
677
-
678
- serializer = serializers.JobClassDetailSerializer(job, context={"request": request})
679
-
680
- return Response(serializer.data)
681
-
682
559
  @extend_schema(responses={"200": serializers.JobVariableSerializer(many=True)})
683
560
  @action(detail=True, filterset_class=None)
684
561
  def variables(self, request, pk):
@@ -744,36 +621,150 @@ class JobViewSet(
744
621
  def run(self, request, *args, pk, **kwargs):
745
622
  """Run the specified Job."""
746
623
  job_model = self.get_object()
747
- return _run_job(request, job_model)
748
-
749
- @extend_schema(
750
- deprecated=True,
751
- methods=["post"],
752
- request=serializers.JobInputSerializer,
753
- responses={"200": serializers.JobClassDetailSerializer()},
754
- operation_id="extras_jobs_run_deprecated",
755
- )
756
- @action(
757
- detail=False, # a /jobs/... URL, not a /jobs/<pk>/... URL
758
- methods=["post"],
759
- permission_classes=[JobRunTokenPermissions],
760
- url_path="(?P<class_path>[^/]+/[^/]+/[^/]+)/run", # /api/extras/jobs/<class_path>/run/
761
- url_name="run",
762
- parser_classes=[JSONParser, MultiPartParser],
763
- )
764
- def run_deprecated(self, request, class_path):
765
- """
766
- Run a Job as identified by its class-path.
767
-
768
- This API endpoint is deprecated; it is recommended to use the extras_jobs_run endpoint instead.
769
- """
770
624
  if not request.user.has_perm("extras.run_job"):
771
625
  raise PermissionDenied("This user does not have permission to run jobs.")
626
+ if not job_model.enabled:
627
+ raise PermissionDenied("This job is not enabled to be run.")
628
+ if not job_model.installed:
629
+ raise MethodNotAllowed(request.method, detail="This job is not presently installed and cannot be run")
630
+ if job_model.has_sensitive_variables:
631
+ if (
632
+ "schedule" in request.data
633
+ and "interval" in request.data["schedule"]
634
+ and request.data["schedule"]["interval"] != JobExecutionType.TYPE_IMMEDIATELY
635
+ ):
636
+ raise ValidationError(
637
+ {"schedule": {"interval": ["Unable to schedule job: Job may have sensitive input variables"]}}
638
+ )
639
+ if job_model.approval_required:
640
+ raise ValidationError(
641
+ "Unable to run or schedule job: "
642
+ "This job is flagged as possibly having sensitive variables but is also flagged as requiring approval."
643
+ "One of these two flags must be removed before this job can be scheduled or run."
644
+ )
645
+
646
+ job_class = job_model.job_class
647
+ if job_class is None:
648
+ raise MethodNotAllowed(
649
+ request.method, detail="This job's source code could not be located and cannot be run"
650
+ )
651
+
652
+ valid_queues = job_model.task_queues if job_model.task_queues else [settings.CELERY_TASK_DEFAULT_QUEUE]
653
+ # Get a default queue from either the job model's specified task queue or system default to fall back on if request doesn't provide one
654
+ default_valid_queue = valid_queues[0]
655
+
656
+ # We need to call request.data for both cases as this is what pulls and caches the request data
657
+ data = request.data
658
+ files = None
659
+ schedule_data = None
660
+
661
+ # We must extract from the request:
662
+ # - Job Form data (for submission to the job itself)
663
+ # - Schedule data
664
+ # - Desired task queue
665
+ # Depending on request content type (largely for backwards compatibility) the keys at which these are found are different
666
+ if "multipart/form-data" in request.content_type:
667
+ data = request._data.dict() # .data will return data and files, we just want the data
668
+ files = request.FILES
669
+
670
+ # JobMultiPartInputSerializer is a "flattened" version of JobInputSerializer
671
+ input_serializer = serializers.JobMultiPartInputSerializer(data=data, context={"request": request})
672
+ input_serializer.is_valid(raise_exception=True)
673
+
674
+ task_queue = input_serializer.validated_data.get("_task_queue", default_valid_queue)
675
+
676
+ # JobMultiPartInputSerializer only has keys for executing job (task_queue, etc),
677
+ # everything else is a candidate for the job form's data.
678
+ # job_class.validate_data will throw an error for any unexpected key/value pairs.
679
+ non_job_keys = input_serializer.validated_data.keys()
680
+ for non_job_key in non_job_keys:
681
+ data.pop(non_job_key, None)
682
+
683
+ # List of keys in serializer that are effectively exploded versions of the schedule dictionary from JobInputSerializer
684
+ schedule_keys = ("_schedule_name", "_schedule_start_time", "_schedule_interval", "_schedule_crontab")
685
+
686
+ # Assign the key from the validated_data output to dictionary without prefixed "_schedule_"
687
+ # For all the keys that are schedule keys
688
+ # Assign only if the key is in the output since we don't want None's if not provided
689
+ if any(schedule_key in non_job_keys for schedule_key in schedule_keys):
690
+ schedule_data = {
691
+ k.replace("_schedule_", ""): input_serializer.validated_data[k]
692
+ for k in schedule_keys
693
+ if k in input_serializer.validated_data
694
+ }
695
+
696
+ else:
697
+ input_serializer = serializers.JobInputSerializer(data=data, context={"request": request})
698
+ input_serializer.is_valid(raise_exception=True)
699
+
700
+ data = input_serializer.validated_data.get("data", {})
701
+ task_queue = input_serializer.validated_data.get("task_queue", default_valid_queue)
702
+ schedule_data = input_serializer.validated_data.get("schedule", None)
703
+
704
+ if task_queue not in valid_queues:
705
+ raise ValidationError({"task_queue": [f'"{task_queue}" is not a valid choice.']})
706
+
707
+ cleaned_data = None
772
708
  try:
773
- job_model = Job.objects.restrict(request.user, "run").get_for_class_path(class_path)
774
- except Job.DoesNotExist:
775
- raise Http404
776
- return _run_job(request, job_model, legacy_response=True)
709
+ cleaned_data = job_class().validate_data(data, files=files)
710
+ cleaned_data = job_model.job_class.prepare_job_kwargs(cleaned_data)
711
+
712
+ except FormsValidationError as e:
713
+ # message_dict can only be accessed if ValidationError got a dict
714
+ # in the constructor (saved as error_dict). Otherwise we get a list
715
+ # of errors under messages
716
+ return Response({"errors": e.message_dict if hasattr(e, "error_dict") else e.messages}, status=400)
717
+
718
+ if not get_worker_count(queue=task_queue):
719
+ raise CeleryWorkerNotRunningException(queue=task_queue)
720
+
721
+ # Default to a null JobResult.
722
+ job_result = None
723
+
724
+ # Approval is not required for dryrun
725
+ if job_class.supports_dryrun:
726
+ dryrun = data.get("dryrun", False)
727
+ approval_required = not dryrun and job_model.approval_required
728
+ else:
729
+ approval_required = job_model.approval_required
730
+
731
+ # Set schedule for jobs that require approval but request did not supply schedule data
732
+ if schedule_data is None and approval_required:
733
+ schedule_data = {"interval": JobExecutionType.TYPE_IMMEDIATELY}
734
+
735
+ # Skip creating a ScheduledJob when job can be executed immediately
736
+ elif schedule_data and schedule_data["interval"] == JobExecutionType.TYPE_IMMEDIATELY and not approval_required:
737
+ schedule_data = None
738
+
739
+ # Try to create a ScheduledJob, or...
740
+ if schedule_data:
741
+ schedule = _create_schedule(
742
+ schedule_data,
743
+ job_class.serialize_data(cleaned_data),
744
+ job_model,
745
+ request.user,
746
+ approval_required,
747
+ task_queue=input_serializer.validated_data.get("task_queue", None),
748
+ )
749
+ else:
750
+ schedule = None
751
+
752
+ # ... If we can't create one, create a JobResult instead.
753
+ if schedule is None:
754
+ job_result = JobResult.enqueue_job(
755
+ job_model,
756
+ request.user,
757
+ task_queue=task_queue,
758
+ **job_class.serialize_data(cleaned_data),
759
+ )
760
+
761
+ # New-style JobModelViewSet response - serialize the schedule or job_result as appropriate
762
+ data = {"scheduled_job": None, "job_result": None}
763
+ if schedule:
764
+ data["scheduled_job"] = serializers.ScheduledJobSerializer(schedule, context={"request": request}).data
765
+ if job_result:
766
+ data["job_result"] = serializers.JobResultSerializer(job_result, context={"request": request}).data
767
+ return Response(data, status=status.HTTP_201_CREATED)
777
768
 
778
769
 
779
770
  #
@@ -819,7 +810,7 @@ class JobResultViewSet(
819
810
  Retrieve a list of job results
820
811
  """
821
812
 
822
- queryset = JobResult.objects.select_related("job_model", "obj_type", "user")
813
+ queryset = JobResult.objects.select_related("job_model", "user")
823
814
  serializer_class = serializers.JobResultSerializer
824
815
  filterset_class = filters.JobResultFilterSet
825
816
 
@@ -827,7 +818,7 @@ class JobResultViewSet(
827
818
  def logs(self, request, pk=None):
828
819
  job_result = self.get_object()
829
820
  logs = job_result.job_log_entries.all()
830
- serializer = nested_serializers.NestedJobLogEntrySerializer(logs, context={"request": request}, many=True)
821
+ serializer = serializers.JobLogEntrySerializer(logs, context={"request": request}, many=True)
831
822
  return Response(serializer.data)
832
823
 
833
824
 
@@ -967,21 +958,19 @@ class ScheduledJobViewSet(ReadOnlyModelViewSet):
967
958
  job_model = scheduled_job.job_model
968
959
  if job_model is None or not job_model.runnable:
969
960
  raise MethodNotAllowed("This job cannot be dry-run at this time.")
961
+ if not job_model.supports_dryrun:
962
+ raise MethodNotAllowed("This job does not support dry-run.")
970
963
  if not Job.objects.check_perms(request.user, instance=job_model, action="run"):
971
964
  raise PermissionDenied("You do not have permission to run this job.")
972
965
 
973
- # Immediately enqueue the job with commit=False
974
- job_content_type = get_job_content_type()
966
+ # Immediately enqueue the job
967
+ job_kwargs = job_model.job_class.prepare_job_kwargs(scheduled_job.kwargs.get("data", {}))
968
+ job_kwargs["dryrun"] = True
975
969
  job_result = JobResult.enqueue_job(
976
- run_job,
977
- job_model.class_path,
978
- job_content_type,
970
+ job_model,
979
971
  request.user,
980
- celery_kwargs=scheduled_job.kwargs.get("celery_kwargs", {}),
981
- data=scheduled_job.kwargs.get("data", {}),
982
- request=copy_safe_request(request),
983
- commit=False, # force a dry-run
984
- task_queue=scheduled_job.kwargs.get("task_queue", None),
972
+ celery_kwargs=scheduled_job.celery_kwargs or {},
973
+ **job_model.job_class.serialize_data(job_kwargs),
985
974
  )
986
975
  serializer = serializers.JobResultSerializer(job_result, context={"request": request})
987
976
 
@@ -993,7 +982,7 @@ class ScheduledJobViewSet(ReadOnlyModelViewSet):
993
982
  #
994
983
 
995
984
 
996
- class NoteViewSet(ModelViewSet):
985
+ class NoteViewSet(ModelViewSet, TableFieldsViewSetMixin):
997
986
  queryset = Note.objects.select_related("user")
998
987
  serializer_class = serializers.NoteSerializer
999
988
  filterset_class = filters.NoteFilterSet
@@ -1008,7 +997,7 @@ class NoteViewSet(ModelViewSet):
1008
997
  #
1009
998
 
1010
999
 
1011
- class ObjectChangeViewSet(ReadOnlyModelViewSet):
1000
+ class ObjectChangeViewSet(ReadOnlyModelViewSet, TableFieldsViewSetMixin):
1012
1001
  """
1013
1002
  Retrieve a list of recent changes.
1014
1003
  """
@@ -1060,6 +1049,31 @@ class SecretsViewSet(NautobotModelViewSet):
1060
1049
  serializer_class = serializers.SecretSerializer
1061
1050
  filterset_class = filters.SecretFilterSet
1062
1051
 
1052
+ @extend_schema(
1053
+ responses={
1054
+ 200: {
1055
+ "type": "object",
1056
+ "properties": {
1057
+ "result": {"type": "boolean"},
1058
+ "message": {"type": "string"},
1059
+ },
1060
+ }
1061
+ },
1062
+ )
1063
+ @action(methods=["GET"], detail=True)
1064
+ def check(self, request, pk):
1065
+ """Check that a secret's value is accessible."""
1066
+ result = False
1067
+ message = "Unknown error"
1068
+ try:
1069
+ self.get_object().get_value()
1070
+ result = True
1071
+ message = "Passed"
1072
+ except SecretError as e:
1073
+ message = str(e)
1074
+ response = {"result": result, "message": message}
1075
+ return Response(response)
1076
+
1063
1077
 
1064
1078
  class SecretsGroupViewSet(NautobotModelViewSet):
1065
1079
  """