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,14 +1,12 @@
1
1
  import datetime
2
+ from io import StringIO
2
3
  import json
3
- import logging
4
+ from pathlib import Path
4
5
  import re
5
- import uuid
6
- from io import StringIO
7
6
  from unittest import mock
7
+ import uuid
8
8
 
9
- from django.contrib.auth import get_user_model
10
9
  from django.contrib.contenttypes.models import ContentType
11
- from django.core.exceptions import ObjectDoesNotExist
12
10
  from django.core.files.uploadedfile import SimpleUploadedFile
13
11
  from django.core.management import call_command
14
12
  from django.core.management.base import CommandError
@@ -19,7 +17,8 @@ from django.utils import timezone
19
17
  from nautobot.core.testing import (
20
18
  TestCase,
21
19
  TransactionTestCase,
22
- run_job_for_testing,
20
+ create_job_result_and_run_job,
21
+ get_job_class_and_model,
23
22
  )
24
23
  from nautobot.core.utils.lookup import get_changes_for_model
25
24
  from nautobot.dcim.models import Device, Location, LocationType
@@ -30,31 +29,11 @@ from nautobot.extras.choices import (
30
29
  ObjectChangeEventContextChoices,
31
30
  )
32
31
  from nautobot.extras.context_managers import JobHookChangeContext, change_logging, web_request_context
33
- from nautobot.extras.jobs import get_job, run_job
34
- from nautobot.extras.models import CustomField, FileProxy, Job, JobHook, JobResult, Role, ScheduledJob, Status
32
+ from nautobot.extras.jobs import get_job
33
+ from nautobot.extras.models import CustomField, FileProxy, JobHook, JobResult, Role, ScheduledJob, Status
35
34
  from nautobot.extras.models.models import JobLogEntry
36
35
 
37
36
 
38
- def get_job_class_and_model(module, name):
39
- """Test helper function to look up a job class and job model and ensure the latter is enabled."""
40
- class_path = f"local/{module}/{name}"
41
- job_class = get_job(class_path)
42
- job_model = Job.objects.get_for_class_path(class_path)
43
- job_model.enabled = True
44
- job_model.validated_save()
45
- return (job_class, job_model)
46
-
47
-
48
- def create_job_result_and_run_job(module, name, *, data=None, commit=True, request=None):
49
- """Test helper function to call get_job_class_and_model() then and call run_job_for_testing()."""
50
- if data is None:
51
- data = {}
52
- _job_class, job_model = get_job_class_and_model(module, name)
53
- job_result = run_job_for_testing(job=job_model, data=data, commit=commit, request=request)
54
- job_result.refresh_from_db()
55
- return job_result
56
-
57
-
58
37
  class JobTest(TransactionTestCase):
59
38
  """
60
39
  Test basic jobs to ensure importing works.
@@ -73,11 +52,11 @@ class JobTest(TransactionTestCase):
73
52
 
74
53
  def test_job_hard_time_limit_less_than_soft_time_limit(self):
75
54
  """
76
- Job test which produces a log_warning because the time_limit is less than the soft_time_limit.
55
+ Job test which produces a warning log message because the time_limit is less than the soft_time_limit.
77
56
  """
78
- module = "test_soft_time_limit_greater_than_time_limit"
57
+ module = "soft_time_limit_greater_than_time_limit"
79
58
  name = "TestSoftTimeLimitGreaterThanHardTimeLimit"
80
- job_result = create_job_result_and_run_job(module, name, commit=False)
59
+ job_result = create_job_result_and_run_job(module, name)
81
60
  log_warning = JobLogEntry.objects.filter(
82
61
  job_result=job_result, log_level=LogLevelChoices.LOG_WARNING, grouping="initialization"
83
62
  ).first()
@@ -88,48 +67,34 @@ class JobTest(TransactionTestCase):
88
67
  "This job will fail silently after 5.0 seconds.",
89
68
  )
90
69
 
91
- def test_job_pass_with_run_job_directly(self):
70
+ def test_job_pass(self):
92
71
  """
93
- Job test with pass result calling run_job directly in order to test for backwards stability of its API.
94
-
95
- Because calling run_job directly used to be the best practice for testing jobs, we want to ensure that calling
96
- it still works even if we ever change the run_job call in the run_job_for_testing wrapper.
72
+ Job test with pass result.
97
73
  """
98
- module = "test_pass"
74
+ module = "pass"
99
75
  name = "TestPass"
100
- _job_class, job_model = get_job_class_and_model(module, name)
101
- job_model.enabled = True
102
- job_model.validated_save()
103
- job_content_type = ContentType.objects.get(app_label="extras", model="job")
104
- job_result = JobResult.objects.create(
105
- name=job_model.class_path,
106
- obj_type=job_content_type,
107
- job_model=job_model,
108
- user=None,
109
- task_id=uuid.uuid4(),
110
- )
111
- run_job(data={}, request=None, commit=False, job_result_pk=job_result.pk)
112
- job_result = create_job_result_and_run_job(module, name, commit=False)
76
+ job_result = create_job_result_and_run_job(module, name)
113
77
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
114
78
 
115
- def test_job_pass(self):
79
+ def test_job_result_manager_censor_sensitive_variables(self):
116
80
  """
117
- Job test with pass result.
81
+ Job test with JobResult Censored Sensitive Variables.
118
82
  """
119
- module = "test_pass"
120
- name = "TestPass"
121
- job_result = create_job_result_and_run_job(module, name, commit=False)
83
+ module = "has_sensitive_variables"
84
+ name = "TestHasSensitiveVariables"
85
+ # This function create_job_result_and_run_job and the subsequent functions' arguments are very messy
86
+ job_result = create_job_result_and_run_job(module, name, "local", 1, 2, "3", kwarg_1=1, kwarg_2="2")
122
87
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
88
+ self.assertEqual(job_result.task_args, [])
89
+ self.assertEqual(job_result.task_kwargs, {})
123
90
 
124
91
  def test_job_fail(self):
125
92
  """
126
93
  Job test with fail result.
127
94
  """
128
- module = "test_fail"
95
+ module = "fail"
129
96
  name = "TestFail"
130
- logging.disable(logging.ERROR)
131
- job_result = create_job_result_and_run_job(module, name, commit=False)
132
- logging.disable(logging.NOTSET)
97
+ job_result = create_job_result_and_run_job(module, name)
133
98
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
134
99
 
135
100
  def test_field_default(self):
@@ -137,9 +102,9 @@ class JobTest(TransactionTestCase):
137
102
  Job test with field that is a default value that is falsey.
138
103
  https://github.com/nautobot/nautobot/issues/2039
139
104
  """
140
- module = "test_field_default"
105
+ module = "field_default"
141
106
  name = "TestFieldDefault"
142
- job_class = get_job(f"local/{module}/{name}")
107
+ job_class = get_job(f"{module}.{name}")
143
108
  form = job_class().as_form()
144
109
 
145
110
  self.assertInHTML(
@@ -156,75 +121,106 @@ class JobTest(TransactionTestCase):
156
121
  """
157
122
  Job test with field order.
158
123
  """
159
- module = "test_field_order"
124
+ module = "field_order"
160
125
  name = "TestFieldOrder"
161
- job_class = get_job(f"local/{module}/{name}")
126
+ job_class = get_job(f"{module}.{name}")
162
127
  form = job_class().as_form()
163
- self.assertSequenceEqual(list(form.fields.keys()), ["var1", "var2", "var23", "_task_queue", "_commit"])
128
+ self.assertSequenceEqual(list(form.fields.keys()), ["var1", "var2", "var23", "_task_queue", "_profile"])
164
129
 
165
130
  def test_no_field_order(self):
166
131
  """
167
132
  Job test without field_order.
168
133
  """
169
- module = "test_no_field_order"
134
+ module = "no_field_order"
170
135
  name = "TestNoFieldOrder"
171
- job_class = get_job(f"local/{module}/{name}")
136
+ job_class = get_job(f"{module}.{name}")
172
137
  form = job_class().as_form()
173
- self.assertSequenceEqual(list(form.fields.keys()), ["var23", "var2", "_task_queue", "_commit"])
138
+ self.assertSequenceEqual(list(form.fields.keys()), ["var23", "var2", "_task_queue", "_profile"])
174
139
 
175
140
  def test_no_field_order_inherited_variable(self):
176
141
  """
177
142
  Job test without field_order with a variable inherited from the base class
178
143
  """
179
- module = "test_no_field_order"
144
+ module = "no_field_order"
180
145
  name = "TestDefaultFieldOrderWithInheritance"
181
- job_class = get_job(f"local/{module}/{name}")
146
+ job_class = get_job(f"{module}.{name}")
182
147
  form = job_class().as_form()
183
148
  self.assertSequenceEqual(
184
149
  list(form.fields.keys()),
185
- ["testvar1", "b_testvar2", "a_testvar3", "_task_queue", "_commit"],
150
+ ["testvar1", "b_testvar2", "a_testvar3", "_task_queue", "_profile"],
186
151
  )
187
152
 
188
- def test_read_only_job_pass(self):
153
+ def test_atomic_transaction_decorator_job_pass(self):
189
154
  """
190
- Job read only test with pass result.
155
+ Job with @transaction.atomic decorator test with pass result.
191
156
  """
192
- module = "test_read_only_pass"
193
- name = "TestReadOnlyPass"
194
- job_result = create_job_result_and_run_job(module, name, commit=False)
157
+ module = "atomic_transaction"
158
+ name = "TestAtomicDecorator"
159
+ job_result = create_job_result_and_run_job(module, name)
195
160
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
196
- self.assertEqual(Location.objects.count(), 0) # Ensure DB transaction was aborted
197
-
198
- def test_read_only_job_fail(self):
199
- """
200
- Job read only test with fail result.
201
- """
202
- module = "test_read_only_fail"
203
- name = "TestReadOnlyFail"
204
- logging.disable(logging.ERROR)
205
- job_result = create_job_result_and_run_job(module, name, commit=False)
206
- logging.disable(logging.NOTSET)
161
+ # Ensure DB transaction was not aborted
162
+ self.assertTrue(Status.objects.filter(name="Test database atomic rollback 1").exists())
163
+ # Ensure the correct job log messages were saved
164
+ job_logs = JobLogEntry.objects.filter(job_result=job_result).values_list("message", flat=True)
165
+ self.assertEqual(len(job_logs), 3)
166
+ self.assertIn("Running job", job_logs)
167
+ self.assertIn("Job succeeded.", job_logs)
168
+ self.assertIn("Job completed", job_logs)
169
+ self.assertNotIn("Job failed, all database changes have been rolled back.", job_logs)
170
+
171
+ def test_atomic_transaction_context_manager_job_pass(self):
172
+ """
173
+ Job with `with transaction.atomic()` context manager test with pass result.
174
+ """
175
+ module = "atomic_transaction"
176
+ name = "TestAtomicContextManager"
177
+ job_result = create_job_result_and_run_job(module, name)
178
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
179
+ # Ensure DB transaction was not aborted
180
+ self.assertTrue(Status.objects.filter(name="Test database atomic rollback 2").exists())
181
+ # Ensure the correct job log messages were saved
182
+ job_logs = JobLogEntry.objects.filter(job_result=job_result).values_list("message", flat=True)
183
+ self.assertEqual(len(job_logs), 3)
184
+ self.assertIn("Running job", job_logs)
185
+ self.assertIn("Job succeeded.", job_logs)
186
+ self.assertIn("Job completed", job_logs)
187
+ self.assertNotIn("Job failed, all database changes have been rolled back.", job_logs)
188
+
189
+ def test_atomic_transaction_decorator_job_fail(self):
190
+ """
191
+ Job with @transaction.atomic decorator test with fail result.
192
+ """
193
+ module = "atomic_transaction"
194
+ name = "TestAtomicDecorator"
195
+ job_result = create_job_result_and_run_job(module, name, fail=True)
207
196
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
208
- self.assertEqual(Location.objects.count(), 0) # Ensure DB transaction was aborted
209
- # Also ensure the standard log message about aborting the transaction is *not* present
210
- run_log = JobLogEntry.objects.filter(grouping="run")
211
- for log in run_log:
212
- self.assertNotEqual(log.message, "Database changes have been reverted due to error.")
213
-
214
- def test_read_only_no_commit_field(self):
215
- """
216
- Job read only test commit field is not shown.
217
- """
218
- module = "test_read_only_no_commit_field"
219
- name = "TestReadOnlyNoCommitField"
220
- job_class = get_job(f"local/{module}/{name}")
221
-
222
- form = job_class().as_form()
223
-
224
- self.assertInHTML(
225
- "<input id='id__commit' name='_commit' type='hidden' value='False'>",
226
- form.as_table(),
227
- )
197
+ # Ensure DB transaction was aborted
198
+ self.assertFalse(Status.objects.filter(name="Test database atomic rollback 1").exists())
199
+ # Ensure the correct job log messages were saved
200
+ job_logs = JobLogEntry.objects.filter(job_result=job_result).values_list("message", flat=True)
201
+ self.assertEqual(len(job_logs), 3)
202
+ self.assertIn("Running job", job_logs)
203
+ self.assertIn("Job failed, all database changes have been rolled back.", job_logs)
204
+ self.assertIn("Job completed", job_logs)
205
+ self.assertNotIn("Job succeeded.", job_logs)
206
+
207
+ def test_atomic_transaction_context_manager_job_fail(self):
208
+ """
209
+ Job with `with transaction.atomic()` context manager test with fail result.
210
+ """
211
+ module = "atomic_transaction"
212
+ name = "TestAtomicContextManager"
213
+ job_result = create_job_result_and_run_job(module, name, fail=True)
214
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
215
+ # Ensure DB transaction was aborted
216
+ self.assertFalse(Status.objects.filter(name="Test database atomic rollback 2").exists())
217
+ # Ensure the correct job log messages were saved
218
+ job_logs = JobLogEntry.objects.filter(job_result=job_result).values_list("message", flat=True)
219
+ self.assertEqual(len(job_logs), 3)
220
+ self.assertIn("Running job", job_logs)
221
+ self.assertIn("Job failed, all database changes have been rolled back.", job_logs)
222
+ self.assertIn("Job completed", job_logs)
223
+ self.assertNotIn("Job succeeded.", job_logs)
228
224
 
229
225
  def test_ip_address_vars(self):
230
226
  """
@@ -236,7 +232,7 @@ class JobTest(TransactionTestCase):
236
232
  - IPAddressWithMaskVar
237
233
  - IPNetworkVar
238
234
  """
239
- module = "test_ipaddress_vars"
235
+ module = "ipaddress_vars"
240
236
  name = "TestIPAddresses"
241
237
  job_class, _job_model = get_job_class_and_model(module, name)
242
238
 
@@ -255,7 +251,7 @@ class JobTest(TransactionTestCase):
255
251
  # Prepare the job data
256
252
  data = job_class.serialize_data(form.cleaned_data)
257
253
  # Need to pass a mock request object as execute_webhooks will be called with the creation of the objects.
258
- job_result = create_job_result_and_run_job(module, name, data=data, commit=False, request=self.request)
254
+ job_result = create_job_result_and_run_job(module, name, **data)
259
255
 
260
256
  log_info = JobLogEntry.objects.filter(
261
257
  job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run"
@@ -274,20 +270,34 @@ class JobTest(TransactionTestCase):
274
270
  """
275
271
  Test that an attempt is made at log redaction.
276
272
  """
277
- module = "test_log_redaction"
273
+ module = "log_redaction"
278
274
  name = "TestLogRedaction"
279
- job_result = create_job_result_and_run_job(module, name, data=None, commit=True, request=self.request)
275
+ job_result = create_job_result_and_run_job(module, name)
280
276
 
281
277
  logs = JobLogEntry.objects.filter(job_result=job_result, grouping="run")
282
278
  self.assertGreater(logs.count(), 0)
283
279
  for log in logs:
284
- self.assertEqual(log.message, "The secret is (redacted)")
280
+ if log.message != "Job completed":
281
+ self.assertEqual(log.message, "The secret is (redacted)")
282
+
283
+ def test_log_skip_db_logging(self):
284
+ """
285
+ Test that an attempt is made at log redaction.
286
+ """
287
+ module = "log_skip_db_logging"
288
+ name = "TestLogSkipDBLogging"
289
+ job_result = create_job_result_and_run_job(module, name)
290
+
291
+ logs = job_result.job_log_entries
292
+ self.assertGreater(logs.count(), 0)
293
+ self.assertFalse(logs.filter(message="I should NOT be logged to the database").exists())
294
+ self.assertTrue(logs.filter(message="I should be logged to the database").exists())
285
295
 
286
296
  def test_object_vars(self):
287
297
  """
288
298
  Test that Object variable fields behave as expected.
289
299
  """
290
- module = "test_object_vars"
300
+ module = "object_vars"
291
301
  name = "TestObjectVars"
292
302
 
293
303
  # Prepare the job data
@@ -298,10 +308,7 @@ class JobTest(TransactionTestCase):
298
308
  "role": {"name": role.name},
299
309
  "roles": [role.pk],
300
310
  }
301
- job_result = create_job_result_and_run_job(module, name, data=data, commit=False, request=self.request)
302
-
303
- # Test storing additional data in job
304
- job_result_data = job_result.data["object_vars"]
311
+ job_result = create_job_result_and_run_job(module, name, **data)
305
312
 
306
313
  info_log = JobLogEntry.objects.filter(
307
314
  job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run"
@@ -309,19 +316,18 @@ class JobTest(TransactionTestCase):
309
316
 
310
317
  # Assert stuff
311
318
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
312
- self.assertEqual({"role": str(role.pk), "roles": [str(role.pk)]}, job_result_data)
313
319
  self.assertEqual(info_log.log_object, "")
314
320
  self.assertEqual(info_log.message, f"Role: {role.name}")
315
- self.assertEqual(job_result.data["output"], "\nNice Roles!")
321
+ self.assertEqual(job_result.result, "Nice Roles!")
316
322
 
317
323
  def test_optional_object_var(self):
318
324
  """
319
325
  Test that an optional Object variable field behaves as expected.
320
326
  """
321
- module = "test_object_var_optional"
327
+ module = "object_var_optional"
322
328
  name = "TestOptionalObjectVar"
323
329
  data = {"location": None}
324
- job_result = create_job_result_and_run_job(module, name, data=data, commit=True, request=self.request)
330
+ job_result = create_job_result_and_run_job(module, name, **data)
325
331
 
326
332
  info_log = JobLogEntry.objects.filter(
327
333
  job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run"
@@ -331,52 +337,30 @@ class JobTest(TransactionTestCase):
331
337
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
332
338
  self.assertEqual(info_log.log_object, "")
333
339
  self.assertEqual(info_log.message, "The Location if any that the user provided.")
334
- self.assertEqual(job_result.data["output"], "\nNice Location (or not)!")
340
+ self.assertEqual(job_result.result, "Nice Location (or not)!")
335
341
 
336
342
  def test_required_object_var(self):
337
343
  """
338
344
  Test that a required Object variable field behaves as expected.
339
345
  """
340
- module = "test_object_var_required"
346
+ module = "object_var_required"
341
347
  name = "TestRequiredObjectVar"
342
348
  data = {"location": None}
343
- logging.disable(logging.ERROR)
344
- job_result = create_job_result_and_run_job(module, name, data=data, commit=False)
345
- logging.disable(logging.NOTSET)
349
+ job_result = create_job_result_and_run_job(module, name, **data)
346
350
 
347
351
  # Assert stuff
348
352
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
349
- log_failure = JobLogEntry.objects.filter(
350
- grouping="initialization", log_level=LogLevelChoices.LOG_FAILURE
351
- ).first()
352
- self.assertIn("location is a required field", log_failure.message)
353
-
354
- def test_job_data_as_string(self):
355
- """
356
- Test that job doesn't error when not a dictionary.
357
- """
358
- module = "test_object_vars"
359
- name = "TestObjectVars"
360
- data = "BAD DATA STRING"
361
- logging.disable(logging.ERROR)
362
- job_result = create_job_result_and_run_job(module, name, data=data, commit=False)
363
- logging.disable(logging.NOTSET)
364
- # Assert stuff
365
- self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
366
- log_failure = JobLogEntry.objects.filter(
367
- grouping="initialization", log_level=LogLevelChoices.LOG_FAILURE
368
- ).first()
369
- self.assertIn("Data should be a dictionary", log_failure.message)
353
+ self.assertIn("location is a required field", job_result.traceback)
370
354
 
371
355
  def test_job_latest_result_property(self):
372
356
  """
373
357
  Job test to see if the latest_result property is indeed returning the most recent job result
374
358
  """
375
- module = "test_pass"
359
+ module = "pass"
376
360
  name = "TestPass"
377
- job_result_1 = create_job_result_and_run_job(module, name, commit=False)
361
+ job_result_1 = create_job_result_and_run_job(module, name)
378
362
  self.assertEqual(job_result_1.status, JobResultStatusChoices.STATUS_SUCCESS)
379
- job_result_2 = create_job_result_and_run_job(module, name, commit=False)
363
+ job_result_2 = create_job_result_and_run_job(module, name)
380
364
  self.assertEqual(job_result_2.status, JobResultStatusChoices.STATUS_SUCCESS)
381
365
  _job_class, job_model = get_job_class_and_model(module, name)
382
366
  self.assertGreaterEqual(job_model.job_results.count(), 2)
@@ -388,7 +372,7 @@ class JobTest(TransactionTestCase):
388
372
  """
389
373
  Test job form with custom task queues defined on the job class
390
374
  """
391
- module = "test_task_queues"
375
+ module = "task_queues"
392
376
  name = "TestWorkerQueues"
393
377
  mock_get_celery_queues.return_value = {"celery": 4, "irrelevant": 5}
394
378
  job_class, _ = get_job_class_and_model(module, name)
@@ -398,10 +382,8 @@ class JobTest(TransactionTestCase):
398
382
  <td><select name="_task_queue" class="form-control" placeholder="Task queue" id="id__task_queue">
399
383
  <option value="celery">celery (4 workers)</option>
400
384
  <option value="nonexistent">nonexistent (0 workers)</option></select><br>
401
- <span class="helptext">The task queue to route this job to</span></td></tr>
402
- <tr><th><label for="id__commit">Commit changes:</label></th>
403
- <td><input type="checkbox" name="_commit" placeholder="Commit changes" id="id__commit" checked><br>
404
- <span class="helptext">Commit changes to the database (uncheck for a dry-run)</span></td></tr>""",
385
+ <span class="helptext">The task queue to route this job to</span>
386
+ <input type="hidden" name="_profile" value="False" id="id__profile"></td></tr>""",
405
387
  form.as_table(),
406
388
  )
407
389
 
@@ -410,7 +392,7 @@ class JobTest(TransactionTestCase):
410
392
  """
411
393
  Test job form with custom task queues defined on the job class and overridden on the model
412
394
  """
413
- module = "test_task_queues"
395
+ module = "task_queues"
414
396
  name = "TestWorkerQueues"
415
397
  mock_get_celery_queues.return_value = {"default": 1, "irrelevant": 5}
416
398
  job_class, job_model = get_job_class_and_model(module, name)
@@ -423,13 +405,64 @@ class JobTest(TransactionTestCase):
423
405
  <td><select name="_task_queue" class="form-control" placeholder="Task queue" id="id__task_queue">
424
406
  <option value="default">default (1 worker)</option>
425
407
  <option value="priority">priority (0 workers)</option>
426
- </select><br><span class="helptext">The task queue to route this job to</span></td></tr>
427
- <tr><th><label for="id__commit">Commit changes:</label></th>
428
- <td><input type="checkbox" name="_commit" placeholder="Commit changes" id="id__commit" checked><br>
429
- <span class="helptext">Commit changes to the database (uncheck for a dry-run)</span></td></tr>""",
408
+ </select><br><span class="helptext">The task queue to route this job to</span>
409
+ <input type="hidden" name="_profile" value="False" id="id__profile"></td></tr>""",
430
410
  form.as_table(),
431
411
  )
432
412
 
413
+ def test_supports_dryrun(self):
414
+ """
415
+ Test job class supports_dryrun field and job model supports_dryrun field
416
+ """
417
+
418
+ module = "dry_run"
419
+ name = "TestDryRun"
420
+ job_class, job_model = get_job_class_and_model(module, name)
421
+ self.assertTrue(job_class.supports_dryrun)
422
+ self.assertTrue(job_model.supports_dryrun)
423
+
424
+ module = "pass"
425
+ name = "TestPass"
426
+ job_class, job_model = get_job_class_and_model(module, name)
427
+ self.assertFalse(job_class.supports_dryrun)
428
+ self.assertFalse(job_model.supports_dryrun)
429
+
430
+ def test_job_profiling(self):
431
+ module = "profiling"
432
+ name = "TestProfilingJob"
433
+
434
+ # The job itself contains the 'assert' by loading the resulting profiling file from the workers filesystem
435
+ job_result = create_job_result_and_run_job(module, name, profile=True)
436
+
437
+ self.assertEqual(
438
+ job_result.status,
439
+ JobResultStatusChoices.STATUS_SUCCESS,
440
+ msg="Profiling test job errored, this indicates that either no profiling file was created or it is malformed.",
441
+ )
442
+
443
+ profiling_result = Path(f"/tmp/nautobot-jobresult-{job_result.pk}.pstats")
444
+ self.assertTrue(profiling_result.exists())
445
+ profiling_result.unlink()
446
+
447
+ def test_dryrun_default(self):
448
+ """Test that dryrun_default is reflected in job form."""
449
+ module = "dry_run"
450
+ name = "TestDryRun"
451
+ job_class, job_model = get_job_class_and_model(module, name)
452
+
453
+ # not overridden on job model, initial form field value should match job class
454
+ job_model.dryrun_default_override = False
455
+ job_model.save()
456
+ form = job_class().as_form()
457
+ self.assertEqual(form.fields["dryrun"].initial, job_class.dryrun_default)
458
+
459
+ # overridden on job model, initial form field value should match job model
460
+ job_model.dryrun_default_override = True
461
+ job_model.dryrun_default = not job_class.dryrun_default
462
+ job_model.save()
463
+ form = job_class().as_form()
464
+ self.assertEqual(form.fields["dryrun"].initial, job_model.dryrun_default)
465
+
433
466
 
434
467
  class JobFileUploadTest(TransactionTestCase):
435
468
  """Test a job that uploads/deletes files."""
@@ -449,7 +482,7 @@ class JobFileUploadTest(TransactionTestCase):
449
482
 
450
483
  def test_run_job_pass(self):
451
484
  """Test that file upload succeeds; job SUCCEEDS; and files are deleted."""
452
- module = "test_file_upload_pass"
485
+ module = "file_upload_pass"
453
486
  name = "TestFileUploadPass"
454
487
  job_class, _job_model = get_job_class_and_model(module, name)
455
488
 
@@ -465,9 +498,7 @@ class JobFileUploadTest(TransactionTestCase):
465
498
  self.assertEqual(FileProxy.objects.count(), 1)
466
499
 
467
500
  # Run the job
468
- job_result = create_job_result_and_run_job(
469
- module, name, data=serialized_data, commit=False, request=self.request
470
- )
501
+ job_result = create_job_result_and_run_job(module, name, **serialized_data)
471
502
 
472
503
  warning_log = JobLogEntry.objects.filter(
473
504
  job_result=job_result, log_level=LogLevelChoices.LOG_WARNING, grouping="run"
@@ -481,7 +512,7 @@ class JobFileUploadTest(TransactionTestCase):
481
512
 
482
513
  def test_run_job_fail(self):
483
514
  """Test that file upload succeeds; job FAILS; files deleted."""
484
- module = "test_file_upload_fail"
515
+ module = "file_upload_fail"
485
516
  name = "TestFileUploadFail"
486
517
  job_class, _job_model = get_job_class_and_model(module, name)
487
518
 
@@ -497,13 +528,11 @@ class JobFileUploadTest(TransactionTestCase):
497
528
  self.assertEqual(FileProxy.objects.count(), 1)
498
529
 
499
530
  # Run the job
500
- logging.disable(logging.ERROR)
501
- job_result = create_job_result_and_run_job(module, name, data=serialized_data, commit=False)
531
+ job_result = create_job_result_and_run_job(module, name, **serialized_data)
502
532
  self.assertIsNotNone(job_result.traceback)
503
533
  # TODO(jathan): If there are more use-cases for asserting class comparison for errors raised
504
534
  # by Jobs, factor this into a test case method.
505
535
  self.assertIn(job_class.exception.__name__, job_result.traceback)
506
- logging.disable(logging.NOTSET)
507
536
 
508
537
  # Assert that file contents were correctly read
509
538
  self.assertEqual(
@@ -512,13 +541,6 @@ class JobFileUploadTest(TransactionTestCase):
512
541
  .message,
513
542
  f"File contents: {self.file_contents}",
514
543
  )
515
- # Also ensure the standard log message about aborting the transaction is present
516
- self.assertEqual(
517
- JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run")
518
- .first()
519
- .message,
520
- "Database changes have been reverted due to error.",
521
- )
522
544
 
523
545
  # Assert that FileProxy was cleaned up
524
546
  self.assertEqual(FileProxy.objects.count(), 0)
@@ -541,80 +563,47 @@ class RunJobManagementCommandTest(TransactionTestCase):
541
563
 
542
564
  def test_runjob_nochange_successful(self):
543
565
  """Basic success-path test for Jobs that don't modify the Nautobot database."""
544
- module = "test_pass"
566
+ module = "pass"
545
567
  name = "TestPass"
546
568
  _job_class, job_model = get_job_class_and_model(module, name)
547
569
 
548
- out, err = self.run_command("--no-color", job_model.class_path)
570
+ out, err = self.run_command("--local", "--no-color", "--username", self.user.username, job_model.class_path)
549
571
  self.assertIn(f"Running {job_model.class_path}...", out)
550
- self.assertIn(f"{module}: 1 success, 1 info, 0 warning, 0 failure", out)
551
- self.assertIn("success: None", out)
552
- self.assertIn("info: Database changes have been reverted automatically.", out)
572
+ self.assertIn("run: 0 debug, 1 info, 0 warning, 0 error, 0 critical", out)
573
+ self.assertIn("info: Success", out)
553
574
  self.assertIn(f"{job_model.class_path}: SUCCESS", out)
554
575
  self.assertEqual("", err)
555
576
 
556
- def test_runjob_db_change_no_commit(self):
557
- """A job that changes the DB, when run with commit=False, doesn't modify the database."""
558
- with self.assertRaises(ObjectDoesNotExist):
559
- Status.objects.get(name="test status")
560
-
561
- module = "test_modify_db"
562
- name = "TestModifyDB"
563
- _job_class, job_model = get_job_class_and_model(module, name)
564
-
565
- out, err = self.run_command("--no-color", job_model.class_path)
566
- self.assertIn(f"Running {job_model.class_path}...", out)
567
- self.assertIn(f"{module}: 1 success, 1 info, 0 warning, 0 failure", out)
568
- self.assertIn("success: Test Status: Status created successfully.", out)
569
- self.assertIn("info: Database changes have been reverted automatically.", out)
570
- self.assertIn(f"{job_model.class_path}: SUCCESS", out)
571
- self.assertEqual("", err)
572
-
573
- with self.assertRaises(ObjectDoesNotExist):
574
- Status.objects.get(name="test status")
575
-
576
- info_log = JobLogEntry.objects.filter(log_level=LogLevelChoices.LOG_INFO).first()
577
- self.assertEqual("Database changes have been reverted automatically.", info_log.message)
578
-
579
- def test_runjob_db_change_commit_no_username(self):
580
- """A job that changes the DB, when run with commit=True but no username, is rejected."""
581
- module = "test_modify_db"
582
- name = "TestModifyDB"
583
- _job_class, job_model = get_job_class_and_model(module, name)
584
- with self.assertRaises(CommandError):
585
- self.run_command("--commit", job_model.class_path)
586
-
587
- def test_runjob_db_change_commit_wrong_username(self):
588
- """A job that changes the DB, when run with commit=True and a nonexistent username, is rejected."""
589
- module = "test_modify_db"
577
+ def test_runjob_wrong_username(self):
578
+ """A job when run with a nonexistent username, is rejected."""
579
+ module = "modify_db"
590
580
  name = "TestModifyDB"
591
581
  _job_class, job_model = get_job_class_and_model(module, name)
592
582
  with self.assertRaises(CommandError):
593
- self.run_command("--commit", "--username", "nosuchuser", job_model.class_path)
594
-
595
- def test_runjob_db_change_commit_and_username(self):
596
- """A job that changes the DB, when run with commit=True and a username, successfully updates the DB."""
597
- get_user_model().objects.create(username="test_user")
583
+ self.run_command("--username", "nosuchuser", job_model.class_path)
598
584
 
599
- module = "test_modify_db"
585
+ def test_runjob_db_change(self):
586
+ """A job that changes the DB, successfully updates the DB."""
587
+ module = "modify_db"
600
588
  name = "TestModifyDB"
601
589
  _job_class, job_model = get_job_class_and_model(module, name)
602
590
 
603
- out, err = self.run_command("--no-color", "--commit", "--username", "test_user", job_model.class_path)
591
+ out, err = self.run_command("--local", "--no-color", "--username", self.user.username, job_model.class_path)
604
592
  self.assertIn(f"Running {job_model.class_path}...", out)
605
593
  # Changed job to actually log data. Can't display empty results if no logs were created.
606
- self.assertIn(f"{module}: 1 success, 0 info, 0 warning, 0 failure", out)
594
+ self.assertIn("run: 0 debug, 1 info, 0 warning, 0 error, 0 critical", out)
607
595
  self.assertIn(f"{job_model.class_path}: SUCCESS", out)
608
596
  self.assertEqual("", err)
609
597
 
610
- success_log = JobLogEntry.objects.filter(log_level=LogLevelChoices.LOG_SUCCESS).first()
611
- self.assertEqual(success_log.message, "Status created successfully.")
598
+ success_log = JobLogEntry.objects.filter(
599
+ log_level=LogLevelChoices.LOG_INFO, message="Status created successfully."
600
+ )
601
+ self.assertTrue(success_log.exists())
602
+ self.assertEqual(success_log.count(), 1)
612
603
 
613
604
  status = Status.objects.get(name="Test Status")
614
605
  self.assertEqual(status.name, "Test Status")
615
606
 
616
- status.delete()
617
-
618
607
 
619
608
  class JobLocationCustomFieldTest(TransactionTestCase):
620
609
  """Test a job that creates a location and a custom field."""
@@ -629,9 +618,9 @@ class JobLocationCustomFieldTest(TransactionTestCase):
629
618
  self.request.user = self.user
630
619
 
631
620
  def test_run(self):
632
- module = "test_location_with_custom_field"
621
+ module = "location_with_custom_field"
633
622
  name = "TestCreateLocationWithCustomField"
634
- job_result = create_job_result_and_run_job(module, name, request=self.request, commit=True)
623
+ job_result = create_job_result_and_run_job(module, name)
635
624
  job_result.refresh_from_db()
636
625
 
637
626
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
@@ -639,13 +628,17 @@ class JobLocationCustomFieldTest(TransactionTestCase):
639
628
  # Test location with a value for custom_field
640
629
  location_1 = Location.objects.filter(slug="test-location-one")
641
630
  self.assertEqual(location_1.count(), 1)
631
+ location_1 = location_1.first()
642
632
  self.assertEqual(CustomField.objects.filter(label="cf1").count(), 1)
643
- self.assertEqual(location_1[0].cf["cf1"], "some-value")
633
+ self.assertIn("cf1", location_1.cf)
634
+ self.assertEqual(location_1.cf["cf1"], "some-value")
644
635
 
645
636
  # Test location with default value for custom field
646
637
  location_2 = Location.objects.filter(slug="test-location-two")
647
638
  self.assertEqual(location_2.count(), 1)
648
- self.assertEqual(location_2[0].cf["cf1"], "-")
639
+ location_2 = location_2.first()
640
+ self.assertIn("cf1", location_2.cf)
641
+ self.assertEqual(location_2.cf["cf1"], "-")
649
642
 
650
643
 
651
644
  class JobButtonReceiverTest(TransactionTestCase):
@@ -662,7 +655,10 @@ class JobButtonReceiverTest(TransactionTestCase):
662
655
  self.request.user = self.user
663
656
 
664
657
  self.location_type = LocationType.objects.create(name="Test Root Type 2")
665
- self.location = Location.objects.create(name="Test Job Button Location 1", location_type=self.location_type)
658
+ status = Status.objects.get_for_model(Location).first()
659
+ self.location = Location.objects.create(
660
+ name="Test Job Button Location 1", location_type=self.location_type, status=status
661
+ )
666
662
  content_type = ContentType.objects.get_for_model(Location)
667
663
  self.data = {
668
664
  "object_pk": self.location.pk,
@@ -670,37 +666,37 @@ class JobButtonReceiverTest(TransactionTestCase):
670
666
  }
671
667
 
672
668
  def test_form_field(self):
673
- module = "test_job_button_receiver"
669
+ module = "job_button_receiver"
674
670
  name = "TestJobButtonReceiverSimple"
675
671
  job_class, _job_model = get_job_class_and_model(module, name)
676
672
  form = job_class().as_form()
677
- self.assertSequenceEqual(list(form.fields.keys()), ["object_pk", "object_model_name", "_task_queue", "_commit"])
673
+ self.assertSequenceEqual(
674
+ list(form.fields.keys()), ["object_pk", "object_model_name", "_task_queue", "_profile"]
675
+ )
678
676
 
679
677
  def test_hidden(self):
680
- module = "test_job_button_receiver"
678
+ module = "job_button_receiver"
681
679
  name = "TestJobButtonReceiverSimple"
682
680
  _job_class, job_model = get_job_class_and_model(module, name)
683
681
  self.assertFalse(job_model.hidden)
684
682
 
685
683
  def test_is_job_button(self):
686
684
  with self.subTest(expected=False):
687
- module = "test_pass"
685
+ module = "pass"
688
686
  name = "TestPass"
689
687
  _job_class, job_model = get_job_class_and_model(module, name)
690
688
  self.assertFalse(job_model.is_job_button_receiver)
691
689
 
692
690
  with self.subTest(expected=True):
693
- module = "test_job_button_receiver"
691
+ module = "job_button_receiver"
694
692
  name = "TestJobButtonReceiverSimple"
695
693
  _job_class, job_model = get_job_class_and_model(module, name)
696
694
  self.assertTrue(job_model.is_job_button_receiver)
697
695
 
698
696
  def test_missing_receive_job_button_method(self):
699
- module = "test_job_button_receiver"
697
+ module = "job_button_receiver"
700
698
  name = "TestJobButtonReceiverFail"
701
- logging.disable(logging.ERROR)
702
- job_result = create_job_result_and_run_job(module, name, data=self.data, commit=False)
703
- logging.disable(logging.NOTSET)
699
+ job_result = create_job_result_and_run_job(module, name, **self.data)
704
700
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
705
701
 
706
702
 
@@ -720,53 +716,52 @@ class JobHookReceiverTest(TransactionTestCase):
720
716
  # generate an ObjectChange by creating a new location
721
717
  with web_request_context(self.user):
722
718
  location_type = LocationType.objects.create(name="Test Root Type 1")
723
- location = Location(name="Test Location 1", location_type=location_type)
719
+ status = Status.objects.get_for_model(Location).first()
720
+ location = Location(name="Test Location 1", location_type=location_type, status=status)
724
721
  location.save()
725
722
  location.refresh_from_db()
726
723
  oc = get_changes_for_model(location).first()
727
724
  self.data = {"object_change": oc.id}
728
725
 
729
726
  def test_form_field(self):
730
- module = "test_job_hook_receiver"
727
+ module = "job_hook_receiver"
731
728
  name = "TestJobHookReceiverLog"
732
729
  job_class, _job_model = get_job_class_and_model(module, name)
733
730
  form = job_class().as_form()
734
- self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_task_queue", "_commit"])
731
+ self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_task_queue", "_profile"])
735
732
 
736
733
  def test_hidden(self):
737
- module = "test_job_hook_receiver"
734
+ module = "job_hook_receiver"
738
735
  name = "TestJobHookReceiverLog"
739
736
  _job_class, job_model = get_job_class_and_model(module, name)
740
737
  self.assertFalse(job_model.hidden)
741
738
 
742
739
  def test_is_job_hook(self):
743
740
  with self.subTest(expected=False):
744
- module = "test_pass"
741
+ module = "pass"
745
742
  name = "TestPass"
746
743
  _job_class, job_model = get_job_class_and_model(module, name)
747
744
  self.assertFalse(job_model.is_job_hook_receiver)
748
745
 
749
746
  with self.subTest(expected=True):
750
- module = "test_job_hook_receiver"
747
+ module = "job_hook_receiver"
751
748
  name = "TestJobHookReceiverLog"
752
749
  _job_class, job_model = get_job_class_and_model(module, name)
753
750
  self.assertTrue(job_model.is_job_hook_receiver)
754
751
 
755
752
  def test_object_change_context(self):
756
- module = "test_job_hook_receiver"
753
+ module = "job_hook_receiver"
757
754
  name = "TestJobHookReceiverChange"
758
- create_job_result_and_run_job(module, name, data=self.data, request=self.request)
755
+ job_result = create_job_result_and_run_job(module, name, **self.data)
759
756
  test_location = Location.objects.get(name="test_jhr")
760
757
  oc = get_changes_for_model(test_location).first()
761
758
  self.assertEqual(oc.change_context, ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK)
762
- self.assertEqual(oc.user_id, self.user.pk)
759
+ self.assertEqual(oc.user_id, job_result.user.pk)
763
760
 
764
761
  def test_missing_receive_job_hook_method(self):
765
- module = "test_job_hook_receiver"
762
+ module = "job_hook_receiver"
766
763
  name = "TestJobHookReceiverFail"
767
- logging.disable(logging.ERROR)
768
- job_result = create_job_result_and_run_job(module, name, data=self.data, commit=False)
769
- logging.disable(logging.NOTSET)
764
+ job_result = create_job_result_and_run_job(module, name, **self.data)
770
765
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
771
766
 
772
767
 
@@ -778,7 +773,7 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
778
773
  def setUp(self):
779
774
  super().setUp()
780
775
 
781
- module = "test_job_hook_receiver"
776
+ module = "job_hook_receiver"
782
777
  name = "TestJobHookReceiverLog"
783
778
  self.job_class, self.job_model = get_job_class_and_model(module, name)
784
779
  job_hook = JobHook(
@@ -793,13 +788,16 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
793
788
 
794
789
  def test_enqueue_job_hook(self):
795
790
  with web_request_context(user=self.user):
796
- Location.objects.create(name="Test Job Hook Location 1", location_type=self.location_type)
791
+ status = Status.objects.get_for_model(Location).first()
792
+ Location.objects.create(name="Test Job Hook Location 1", location_type=self.location_type, status=status)
797
793
  job_result = JobResult.objects.get(job_model=self.job_model)
798
794
  expected_log_messages = [
795
+ ("info", "Running job"),
799
796
  ("info", f"change: dcim | location Test Job Hook Location 1 created by {self.user.username}"),
800
797
  ("info", "action: create"),
801
- ("info", f"request.user: {self.user.username}"),
802
- ("success", "Test Job Hook Location 1"),
798
+ ("info", f"jobresult.user: {self.user.username}"),
799
+ ("info", "Test Job Hook Location 1"),
800
+ ("info", "Job completed"),
803
801
  ]
804
802
  log_messages = JobLogEntry.objects.filter(job_result=job_result).values_list("log_level", "message")
805
803
  self.assertSequenceEqual(log_messages, expected_log_messages)
@@ -807,8 +805,9 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
807
805
  @mock.patch.object(JobResult, "enqueue_job")
808
806
  def test_enqueue_job_hook_skipped(self, mock_enqueue_job):
809
807
  change_context = JobHookChangeContext(user=self.user)
808
+ status = Status.objects.get_for_model(Location).first()
810
809
  with change_logging(change_context):
811
- Location.objects.create(name="Test Job Hook Location 2", location_type=self.location_type)
810
+ Location.objects.create(name="Test Job Hook Location 2", location_type=self.location_type, status=status)
812
811
 
813
812
  self.assertFalse(mock_enqueue_job.called)
814
813
 
@@ -818,8 +817,8 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
818
817
  for i in range(1, 7):
819
818
  ScheduledJob.objects.create(
820
819
  name=f"test{i}",
821
- task="nautobot.extras.jobs.scheduled_job_handler",
822
- job_class="local/test_pass/TestPass",
820
+ task="pass.TestPass",
821
+ job_class="pass.TestPass",
823
822
  interval=JobExecutionType.TYPE_FUTURE,
824
823
  user=self.user,
825
824
  start_time=timezone.now() - datetime.timedelta(days=i * 30),
@@ -828,8 +827,8 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
828
827
 
829
828
  ScheduledJob.objects.create(
830
829
  name="test7",
831
- task="nautobot.extras.jobs.scheduled_job_handler",
832
- job_class="local/test_pass/TestPass",
830
+ task="pass.TestPass",
831
+ job_class="pass.TestPass",
833
832
  interval=JobExecutionType.TYPE_DAILY,
834
833
  user=self.user,
835
834
  start_time=timezone.now() - datetime.timedelta(days=180),
@@ -857,8 +856,8 @@ class ScheduledJobIntervalTestCase(TestCase):
857
856
  start_time = timezone.now() + datetime.timedelta(days=6)
858
857
  scheduled_job = ScheduledJob.objects.create(
859
858
  name="weekly_interval",
860
- task="nautobot.extras.jobs.scheduled_job_handler",
861
- job_class="local/test_pass/TestPass",
859
+ task="pass.TestPass",
860
+ job_class="pass.TestPass",
862
861
  interval=JobExecutionType.TYPE_WEEKLY,
863
862
  user=self.user,
864
863
  start_time=start_time,