nautobot 2.0.0a2__py3-none-any.whl → 2.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1029) hide show
  1. nautobot/__init__.py +1 -5
  2. nautobot/apps/api.py +6 -8
  3. nautobot/apps/forms.py +0 -2
  4. nautobot/apps/ui.py +0 -8
  5. nautobot/circuits/api/serializers.py +9 -119
  6. nautobot/circuits/api/urls.py +1 -1
  7. nautobot/circuits/api/views.py +0 -1
  8. nautobot/circuits/choices.py +0 -2
  9. nautobot/circuits/filters.py +7 -6
  10. nautobot/circuits/forms.py +3 -73
  11. nautobot/circuits/migrations/0001_initial_part_1.py +0 -1
  12. nautobot/circuits/migrations/0002_initial_part_2.py +0 -1
  13. nautobot/circuits/migrations/0003_auto_slug.py +0 -1
  14. nautobot/circuits/migrations/0004_increase_provider_account_length.py +0 -1
  15. nautobot/circuits/migrations/0005_providernetwork.py +0 -1
  16. nautobot/circuits/migrations/0006_cache_circuit_terminations.py +0 -1
  17. nautobot/circuits/migrations/0007_circuitterminations_primary_model.py +0 -1
  18. nautobot/circuits/migrations/0008_add_natural_indexing.py +0 -1
  19. nautobot/circuits/migrations/0009_circuittermination_location.py +0 -1
  20. nautobot/circuits/migrations/0010_rename_foreign_keys_and_related_names.py +0 -1
  21. nautobot/circuits/migrations/0011_remove_site_foreign_key_from_circuit_termination_class.py +0 -1
  22. nautobot/circuits/migrations/0012_created_datetime.py +0 -1
  23. nautobot/circuits/migrations/0013_alter_circuittermination__path.py +0 -1
  24. nautobot/circuits/migrations/0014_related_name_changes.py +1 -2
  25. nautobot/circuits/migrations/0015_remove_circuittype_provider_slug.py +20 -0
  26. nautobot/circuits/migrations/0016_tagsfield.py +34 -0
  27. nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
  28. nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
  29. nautobot/circuits/models.py +3 -93
  30. nautobot/circuits/navigation.py +14 -69
  31. nautobot/circuits/signals.py +0 -2
  32. nautobot/circuits/tables.py +42 -5
  33. nautobot/circuits/templates/circuits/circuit_retrieve.html +1 -1
  34. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -1
  35. nautobot/circuits/templates/circuits/circuittype_retrieve.html +1 -1
  36. nautobot/circuits/templates/circuits/provider_create.html +0 -1
  37. nautobot/circuits/templates/circuits/provider_retrieve.html +1 -1
  38. nautobot/circuits/tests/integration/test_relationships.py +13 -16
  39. nautobot/circuits/tests/test_api.py +13 -43
  40. nautobot/circuits/tests/test_filters.py +20 -15
  41. nautobot/circuits/tests/test_models.py +7 -3
  42. nautobot/circuits/tests/test_views.py +57 -67
  43. nautobot/circuits/views.py +18 -9
  44. nautobot/core/api/__init__.py +8 -2
  45. nautobot/core/api/authentication.py +0 -3
  46. nautobot/core/api/fields.py +15 -6
  47. nautobot/core/api/filter_backends.py +3 -2
  48. nautobot/core/api/metadata.py +237 -30
  49. nautobot/core/api/mixins.py +94 -0
  50. nautobot/core/api/pagination.py +3 -3
  51. nautobot/core/api/parsers.py +154 -0
  52. nautobot/core/api/renderers.py +153 -2
  53. nautobot/core/api/schema.py +47 -3
  54. nautobot/core/api/serializers.py +377 -37
  55. nautobot/core/api/urls.py +11 -3
  56. nautobot/core/api/utils.py +174 -2
  57. nautobot/core/api/versioning.py +32 -10
  58. nautobot/core/api/views.py +266 -75
  59. nautobot/core/apps/__init__.py +138 -221
  60. nautobot/core/celery/__init__.py +112 -41
  61. nautobot/core/celery/backends.py +19 -13
  62. nautobot/core/celery/control.py +46 -0
  63. nautobot/core/celery/encoders.py +53 -0
  64. nautobot/core/celery/log.py +38 -0
  65. nautobot/core/celery/schedulers.py +23 -4
  66. nautobot/core/celery/task.py +1 -16
  67. nautobot/core/checks.py +0 -27
  68. nautobot/core/choices.py +21 -113
  69. nautobot/core/{cli.py → cli/__init__.py} +1 -2
  70. nautobot/core/cli/__main__.py +3 -0
  71. nautobot/core/constants.py +25 -43
  72. nautobot/core/context_processors.py +12 -0
  73. nautobot/core/filters.py +2 -2
  74. nautobot/core/forms/__init__.py +0 -4
  75. nautobot/core/forms/fields.py +39 -68
  76. nautobot/core/forms/forms.py +27 -27
  77. nautobot/core/forms/utils.py +7 -59
  78. nautobot/core/forms/widgets.py +0 -1
  79. nautobot/core/graphql/__init__.py +2 -2
  80. nautobot/core/graphql/schema.py +4 -27
  81. nautobot/core/jobs/__init__.py +75 -0
  82. nautobot/core/management/commands/build_ui.py +255 -0
  83. nautobot/core/management/commands/celery.py +0 -1
  84. nautobot/core/management/commands/generate_test_data.py +18 -13
  85. nautobot/core/management/commands/post_upgrade.py +24 -24
  86. nautobot/core/management/commands/validate_models.py +0 -1
  87. nautobot/core/middleware.py +0 -1
  88. nautobot/core/models/__init__.py +26 -1
  89. nautobot/core/models/fields.py +24 -5
  90. nautobot/core/models/generics.py +2 -46
  91. nautobot/core/models/managers.py +5 -0
  92. nautobot/core/models/name_color_content_types.py +1 -19
  93. nautobot/core/models/tree_queries.py +14 -4
  94. nautobot/core/models/utils.py +9 -10
  95. nautobot/core/models/validators.py +17 -8
  96. nautobot/core/releases.py +8 -10
  97. nautobot/core/settings.py +81 -53
  98. nautobot/core/tables.py +5 -5
  99. nautobot/core/tasks.py +4 -7
  100. nautobot/core/templates/base.html +1 -49
  101. nautobot/core/templates/base_django.html +49 -0
  102. nautobot/core/templates/base_react.html +55 -0
  103. nautobot/core/templates/buttons/export.html +6 -4
  104. nautobot/core/templates/generic/object_bulk_create.html +10 -21
  105. nautobot/core/templates/generic/object_list.html +4 -1
  106. nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
  107. nautobot/core/templates/inc/footer.html +1 -0
  108. nautobot/core/templates/inc/javascript.html +0 -14
  109. nautobot/core/templates/inc/nav_menu.html +28 -33
  110. nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
  111. nautobot/core/templates/inc/relationships_table_rows.html +2 -2
  112. nautobot/core/templates/nautobot_config.py.j2 +8 -25
  113. nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
  114. nautobot/core/templates/rest_framework/api.html +8 -0
  115. nautobot/core/templatetags/buttons.py +32 -29
  116. nautobot/core/templatetags/helpers.py +1 -1
  117. nautobot/core/testing/__init__.py +47 -44
  118. nautobot/core/testing/api.py +365 -47
  119. nautobot/core/testing/filters.py +12 -7
  120. nautobot/core/testing/integration.py +1 -1
  121. nautobot/core/testing/migrations.py +2 -0
  122. nautobot/core/testing/mixins.py +22 -12
  123. nautobot/core/testing/schema.py +2 -1
  124. nautobot/core/testing/views.py +28 -51
  125. nautobot/core/tests/integration/test_filters.py +17 -8
  126. nautobot/core/tests/integration/test_navbar.py +11 -34
  127. nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
  128. nautobot/core/tests/nautobot_config.py +2 -3
  129. nautobot/core/tests/runner.py +0 -1
  130. nautobot/core/tests/test_api.py +290 -24
  131. nautobot/core/tests/test_authentication.py +57 -14
  132. nautobot/core/tests/test_checks.py +0 -7
  133. nautobot/core/tests/test_choices.py +0 -1
  134. nautobot/core/tests/test_filters.py +117 -110
  135. nautobot/core/tests/test_forms.py +47 -110
  136. nautobot/core/tests/test_graphql.py +158 -135
  137. nautobot/core/tests/test_logging.py +4 -1
  138. nautobot/core/tests/test_managers.py +3 -5
  139. nautobot/core/tests/test_models.py +2 -0
  140. nautobot/core/tests/test_ordering.py +0 -2
  141. nautobot/core/tests/test_paginator.py +3 -1
  142. nautobot/core/tests/test_releases.py +12 -12
  143. nautobot/core/tests/test_templatetags_helpers.py +7 -4
  144. nautobot/core/tests/test_utils.py +112 -78
  145. nautobot/core/tests/test_views.py +12 -17
  146. nautobot/core/tests/test_views_utils.py +6 -9
  147. nautobot/core/utils/data.py +17 -0
  148. nautobot/core/utils/deprecation.py +13 -20
  149. nautobot/core/utils/filtering.py +53 -9
  150. nautobot/core/utils/git.py +12 -4
  151. nautobot/core/utils/lookup.py +3 -1
  152. nautobot/core/utils/requests.py +23 -116
  153. nautobot/core/views/__init__.py +1 -2
  154. nautobot/core/views/generic.py +131 -119
  155. nautobot/core/views/mixins.py +53 -62
  156. nautobot/core/views/paginator.py +0 -1
  157. nautobot/core/views/renderers.py +14 -12
  158. nautobot/core/views/utils.py +87 -4
  159. nautobot/dcim/api/serializers.py +160 -672
  160. nautobot/dcim/api/urls.py +1 -1
  161. nautobot/dcim/api/views.py +7 -46
  162. nautobot/dcim/choices.py +2 -25
  163. nautobot/dcim/elevations.py +0 -1
  164. nautobot/dcim/factory.py +15 -4
  165. nautobot/dcim/filters/__init__.py +42 -13
  166. nautobot/dcim/form_mixins.py +1 -27
  167. nautobot/dcim/forms.py +58 -797
  168. nautobot/dcim/management/commands/trace_paths.py +0 -1
  169. nautobot/dcim/migrations/0001_initial_part_1.py +0 -1
  170. nautobot/dcim/migrations/0002_initial_part_2.py +0 -1
  171. nautobot/dcim/migrations/0003_initial_part_3.py +0 -1
  172. nautobot/dcim/migrations/0004_initial_part_4.py +0 -1
  173. nautobot/dcim/migrations/0005_device_local_context_schema.py +0 -1
  174. nautobot/dcim/migrations/0006_auto_slug.py +0 -1
  175. nautobot/dcim/migrations/0007_device_secrets_group.py +0 -1
  176. nautobot/dcim/migrations/0008_increase_all_serial_lengths.py +0 -1
  177. nautobot/dcim/migrations/0009_add_natural_indexing.py +0 -1
  178. nautobot/dcim/migrations/0010_interface_status.py +0 -1
  179. nautobot/dcim/migrations/0011_interface_status_data_migration.py +0 -1
  180. nautobot/dcim/migrations/0012_interface_parent_bridge.py +0 -1
  181. nautobot/dcim/migrations/0013_location_location_type.py +0 -1
  182. nautobot/dcim/migrations/0014_location_status_data_migration.py +0 -1
  183. nautobot/dcim/migrations/0015_device_components__changeloggedmodel.py +0 -1
  184. nautobot/dcim/migrations/0016_device_components__timestamp_data_migration.py +0 -1
  185. nautobot/dcim/migrations/0017_locationtype_nestable.py +0 -1
  186. nautobot/dcim/migrations/0018_device_redundancy_group.py +0 -1
  187. nautobot/dcim/migrations/0019_device_redundancy_group_data_migration.py +0 -1
  188. nautobot/dcim/migrations/0020_move_site_fields_to_location_model.py +0 -1
  189. nautobot/dcim/migrations/0021_mptt_to_tree_queries.py +0 -1
  190. nautobot/dcim/migrations/0022_interface_mac_address_data_migration.py +0 -1
  191. nautobot/dcim/migrations/0023_alter_interface_mac_address.py +0 -1
  192. nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -2
  193. nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -14
  194. nautobot/dcim/migrations/0026_rename_device_and_rack_role.py +0 -1
  195. nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -2
  196. nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -2
  197. nautobot/dcim/migrations/0029_add_tree_managers_and_foreign_keys_pre_data_migration.py +0 -1
  198. nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
  199. nautobot/dcim/migrations/0031_rename_path_end_point_related_name.py +0 -1
  200. nautobot/dcim/migrations/0032_remove_site_foreign_key_from_dcim_models.py +0 -1
  201. nautobot/dcim/migrations/0033_created_datetime.py +0 -1
  202. nautobot/dcim/migrations/0034_fixup_fks_and_related_names.py +0 -1
  203. nautobot/dcim/migrations/0035_related_name_changes.py +1 -2
  204. nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -2
  205. nautobot/dcim/migrations/0037_interface_ip_addresses_m2m.py +0 -1
  206. nautobot/dcim/migrations/0038_alter_location_managers.py +0 -1
  207. nautobot/dcim/migrations/0039_remove_slug.py +24 -0
  208. nautobot/dcim/migrations/0040_tagsfield.py +109 -0
  209. nautobot/dcim/migrations/0041_ipam__namespaces.py +25 -0
  210. nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
  211. nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
  212. nautobot/dcim/models/cables.py +4 -35
  213. nautobot/dcim/models/device_component_templates.py +7 -2
  214. nautobot/dcim/models/device_components.py +26 -203
  215. nautobot/dcim/models/devices.py +30 -152
  216. nautobot/dcim/models/locations.py +3 -64
  217. nautobot/dcim/models/power.py +3 -51
  218. nautobot/dcim/models/racks.py +7 -86
  219. nautobot/dcim/navigation.py +141 -467
  220. nautobot/dcim/signals.py +0 -2
  221. nautobot/dcim/tables/devices.py +8 -5
  222. nautobot/dcim/tables/devicetypes.py +1 -1
  223. nautobot/dcim/tables/locations.py +2 -2
  224. nautobot/dcim/tables/power.py +2 -2
  225. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
  226. nautobot/dcim/templates/dcim/device.html +15 -4
  227. nautobot/dcim/templates/dcim/device_edit.html +6 -0
  228. nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +0 -1
  229. nautobot/dcim/templates/dcim/devicetype.html +2 -2
  230. nautobot/dcim/templates/dcim/interface.html +4 -0
  231. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
  232. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  233. nautobot/dcim/templates/dcim/location.html +16 -1
  234. nautobot/dcim/templates/dcim/locationtype.html +15 -0
  235. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
  236. nautobot/dcim/templates/dcim/rackgroup.html +0 -12
  237. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  238. nautobot/dcim/tests/test_api.py +202 -130
  239. nautobot/dcim/tests/test_cablepaths.py +47 -42
  240. nautobot/dcim/tests/test_filters.py +156 -134
  241. nautobot/dcim/tests/test_forms.py +12 -213
  242. nautobot/dcim/tests/test_graphql.py +8 -3
  243. nautobot/dcim/tests/test_migrations.py +6 -11
  244. nautobot/dcim/tests/test_models.py +208 -158
  245. nautobot/dcim/tests/test_natural_ordering.py +12 -14
  246. nautobot/dcim/tests/test_signals.py +7 -4
  247. nautobot/dcim/tests/test_views.py +270 -264
  248. nautobot/dcim/urls.py +21 -26
  249. nautobot/dcim/views.py +14 -156
  250. nautobot/docs/additional-features/caching.md +6 -87
  251. nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
  252. nautobot/docs/additional-features/jobs.md +179 -197
  253. nautobot/docs/administration/nautobot-server.md +9 -24
  254. nautobot/docs/administration/nautobot-shell.md +6 -6
  255. nautobot/docs/administration/replicating-nautobot.md +0 -10
  256. nautobot/docs/configuration/index.md +9 -9
  257. nautobot/docs/configuration/optional-settings.md +32 -61
  258. nautobot/docs/configuration/required-settings.md +11 -52
  259. nautobot/docs/development/application-registry.md +2 -13
  260. nautobot/docs/development/best-practices.md +2 -1
  261. nautobot/docs/development/docker-compose-advanced-use-cases.md +1 -1
  262. nautobot/docs/development/extending-models.md +15 -17
  263. nautobot/docs/development/generic-views.md +0 -2
  264. nautobot/docs/development/getting-started.md +56 -6
  265. nautobot/docs/development/navigation-menu.md +22 -93
  266. nautobot/docs/development/react-ui.md +105 -0
  267. nautobot/docs/development/release-checklist.md +3 -3
  268. nautobot/docs/development/role-internals.md +1 -3
  269. nautobot/docs/development/style-guide.md +6 -4
  270. nautobot/docs/development/templates.md +2 -1
  271. nautobot/docs/docker/index.md +16 -14
  272. nautobot/docs/index.md +7 -3
  273. nautobot/docs/installation/index.md +4 -1
  274. nautobot/docs/installation/migrating-from-netbox.md +12 -43
  275. nautobot/docs/installation/migrating-from-postgresql.md +1 -1
  276. nautobot/docs/installation/nautobot.md +1 -1
  277. nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  278. nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  279. nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  280. nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  281. nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
  282. nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  283. nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  284. nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  285. nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  286. nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  287. nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  288. nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  289. nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  290. nautobot/docs/installation/upgrading-from-nautobot-v1.md +190 -636
  291. nautobot/docs/installation/upgrading.md +5 -2
  292. nautobot/docs/models/dcim/device.md +3 -0
  293. nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
  294. nautobot/docs/models/extras/computedfield.md +4 -4
  295. nautobot/docs/models/extras/dynamicgroup.md +9 -9
  296. nautobot/docs/models/extras/gitrepository.md +3 -0
  297. nautobot/docs/models/extras/job.md +1 -0
  298. nautobot/docs/models/extras/jobbutton.md +18 -13
  299. nautobot/docs/models/extras/jobhook.md +7 -4
  300. nautobot/docs/models/extras/jobresult.md +6 -2
  301. nautobot/docs/models/extras/relationship.md +2 -2
  302. nautobot/docs/models/extras/status.md +6 -19
  303. nautobot/docs/models/ipam/ipaddress.md +3 -0
  304. nautobot/docs/models/ipam/vrf.md +0 -3
  305. nautobot/docs/models/virtualization/virtualmachine.md +3 -0
  306. nautobot/docs/plugins/development.md +92 -24
  307. nautobot/docs/release-notes/version-1.5.md +96 -0
  308. nautobot/docs/release-notes/version-2.0.md +216 -0
  309. nautobot/docs/requirements.txt +5 -4
  310. nautobot/docs/rest-api/overview.md +384 -215
  311. nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
  312. nautobot/extras/admin.py +3 -5
  313. nautobot/extras/api/customfields.py +15 -39
  314. nautobot/extras/api/fields.py +0 -11
  315. nautobot/extras/api/mixins.py +45 -0
  316. nautobot/extras/api/relationships.py +63 -159
  317. nautobot/extras/api/serializers.py +165 -706
  318. nautobot/extras/api/urls.py +1 -1
  319. nautobot/extras/api/views.py +295 -282
  320. nautobot/extras/apps.py +4 -7
  321. nautobot/extras/choices.py +11 -22
  322. nautobot/extras/constants.py +9 -3
  323. nautobot/extras/datasources/__init__.py +2 -0
  324. nautobot/extras/datasources/git.py +135 -186
  325. nautobot/extras/datasources/registry.py +25 -35
  326. nautobot/extras/factory.py +1 -3
  327. nautobot/extras/filters/__init__.py +49 -47
  328. nautobot/extras/filters/mixins.py +10 -8
  329. nautobot/extras/forms/forms.py +72 -148
  330. nautobot/extras/forms/mixins.py +34 -57
  331. nautobot/extras/health_checks.py +0 -33
  332. nautobot/extras/jobs.py +387 -566
  333. nautobot/extras/management/__init__.py +55 -48
  334. nautobot/extras/management/commands/renaturalize.py +0 -1
  335. nautobot/extras/management/commands/runjob.py +24 -62
  336. nautobot/extras/management/commands/webhook_receiver.py +0 -1
  337. nautobot/extras/managers.py +30 -7
  338. nautobot/extras/migrations/0001_initial_part_1.py +0 -1
  339. nautobot/extras/migrations/0002_initial_part_2.py +0 -1
  340. nautobot/extras/migrations/0003_initial_part_3.py +0 -1
  341. nautobot/extras/migrations/0004_populate_default_status_records.py +0 -1
  342. nautobot/extras/migrations/0005_configcontext_device_types.py +0 -1
  343. nautobot/extras/migrations/0006_graphqlquery.py +0 -1
  344. nautobot/extras/migrations/0007_configcontextschema.py +0 -1
  345. nautobot/extras/migrations/0008_jobresult__custom_field_data.py +0 -1
  346. nautobot/extras/migrations/0009_computedfield.py +0 -1
  347. nautobot/extras/migrations/0010_change_cf_validation_max_min_field_to_bigint.py +0 -1
  348. nautobot/extras/migrations/0011_fileattachment_fileproxy.py +0 -1
  349. nautobot/extras/migrations/0012_healthchecktestmodel.py +0 -1
  350. nautobot/extras/migrations/0013_default_fallback_value_computedfield.py +0 -1
  351. nautobot/extras/migrations/0014_auto_slug.py +0 -1
  352. nautobot/extras/migrations/0015_scheduled_job.py +0 -1
  353. nautobot/extras/migrations/0016_secret.py +0 -1
  354. nautobot/extras/migrations/0017_joblogentry.py +0 -1
  355. nautobot/extras/migrations/0018_joblog_data_migration.py +0 -2
  356. nautobot/extras/migrations/0019_joblogentry__meta_options__related_name.py +0 -1
  357. nautobot/extras/migrations/0020_customfield_changelog.py +0 -1
  358. nautobot/extras/migrations/0021_customfield_changelog_data.py +0 -1
  359. nautobot/extras/migrations/0022_objectchange_object_datav2.py +0 -1
  360. nautobot/extras/migrations/0023_job_model.py +0 -1
  361. nautobot/extras/migrations/0024_job_data_migration.py +0 -1
  362. nautobot/extras/migrations/0025_add_advanced_ui_boolean_to_customfield_conputedfield_and_relationship.py +0 -1
  363. nautobot/extras/migrations/0026_job_add_gitrepository_fk.py +0 -1
  364. nautobot/extras/migrations/0027_job_gitrepository_data_migration.py +0 -1
  365. nautobot/extras/migrations/0028_job_reduce_source.py +0 -1
  366. nautobot/extras/migrations/0029_dynamicgroup.py +0 -1
  367. nautobot/extras/migrations/0030_webhook_alter_unique_together.py +0 -1
  368. nautobot/extras/migrations/0031_tag_content_types.py +0 -1
  369. nautobot/extras/migrations/0032_tag_content_types_data_migration.py +0 -1
  370. nautobot/extras/migrations/0033_add__optimized_indexing.py +0 -1
  371. nautobot/extras/migrations/0034_alter_fileattachment_mimetype.py +0 -1
  372. nautobot/extras/migrations/0035_scheduledjob_crontab.py +0 -1
  373. nautobot/extras/migrations/0036_job_add_has_sensitive_variables.py +0 -1
  374. nautobot/extras/migrations/0037_configcontextschema__remove_name_unique__create_constraint_unique_name_owner.py +0 -1
  375. nautobot/extras/migrations/0038_configcontext_locations.py +0 -1
  376. nautobot/extras/migrations/0039_objectchange__add_change_context.py +0 -1
  377. nautobot/extras/migrations/0040_dynamicgroup__dynamicgroupmembership.py +0 -1
  378. nautobot/extras/migrations/0041_jobresult_job_kwargs.py +0 -1
  379. nautobot/extras/migrations/0042_job__add_is_job_hook_receiver.py +0 -1
  380. nautobot/extras/migrations/0043_note.py +0 -1
  381. nautobot/extras/migrations/0044_add_job_hook.py +0 -1
  382. nautobot/extras/migrations/0045_add_custom_field_slug.py +0 -1
  383. nautobot/extras/migrations/0046_populate_custom_field_slug_label.py +0 -1
  384. nautobot/extras/migrations/0047_enforce_custom_field_slug.py +0 -1
  385. nautobot/extras/migrations/0048_alter_objectchange_change_context_detail.py +0 -1
  386. nautobot/extras/migrations/0049_alter_tag_slug.py +0 -1
  387. nautobot/extras/migrations/0050_customfield_grouping.py +0 -1
  388. nautobot/extras/migrations/0051_add_job_task_queues.py +0 -1
  389. nautobot/extras/migrations/0052_configcontext_device_redundancy_groups.py +0 -1
  390. nautobot/extras/migrations/0053_relationship_required_on.py +0 -1
  391. nautobot/extras/migrations/0054_scheduledjob_kwargs_request_user_change.py +0 -1
  392. nautobot/extras/migrations/0055_configcontext_dynamic_groups.py +0 -1
  393. nautobot/extras/migrations/0056_objectchange_add_reverse_time_idx.py +0 -1
  394. nautobot/extras/migrations/0057_jobbutton.py +0 -1
  395. nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
  396. nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -2
  397. nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -2
  398. nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -8
  399. nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -33
  400. nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -2
  401. nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -2
  402. nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
  403. nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -2
  404. nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -3
  405. nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -2
  406. nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -2
  407. nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
  408. nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -2
  409. nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
  410. nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -10
  411. nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -2
  412. nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
  413. nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
  414. nautobot/extras/migrations/{0076_remove_name_field_and_make_label_field_non_nullable.py → 0077_remove_name_field_and_make_label_field_non_nullable.py} +1 -1
  415. nautobot/extras/migrations/0078_remove_slug.py +45 -0
  416. nautobot/extras/migrations/0079_tagsfield.py +28 -0
  417. nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
  418. nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
  419. nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
  420. nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
  421. nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
  422. nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
  423. nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
  424. nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
  425. nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
  426. nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
  427. nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
  428. nautobot/extras/models/__init__.py +2 -3
  429. nautobot/extras/models/change_logging.py +0 -36
  430. nautobot/extras/models/customfields.py +39 -33
  431. nautobot/extras/models/datasources.py +48 -50
  432. nautobot/extras/models/groups.py +5 -12
  433. nautobot/extras/models/jobs.py +190 -323
  434. nautobot/extras/models/mixins.py +0 -71
  435. nautobot/extras/models/models.py +1 -22
  436. nautobot/extras/models/relationships.py +20 -21
  437. nautobot/extras/models/roles.py +0 -23
  438. nautobot/extras/models/secrets.py +2 -31
  439. nautobot/extras/models/statuses.py +6 -5
  440. nautobot/extras/models/tags.py +2 -17
  441. nautobot/extras/navigation.py +89 -307
  442. nautobot/extras/plugins/__init__.py +3 -121
  443. nautobot/extras/plugins/utils.py +0 -3
  444. nautobot/extras/plugins/validators.py +5 -4
  445. nautobot/extras/plugins/views.py +16 -4
  446. nautobot/extras/querysets.py +1 -7
  447. nautobot/extras/registry.py +3 -0
  448. nautobot/extras/signals.py +26 -60
  449. nautobot/extras/tables.py +42 -49
  450. nautobot/extras/tasks.py +0 -12
  451. nautobot/extras/templates/extras/configcontext.html +1 -1
  452. nautobot/extras/templates/extras/configcontextschema.html +16 -1
  453. nautobot/extras/templates/extras/customfield.html +0 -13
  454. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -1
  455. nautobot/extras/templates/extras/gitrepository.html +3 -3
  456. nautobot/extras/templates/extras/inc/jobresult.html +10 -0
  457. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  458. nautobot/extras/templates/extras/job.html +35 -25
  459. nautobot/extras/templates/extras/job_approval_request.html +15 -30
  460. nautobot/extras/templates/extras/job_detail.html +13 -31
  461. nautobot/extras/templates/extras/job_edit.html +14 -17
  462. nautobot/extras/templates/extras/jobresult.html +24 -6
  463. nautobot/extras/templates/extras/objectchange_list.html +1 -1
  464. nautobot/extras/templates/extras/scheduledjob.html +2 -2
  465. nautobot/extras/templates/extras/secret.html +28 -0
  466. nautobot/extras/templates/extras/secret_edit.html +0 -1
  467. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -1
  468. nautobot/extras/templatetags/custom_links.py +0 -2
  469. nautobot/extras/templatetags/job_buttons.py +1 -0
  470. nautobot/extras/templatetags/plugins.py +0 -1
  471. nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
  472. nautobot/extras/test_jobs/atomic_transaction.py +53 -0
  473. nautobot/extras/test_jobs/dry_run.py +29 -0
  474. nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
  475. nautobot/extras/test_jobs/duplicate_name2.py +9 -0
  476. nautobot/extras/test_jobs/fail.py +23 -0
  477. nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
  478. nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
  479. nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
  480. nautobot/extras/test_jobs/file_upload_pass.py +25 -0
  481. nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
  482. nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
  483. nautobot/extras/test_jobs/job_button_receiver.py +28 -0
  484. nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
  485. nautobot/extras/test_jobs/job_variables.py +88 -0
  486. nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
  487. nautobot/extras/test_jobs/log_redaction.py +20 -0
  488. nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
  489. nautobot/extras/test_jobs/modify_db.py +25 -0
  490. nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
  491. nautobot/extras/test_jobs/object_var_optional.py +21 -0
  492. nautobot/extras/test_jobs/object_var_required.py +21 -0
  493. nautobot/extras/test_jobs/object_vars.py +26 -0
  494. nautobot/extras/test_jobs/pass.py +25 -0
  495. nautobot/extras/test_jobs/profiling.py +32 -0
  496. nautobot/extras/test_jobs/read_only_job.py +15 -0
  497. nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
  498. nautobot/extras/{tests/example_jobs/test_soft_time_limit_greater_than_time_limit.py → test_jobs/soft_time_limit_greater_than_time_limit.py} +5 -1
  499. nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
  500. nautobot/extras/tests/integration/__init__.py +3 -3
  501. nautobot/extras/tests/integration/test_computedfields.py +1 -1
  502. nautobot/extras/tests/integration/test_configcontextschema.py +7 -5
  503. nautobot/extras/tests/integration/test_customfields.py +4 -2
  504. nautobot/extras/tests/integration/test_dynamicgroups.py +2 -2
  505. nautobot/extras/tests/integration/test_jobs.py +25 -27
  506. nautobot/extras/tests/integration/test_notes.py +8 -4
  507. nautobot/extras/tests/integration/test_plugins.py +4 -4
  508. nautobot/extras/tests/integration/test_relationships.py +2 -2
  509. nautobot/extras/tests/test_api.py +371 -381
  510. nautobot/extras/tests/test_changelog.py +17 -16
  511. nautobot/extras/tests/test_context_managers.py +5 -6
  512. nautobot/extras/tests/test_customfields.py +112 -73
  513. nautobot/extras/tests/test_datasources.py +191 -117
  514. nautobot/extras/tests/test_dynamicgroups.py +45 -68
  515. nautobot/extras/tests/test_filters.py +170 -130
  516. nautobot/extras/tests/test_forms.py +107 -109
  517. nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
  518. nautobot/extras/tests/test_jobs.py +271 -273
  519. nautobot/extras/tests/test_management.py +3 -6
  520. nautobot/extras/tests/test_migrations.py +5 -3
  521. nautobot/extras/tests/test_models.py +121 -173
  522. nautobot/extras/tests/test_notes.py +0 -1
  523. nautobot/extras/tests/test_plugins.py +55 -89
  524. nautobot/extras/tests/test_relationships.py +174 -130
  525. nautobot/extras/tests/test_tags.py +6 -12
  526. nautobot/extras/tests/test_utils.py +31 -1
  527. nautobot/extras/tests/test_views.py +223 -184
  528. nautobot/extras/tests/test_webhooks.py +16 -15
  529. nautobot/extras/urls.py +69 -69
  530. nautobot/extras/utils.py +137 -163
  531. nautobot/extras/views.py +81 -153
  532. nautobot/ipam/api/fields.py +17 -0
  533. nautobot/ipam/api/serializers.py +77 -164
  534. nautobot/ipam/api/urls.py +4 -1
  535. nautobot/ipam/api/views.py +28 -19
  536. nautobot/ipam/apps.py +1 -0
  537. nautobot/ipam/choices.py +5 -12
  538. nautobot/ipam/constants.py +1 -0
  539. nautobot/ipam/factory.py +41 -30
  540. nautobot/ipam/filters.py +58 -25
  541. nautobot/ipam/forms.py +82 -211
  542. nautobot/ipam/graphql/types.py +0 -9
  543. nautobot/ipam/lookups.py +13 -8
  544. nautobot/ipam/management/commands/__init__.py +0 -0
  545. nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
  546. nautobot/ipam/migrations/0001_initial_part_1.py +0 -1
  547. nautobot/ipam/migrations/0002_initial_part_2.py +0 -1
  548. nautobot/ipam/migrations/0003_remove_max_length.py +0 -1
  549. nautobot/ipam/migrations/0004_fixup_p2p_broadcast.py +0 -1
  550. nautobot/ipam/migrations/0005_auto_slug.py +0 -1
  551. nautobot/ipam/migrations/0006_ipaddress_nat_outside_list.py +0 -1
  552. nautobot/ipam/migrations/0007_add_natural_indexing.py +0 -1
  553. nautobot/ipam/migrations/0008_prefix_vlan_vlangroup_location.py +0 -1
  554. nautobot/ipam/migrations/0009_alter_vlan_name.py +0 -1
  555. nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -2
  556. nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -39
  557. nautobot/ipam/migrations/0012_rename_ipam_roles.py +0 -1
  558. nautobot/ipam/migrations/0013_delete_role.py +0 -1
  559. nautobot/ipam/migrations/0014_rename_foreign_keys_and_related_names.py +0 -1
  560. nautobot/ipam/migrations/0015_prefix_add_type.py +0 -1
  561. nautobot/ipam/migrations/0016_prefix_type_data_migration.py +0 -3
  562. nautobot/ipam/migrations/0017_prefix_remove_is_pool.py +0 -1
  563. nautobot/ipam/migrations/0018_remove_site_foreign_key_from_ipam_models.py +0 -1
  564. nautobot/ipam/migrations/0019_created_datetime.py +0 -1
  565. nautobot/ipam/migrations/0020_related_name_changes.py +1 -2
  566. nautobot/ipam/migrations/0021_prefix_add_rir_and_date_allocated.py +0 -1
  567. nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +3 -5
  568. nautobot/ipam/migrations/0023_delete_aggregate.py +0 -1
  569. nautobot/ipam/migrations/0024_interface_to_ipaddress_m2m.py +0 -1
  570. nautobot/ipam/migrations/0025_interface_ipaddress_m2m_data_migration.py +0 -1
  571. nautobot/ipam/migrations/0026_ipaddress_remove_assigned_object.py +0 -1
  572. nautobot/ipam/migrations/0027_remove_rir_slug.py +16 -0
  573. nautobot/ipam/migrations/0028_tagsfield.py +44 -0
  574. nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
  575. nautobot/ipam/migrations/0030_ipam__namespaces.py +231 -0
  576. nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
  577. nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
  578. nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
  579. nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
  580. nautobot/ipam/models.py +579 -368
  581. nautobot/ipam/navigation.py +36 -159
  582. nautobot/ipam/querysets.py +117 -90
  583. nautobot/ipam/signals.py +89 -0
  584. nautobot/ipam/tables.py +86 -28
  585. nautobot/ipam/templates/ipam/ipaddress.html +14 -30
  586. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -0
  587. nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
  588. nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
  589. nautobot/ipam/templates/ipam/namespace_retrieve.html +42 -0
  590. nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
  591. nautobot/ipam/templates/ipam/prefix.html +27 -33
  592. nautobot/ipam/templates/ipam/prefix_edit.html +7 -1
  593. nautobot/ipam/templates/ipam/vlangroup.html +0 -13
  594. nautobot/ipam/templates/ipam/vrf.html +6 -4
  595. nautobot/ipam/templates/ipam/vrf_edit.html +20 -2
  596. nautobot/ipam/tests/integration/test_prefixes.py +4 -27
  597. nautobot/ipam/tests/test_api.py +60 -61
  598. nautobot/ipam/tests/test_filters.py +187 -126
  599. nautobot/ipam/tests/test_forms.py +12 -6
  600. nautobot/ipam/tests/test_graphql.py +8 -6
  601. nautobot/ipam/tests/test_migrations.py +8 -13
  602. nautobot/ipam/tests/test_models.py +426 -274
  603. nautobot/ipam/tests/test_ordering.py +6 -3
  604. nautobot/ipam/tests/test_querysets.py +340 -96
  605. nautobot/ipam/tests/test_views.py +100 -55
  606. nautobot/ipam/urls.py +28 -5
  607. nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
  608. nautobot/ipam/utils/migrations.py +713 -0
  609. nautobot/ipam/views.py +237 -122
  610. nautobot/project-static/docs/404.html +1399 -166
  611. nautobot/project-static/docs/additional-features/caching.html +1416 -320
  612. nautobot/project-static/docs/additional-features/change-logging.html +1389 -190
  613. nautobot/project-static/docs/additional-features/config-contexts.html +1389 -190
  614. nautobot/project-static/docs/additional-features/graphql.html +1389 -190
  615. nautobot/project-static/docs/additional-features/healthcheck.html +1389 -190
  616. nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1393 -190
  617. nautobot/project-static/docs/additional-features/jobs.html +1677 -460
  618. nautobot/project-static/docs/additional-features/napalm.html +1389 -190
  619. nautobot/project-static/docs/additional-features/prometheus-metrics.html +1389 -190
  620. nautobot/project-static/docs/additional-features/template-filters.html +1389 -190
  621. nautobot/project-static/docs/administration/celery-queues.html +1389 -190
  622. nautobot/project-static/docs/administration/nautobot-server.html +1553 -375
  623. nautobot/project-static/docs/administration/nautobot-shell.html +1395 -196
  624. nautobot/project-static/docs/administration/permissions.html +1389 -190
  625. nautobot/project-static/docs/administration/replicating-nautobot.html +1387 -207
  626. nautobot/project-static/docs/apps/index.html +1389 -190
  627. nautobot/project-static/docs/apps/nautobot-apps.html +1387 -175
  628. nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js +29 -0
  629. nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js.map +8 -0
  630. nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js → search.208ed371.min.js} +9 -15
  631. nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js.map → search.208ed371.min.js.map} +4 -4
  632. nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css +1 -0
  633. nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css.map +1 -0
  634. nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css +1 -0
  635. nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css.map +1 -0
  636. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1775 -590
  637. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1389 -190
  638. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3588 -1922
  639. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1461 -262
  640. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1401 -170
  641. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1396 -191
  642. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2095 -894
  643. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2357 -1194
  644. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2258 -940
  645. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1389 -190
  646. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1400 -201
  647. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +11068 -7861
  648. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2867 -2224
  649. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1389 -190
  650. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2641 -1573
  651. nautobot/project-static/docs/configuration/authentication/ldap.html +1389 -190
  652. nautobot/project-static/docs/configuration/authentication/remote.html +1389 -190
  653. nautobot/project-static/docs/configuration/authentication/sso.html +1389 -190
  654. nautobot/project-static/docs/configuration/index.html +1398 -199
  655. nautobot/project-static/docs/configuration/optional-settings.html +1418 -274
  656. nautobot/project-static/docs/configuration/required-settings.html +1419 -287
  657. nautobot/project-static/docs/core-functionality/circuits.html +1446 -247
  658. nautobot/project-static/docs/core-functionality/device-types.html +1448 -249
  659. nautobot/project-static/docs/core-functionality/devices.html +1452 -249
  660. nautobot/project-static/docs/core-functionality/ipam.html +1452 -253
  661. nautobot/project-static/docs/core-functionality/power.html +1448 -249
  662. nautobot/project-static/docs/core-functionality/secrets.html +1448 -249
  663. nautobot/project-static/docs/core-functionality/services.html +1448 -249
  664. nautobot/project-static/docs/core-functionality/sites-and-racks.html +1448 -249
  665. nautobot/project-static/docs/core-functionality/tenancy.html +1448 -249
  666. nautobot/project-static/docs/core-functionality/virtualization.html +1452 -249
  667. nautobot/project-static/docs/core-functionality/vlans.html +1448 -249
  668. nautobot/project-static/docs/development/application-registry.html +1393 -214
  669. nautobot/project-static/docs/development/best-practices.html +1392 -192
  670. nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1390 -191
  671. nautobot/project-static/docs/development/extending-models.html +1443 -257
  672. nautobot/project-static/docs/development/generic-views.html +1403 -174
  673. nautobot/project-static/docs/development/getting-started.html +1568 -262
  674. nautobot/project-static/docs/development/homepage.html +1389 -190
  675. nautobot/project-static/docs/development/index.html +1389 -190
  676. nautobot/project-static/docs/development/model-features.html +1389 -190
  677. nautobot/project-static/docs/development/natural-keys.html +1389 -190
  678. nautobot/project-static/docs/development/navigation-menu.html +1451 -330
  679. nautobot/project-static/docs/development/react-ui.html +4199 -0
  680. nautobot/project-static/docs/development/release-checklist.html +1392 -193
  681. nautobot/project-static/docs/development/role-internals.html +1402 -172
  682. nautobot/project-static/docs/development/style-guide.html +1399 -199
  683. nautobot/project-static/docs/development/templates.html +1391 -191
  684. nautobot/project-static/docs/development/testing.html +1389 -190
  685. nautobot/project-static/docs/development/user-preferences.html +1389 -190
  686. nautobot/project-static/docs/docker/index.html +1408 -206
  687. nautobot/project-static/docs/index.html +1397 -180
  688. nautobot/project-static/docs/installation/centos.html +1401 -170
  689. nautobot/project-static/docs/installation/external-authentication.html +1389 -190
  690. nautobot/project-static/docs/installation/http-server.html +1389 -190
  691. nautobot/project-static/docs/installation/index.html +1394 -191
  692. nautobot/project-static/docs/installation/migrating-from-netbox.html +1452 -305
  693. nautobot/project-static/docs/installation/migrating-from-postgresql.html +1390 -191
  694. nautobot/project-static/docs/installation/nautobot.html +1390 -191
  695. nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1389 -190
  696. nautobot/project-static/docs/installation/selinux-troubleshooting.html +1401 -170
  697. nautobot/project-static/docs/installation/services.html +1389 -190
  698. nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  699. nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  700. nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  701. nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  702. nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
  703. nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  704. nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  705. nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  706. nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  707. nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  708. nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  709. nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  710. nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  711. nautobot/project-static/docs/installation/ubuntu.html +1401 -170
  712. nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +4254 -1923
  713. nautobot/project-static/docs/installation/upgrading.html +1395 -192
  714. nautobot/project-static/docs/models/circuits/circuit.html +1427 -174
  715. nautobot/project-static/docs/models/circuits/circuittermination.html +1427 -174
  716. nautobot/project-static/docs/models/circuits/circuittype.html +1427 -174
  717. nautobot/project-static/docs/models/circuits/provider.html +1427 -174
  718. nautobot/project-static/docs/models/circuits/providernetwork.html +1427 -174
  719. nautobot/project-static/docs/models/dcim/cable.html +1458 -174
  720. nautobot/project-static/docs/models/dcim/consoleport.html +1427 -174
  721. nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1427 -174
  722. nautobot/project-static/docs/models/dcim/consoleserverport.html +1427 -174
  723. nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1427 -174
  724. nautobot/project-static/docs/models/dcim/device.html +1431 -174
  725. nautobot/project-static/docs/models/dcim/devicebay.html +1427 -174
  726. nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1427 -174
  727. nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1522 -177
  728. nautobot/project-static/docs/models/dcim/devicetype.html +1427 -174
  729. nautobot/project-static/docs/models/dcim/frontport.html +1427 -174
  730. nautobot/project-static/docs/models/dcim/frontporttemplate.html +1427 -174
  731. nautobot/project-static/docs/models/dcim/interface.html +1427 -174
  732. nautobot/project-static/docs/models/dcim/interfacetemplate.html +1427 -174
  733. nautobot/project-static/docs/models/dcim/inventoryitem.html +1427 -174
  734. nautobot/project-static/docs/models/dcim/location.html +1427 -174
  735. nautobot/project-static/docs/models/dcim/locationtype.html +1427 -174
  736. nautobot/project-static/docs/models/dcim/manufacturer.html +1427 -174
  737. nautobot/project-static/docs/models/dcim/platform.html +1427 -174
  738. nautobot/project-static/docs/models/dcim/powerfeed.html +1425 -172
  739. nautobot/project-static/docs/models/dcim/poweroutlet.html +1427 -174
  740. nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1427 -174
  741. nautobot/project-static/docs/models/dcim/powerpanel.html +1425 -172
  742. nautobot/project-static/docs/models/dcim/powerport.html +1427 -174
  743. nautobot/project-static/docs/models/dcim/powerporttemplate.html +1427 -174
  744. nautobot/project-static/docs/models/dcim/rack.html +1427 -174
  745. nautobot/project-static/docs/models/dcim/rackgroup.html +1427 -174
  746. nautobot/project-static/docs/models/dcim/rackreservation.html +1427 -174
  747. nautobot/project-static/docs/models/dcim/rearport.html +1427 -174
  748. nautobot/project-static/docs/models/dcim/rearporttemplate.html +1427 -174
  749. nautobot/project-static/docs/models/dcim/region.html +1401 -170
  750. nautobot/project-static/docs/models/dcim/site.html +1401 -170
  751. nautobot/project-static/docs/models/dcim/virtualchassis.html +1425 -172
  752. nautobot/project-static/docs/models/extras/computedfield.html +1393 -194
  753. nautobot/project-static/docs/models/extras/configcontext.html +1465 -174
  754. nautobot/project-static/docs/models/extras/configcontextschema.html +1421 -168
  755. nautobot/project-static/docs/models/extras/customfield.html +1389 -190
  756. nautobot/project-static/docs/models/extras/customlink.html +1389 -190
  757. nautobot/project-static/docs/models/extras/dynamicgroup.html +1398 -199
  758. nautobot/project-static/docs/models/extras/exporttemplate.html +1389 -190
  759. nautobot/project-static/docs/models/extras/gitrepository.html +1393 -190
  760. nautobot/project-static/docs/models/extras/graphqlquery.html +1469 -171
  761. nautobot/project-static/docs/models/extras/imageattachment.html +1434 -181
  762. nautobot/project-static/docs/models/extras/job.html +1411 -157
  763. nautobot/project-static/docs/models/extras/jobbutton.html +1410 -207
  764. nautobot/project-static/docs/models/extras/jobhook.html +1397 -194
  765. nautobot/project-static/docs/models/extras/joblogentry.html +1408 -155
  766. nautobot/project-static/docs/models/extras/jobresult.html +1417 -159
  767. nautobot/project-static/docs/models/extras/note.html +1389 -190
  768. nautobot/project-static/docs/models/extras/relationship.html +1391 -192
  769. nautobot/project-static/docs/models/extras/role.html +1495 -198
  770. nautobot/project-static/docs/models/extras/secret.html +1492 -201
  771. nautobot/project-static/docs/models/extras/secretsgroup.html +1410 -157
  772. nautobot/project-static/docs/models/extras/status.html +1381 -221
  773. nautobot/project-static/docs/models/extras/tag.html +1389 -190
  774. nautobot/project-static/docs/models/extras/webhook.html +1389 -190
  775. nautobot/project-static/docs/models/ipam/ipaddress.html +1488 -200
  776. nautobot/project-static/docs/models/ipam/prefix.html +1410 -157
  777. nautobot/project-static/docs/models/ipam/rir.html +1410 -157
  778. nautobot/project-static/docs/models/ipam/routetarget.html +1410 -157
  779. nautobot/project-static/docs/models/ipam/service.html +1410 -157
  780. nautobot/project-static/docs/models/ipam/vlan.html +1410 -157
  781. nautobot/project-static/docs/models/ipam/vlangroup.html +1410 -157
  782. nautobot/project-static/docs/models/ipam/vrf.html +1410 -161
  783. nautobot/project-static/docs/models/tenancy/tenant.html +1412 -159
  784. nautobot/project-static/docs/models/tenancy/tenantgroup.html +1412 -159
  785. nautobot/project-static/docs/models/users/objectpermission.html +1462 -171
  786. nautobot/project-static/docs/models/users/token.html +1410 -157
  787. nautobot/project-static/docs/models/virtualization/cluster.html +1410 -157
  788. nautobot/project-static/docs/models/virtualization/clustergroup.html +1410 -157
  789. nautobot/project-static/docs/models/virtualization/clustertype.html +1410 -157
  790. nautobot/project-static/docs/models/virtualization/virtualmachine.html +1414 -157
  791. nautobot/project-static/docs/models/virtualization/vminterface.html +1410 -157
  792. nautobot/project-static/docs/objects.inv +0 -0
  793. nautobot/project-static/docs/plugins/development.html +1916 -646
  794. nautobot/project-static/docs/plugins/index.html +1389 -190
  795. nautobot/project-static/docs/plugins/porting-from-netbox.html +1389 -190
  796. nautobot/project-static/docs/release-notes/index.html +1389 -190
  797. nautobot/project-static/docs/release-notes/version-1.0.html +1389 -190
  798. nautobot/project-static/docs/release-notes/version-1.1.html +1389 -190
  799. nautobot/project-static/docs/release-notes/version-1.2.html +1389 -190
  800. nautobot/project-static/docs/release-notes/version-1.3.html +1389 -190
  801. nautobot/project-static/docs/release-notes/version-1.4.html +1389 -190
  802. nautobot/project-static/docs/release-notes/version-1.5.html +2016 -397
  803. nautobot/project-static/docs/release-notes/version-2.0.html +1935 -287
  804. nautobot/project-static/docs/requirements.txt +5 -4
  805. nautobot/project-static/docs/rest-api/authentication.html +1389 -190
  806. nautobot/project-static/docs/rest-api/filtering.html +1389 -190
  807. nautobot/project-static/docs/rest-api/overview.html +2002 -576
  808. nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
  809. nautobot/project-static/docs/search/search_index.json +1 -1
  810. nautobot/project-static/docs/sitemap.xml +197 -187
  811. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  812. nautobot/project-static/docs/user-guides/custom-fields.html +1390 -191
  813. nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1392 -193
  814. nautobot/project-static/docs/user-guides/getting-started/index.html +1388 -189
  815. nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1388 -189
  816. nautobot/project-static/docs/user-guides/getting-started/ipam.html +1386 -187
  817. nautobot/project-static/docs/user-guides/getting-started/platforms.html +1448 -249
  818. nautobot/project-static/docs/user-guides/getting-started/regions.html +1411 -212
  819. nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1395 -196
  820. nautobot/project-static/docs/user-guides/getting-started/tenants.html +1448 -249
  821. nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1448 -249
  822. nautobot/project-static/docs/user-guides/git-data-source.html +1405 -206
  823. nautobot/project-static/docs/user-guides/graphql.html +1402 -203
  824. nautobot/project-static/docs/user-guides/relationships.html +1448 -249
  825. nautobot/project-static/docs/user-guides/s3-django-storage.html +1448 -249
  826. nautobot/project-static/js/forms.js +16 -9
  827. nautobot/project-static/js/theme.js +5 -0
  828. nautobot/tenancy/api/serializers.py +4 -34
  829. nautobot/tenancy/api/urls.py +1 -1
  830. nautobot/tenancy/filters/__init__.py +9 -7
  831. nautobot/tenancy/filters/mixins.py +3 -2
  832. nautobot/tenancy/forms.py +3 -36
  833. nautobot/tenancy/migrations/0001_initial.py +0 -1
  834. nautobot/tenancy/migrations/0002_auto_slug.py +0 -1
  835. nautobot/tenancy/migrations/0003_mptt_to_tree_queries.py +0 -1
  836. nautobot/tenancy/migrations/0004_change_tree_manager_on_tree_models.py +0 -1
  837. nautobot/tenancy/migrations/0005_rename_foreign_keys_and_related_names.py +0 -1
  838. nautobot/tenancy/migrations/0006_created_datetime.py +0 -1
  839. nautobot/tenancy/migrations/0007_remove_tenant_tenantgroup_slug.py +20 -0
  840. nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
  841. nautobot/tenancy/models.py +0 -30
  842. nautobot/tenancy/navigation.py +6 -39
  843. nautobot/tenancy/tables.py +4 -4
  844. nautobot/tenancy/templates/tenancy/tenant.html +12 -12
  845. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -1
  846. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  847. nautobot/tenancy/tests/test_api.py +1 -12
  848. nautobot/tenancy/tests/test_filters.py +20 -12
  849. nautobot/tenancy/tests/test_views.py +11 -29
  850. nautobot/tenancy/urls.py +10 -10
  851. nautobot/tenancy/views.py +0 -3
  852. nautobot/ui/.eslintignore +6 -0
  853. nautobot/ui/.gitignore +10 -0
  854. nautobot/ui/.prettierignore +9 -0
  855. nautobot/ui/.prettierrc +4 -0
  856. nautobot/ui/README.md +33 -0
  857. nautobot/ui/app_imports.js.j2 +7 -0
  858. nautobot/ui/craco.config.js +46 -0
  859. nautobot/ui/jsconfig-base.json +11 -0
  860. nautobot/ui/jsconfig.json +5 -0
  861. nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
  862. nautobot/ui/package-lock.json +21451 -0
  863. nautobot/ui/package.json +70 -0
  864. nautobot/ui/public/index.html +47 -0
  865. nautobot/ui/public/logo192.png +0 -0
  866. nautobot/ui/public/logo512.png +0 -0
  867. nautobot/ui/public/manifest.json +25 -0
  868. nautobot/ui/public/nautobot_logo.svg +131 -0
  869. nautobot/ui/public/robots.txt +3 -0
  870. nautobot/ui/src/App.js +71 -0
  871. nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
  872. nautobot/ui/src/components/AppTab.js +40 -0
  873. nautobot/ui/src/components/Apps.js +60 -0
  874. nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
  875. nautobot/ui/src/components/HomePanel.js +58 -0
  876. nautobot/ui/src/components/JobHistoryTable.js +78 -0
  877. nautobot/ui/src/components/Layout.js +53 -0
  878. nautobot/ui/src/components/LoadingWidget.js +25 -0
  879. nautobot/ui/src/components/Navbar.js +116 -0
  880. nautobot/ui/src/components/NotificationPopover.js +27 -0
  881. nautobot/ui/src/components/ObjectListTable.js +209 -0
  882. nautobot/ui/src/components/ReferenceDataTag.js +35 -0
  883. nautobot/ui/src/components/RouterButton.js +10 -0
  884. nautobot/ui/src/components/RouterLink.js +10 -0
  885. nautobot/ui/src/components/SidebarNav.js +147 -0
  886. nautobot/ui/src/components/Table.js +48 -0
  887. nautobot/ui/src/components/TableItem.js +71 -0
  888. nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
  889. nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
  890. nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
  891. nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
  892. nautobot/ui/src/components/__tests__/Table.test.js +36 -0
  893. nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
  894. nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
  895. nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
  896. nautobot/ui/src/components/pagination.js +93 -0
  897. nautobot/ui/src/components/paginator.js +79 -0
  898. nautobot/ui/src/components/paginator_form.js +43 -0
  899. nautobot/ui/src/components/usePagination.js +57 -0
  900. nautobot/ui/src/constants/apiPath.js +10 -0
  901. nautobot/ui/src/constants/icons.js +15 -0
  902. nautobot/ui/src/constants/size.js +15 -0
  903. nautobot/ui/src/index.js +65 -0
  904. nautobot/ui/src/reportWebVitals.js +15 -0
  905. nautobot/ui/src/router.js +77 -0
  906. nautobot/ui/src/utils/api.js +131 -0
  907. nautobot/ui/src/utils/app-import.js +15 -0
  908. nautobot/ui/src/utils/color.js +15 -0
  909. nautobot/ui/src/utils/date.js +14 -0
  910. nautobot/ui/src/utils/index.js +15 -0
  911. nautobot/ui/src/utils/navigation.js +32 -0
  912. nautobot/ui/src/utils/session.js +64 -0
  913. nautobot/ui/src/utils/store.js +242 -0
  914. nautobot/ui/src/utils/string.js +6 -0
  915. nautobot/ui/src/utils/url.js +4 -0
  916. nautobot/ui/src/views/Home.js +138 -0
  917. nautobot/ui/src/views/InstalledApps.js +80 -0
  918. nautobot/ui/src/views/Login.js +48 -0
  919. nautobot/ui/src/views/Logout.js +20 -0
  920. nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
  921. nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
  922. nautobot/ui/src/views/__tests__/Login.test.js +15 -0
  923. nautobot/ui/src/views/generic/GenericView.js +142 -0
  924. nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
  925. nautobot/ui/src/views/generic/ObjectList.js +127 -0
  926. nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
  927. nautobot/users/admin.py +1 -1
  928. nautobot/users/api/serializers.py +51 -61
  929. nautobot/users/api/urls.py +1 -1
  930. nautobot/users/api/views.py +53 -2
  931. nautobot/users/migrations/0001_initial.py +0 -1
  932. nautobot/users/migrations/0002_token_ordering_by_created.py +0 -1
  933. nautobot/users/migrations/0003_alter_user_options.py +0 -1
  934. nautobot/users/migrations/0004_alter_user_managers.py +0 -1
  935. nautobot/users/tests/test_api.py +109 -28
  936. nautobot/users/tests/test_filters.py +0 -4
  937. nautobot/users/tests/test_models.py +0 -1
  938. nautobot/users/views.py +0 -7
  939. nautobot/virtualization/api/serializers.py +18 -132
  940. nautobot/virtualization/api/urls.py +1 -1
  941. nautobot/virtualization/api/views.py +1 -22
  942. nautobot/virtualization/choices.py +0 -2
  943. nautobot/virtualization/filters.py +12 -7
  944. nautobot/virtualization/forms.py +21 -117
  945. nautobot/virtualization/migrations/0001_initial.py +0 -1
  946. nautobot/virtualization/migrations/0002_virtualmachine_local_context_schema.py +0 -1
  947. nautobot/virtualization/migrations/0003_vminterface_verbose_name.py +0 -1
  948. nautobot/virtualization/migrations/0004_auto_slug.py +0 -1
  949. nautobot/virtualization/migrations/0005_add_natural_indexing.py +0 -1
  950. nautobot/virtualization/migrations/0006_vminterface_status.py +0 -1
  951. nautobot/virtualization/migrations/0007_vminterface_status_data_migration.py +0 -1
  952. nautobot/virtualization/migrations/0008_vminterface_parent.py +0 -1
  953. nautobot/virtualization/migrations/0009_cluster_location.py +0 -1
  954. nautobot/virtualization/migrations/0010_vminterface_mac_address_data_migration.py +0 -1
  955. nautobot/virtualization/migrations/0011_alter_vminterface_mac_address.py +0 -1
  956. nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -2
  957. nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -12
  958. nautobot/virtualization/migrations/0014_rename_virtualmachine_roles.py +0 -1
  959. nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -2
  960. nautobot/virtualization/migrations/0016_remove_site_foreign_key_from_cluster_class.py +0 -1
  961. nautobot/virtualization/migrations/0017_created_datetime.py +0 -1
  962. nautobot/virtualization/migrations/0018_related_name_changes.py +1 -2
  963. nautobot/virtualization/migrations/0019_vminterface_ip_addresses_m2m.py +0 -1
  964. nautobot/virtualization/migrations/0020_remove_clustergroup_clustertype_slug.py +20 -0
  965. nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
  966. nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
  967. nautobot/virtualization/migrations/0023_ipam__namespaces.py +25 -0
  968. nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
  969. nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
  970. nautobot/virtualization/models.py +39 -131
  971. nautobot/virtualization/navigation.py +18 -99
  972. nautobot/virtualization/tables.py +4 -4
  973. nautobot/virtualization/templates/virtualization/virtualmachine.html +13 -2
  974. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
  975. nautobot/virtualization/tests/test_api.py +42 -52
  976. nautobot/virtualization/tests/test_filters.py +98 -75
  977. nautobot/virtualization/tests/test_models.py +36 -13
  978. nautobot/virtualization/tests/test_views.py +68 -73
  979. nautobot/virtualization/urls.py +10 -10
  980. nautobot/virtualization/views.py +8 -14
  981. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +15 -22
  982. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +987 -834
  983. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
  984. nautobot/circuits/api/nested_serializers.py +0 -69
  985. nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
  986. nautobot/dcim/api/nested_serializers.py +0 -356
  987. nautobot/dcim/templates/dcim/device_import.html +0 -5
  988. nautobot/dcim/templates/dcim/device_import_child.html +0 -5
  989. nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
  990. nautobot/extras/api/nested_serializers.py +0 -353
  991. nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -42
  992. nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -47
  993. nautobot/extras/reports.py +0 -60
  994. nautobot/extras/scripts.py +0 -72
  995. nautobot/extras/tests/example_jobs/script_variables.py +0 -67
  996. nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
  997. nautobot/extras/tests/example_jobs/test_fail.py +0 -16
  998. nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
  999. nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
  1000. nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
  1001. nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
  1002. nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
  1003. nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
  1004. nautobot/extras/tests/example_jobs/test_modify_db.py +0 -19
  1005. nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
  1006. nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
  1007. nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
  1008. nautobot/extras/tests/example_jobs/test_pass.py +0 -19
  1009. nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
  1010. nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
  1011. nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
  1012. nautobot/ipam/api/nested_serializers.py +0 -143
  1013. nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js +0 -29
  1014. nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js.map +0 -8
  1015. nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js +0 -18
  1016. nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js.map +0 -8
  1017. nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css +0 -1
  1018. nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css.map +0 -1
  1019. nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css +0 -1
  1020. nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css.map +0 -1
  1021. nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css +0 -1
  1022. nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css.map +0 -1
  1023. nautobot/tenancy/api/nested_serializers.py +0 -31
  1024. nautobot/users/api/nested_serializers.py +0 -67
  1025. nautobot/virtualization/api/nested_serializers.py +0 -65
  1026. /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
  1027. /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
  1028. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
  1029. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -1,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,26 +232,26 @@ 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
 
243
239
  # Fill out the form
244
- form_data = dict(
245
- ipv4_address="1.2.3.4",
246
- ipv4_with_mask="1.2.3.4/32",
247
- ipv4_network="1.2.3.0/24",
248
- ipv6_address="2001:db8::1",
249
- ipv6_with_mask="2001:db8::1/64",
250
- ipv6_network="2001:db8::/64",
251
- )
240
+ form_data = {
241
+ "ipv4_address": "1.2.3.4",
242
+ "ipv4_with_mask": "1.2.3.4/32",
243
+ "ipv4_network": "1.2.3.0/24",
244
+ "ipv6_address": "2001:db8::1",
245
+ "ipv6_with_mask": "2001:db8::1/64",
246
+ "ipv6_network": "2001:db8::/64",
247
+ }
252
248
  form = job_class().as_form(form_data)
253
249
  self.assertTrue(form.is_valid())
254
250
 
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)
346
-
347
- # Assert stuff
348
- 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)
349
+ job_result = create_job_result_and_run_job(module, name, **data)
353
350
 
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
351
  # Assert stuff
365
352
  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)
549
- 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)
553
- self.assertIn(f"{job_model.class_path}: SUCCESS", out)
554
- self.assertEqual("", err)
555
-
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(slug="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)
570
+ out, err = self.run_command("--local", "--no-color", "--username", self.user.username, job_model.class_path)
566
571
  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)
572
+ self.assertIn("run: 0 debug, 1 info, 0 warning, 0 error, 0 critical", out)
573
+ self.assertIn("info: Success", out)
570
574
  self.assertIn(f"{job_model.class_path}: SUCCESS", out)
571
575
  self.assertEqual("", err)
572
576
 
573
- with self.assertRaises(ObjectDoesNotExist):
574
- Status.objects.get(slug="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"
577
+ def test_runjob_wrong_username(self):
578
+ """A job when run with a nonexistent username, is rejected."""
579
+ module = "modify_db"
582
580
  name = "TestModifyDB"
583
581
  _job_class, job_model = get_job_class_and_model(module, name)
584
582
  with self.assertRaises(CommandError):
585
- self.run_command("--commit", job_model.class_path)
583
+ self.run_command("--username", "nosuchuser", job_model.class_path)
586
584
 
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"
590
- name = "TestModifyDB"
591
- _job_class, job_model = get_job_class_and_model(module, name)
592
- 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")
598
-
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
- status = Status.objects.get(slug="test-status")
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,38 +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
-
687
684
  with self.subTest(expected=False):
688
- module = "test_pass"
685
+ module = "pass"
689
686
  name = "TestPass"
690
687
  _job_class, job_model = get_job_class_and_model(module, name)
691
688
  self.assertFalse(job_model.is_job_button_receiver)
692
689
 
693
690
  with self.subTest(expected=True):
694
- module = "test_job_button_receiver"
691
+ module = "job_button_receiver"
695
692
  name = "TestJobButtonReceiverSimple"
696
693
  _job_class, job_model = get_job_class_and_model(module, name)
697
694
  self.assertTrue(job_model.is_job_button_receiver)
698
695
 
699
696
  def test_missing_receive_job_button_method(self):
700
- module = "test_job_button_receiver"
697
+ module = "job_button_receiver"
701
698
  name = "TestJobButtonReceiverFail"
702
- logging.disable(logging.ERROR)
703
- job_result = create_job_result_and_run_job(module, name, data=self.data, commit=False)
704
- logging.disable(logging.NOTSET)
699
+ job_result = create_job_result_and_run_job(module, name, **self.data)
705
700
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
706
701
 
707
702
 
@@ -721,53 +716,52 @@ class JobHookReceiverTest(TransactionTestCase):
721
716
  # generate an ObjectChange by creating a new location
722
717
  with web_request_context(self.user):
723
718
  location_type = LocationType.objects.create(name="Test Root Type 1")
724
- 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)
725
721
  location.save()
726
722
  location.refresh_from_db()
727
723
  oc = get_changes_for_model(location).first()
728
724
  self.data = {"object_change": oc.id}
729
725
 
730
726
  def test_form_field(self):
731
- module = "test_job_hook_receiver"
727
+ module = "job_hook_receiver"
732
728
  name = "TestJobHookReceiverLog"
733
729
  job_class, _job_model = get_job_class_and_model(module, name)
734
730
  form = job_class().as_form()
735
- self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_task_queue", "_commit"])
731
+ self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_task_queue", "_profile"])
736
732
 
737
733
  def test_hidden(self):
738
- module = "test_job_hook_receiver"
734
+ module = "job_hook_receiver"
739
735
  name = "TestJobHookReceiverLog"
740
736
  _job_class, job_model = get_job_class_and_model(module, name)
741
737
  self.assertFalse(job_model.hidden)
742
738
 
743
739
  def test_is_job_hook(self):
744
740
  with self.subTest(expected=False):
745
- module = "test_pass"
741
+ module = "pass"
746
742
  name = "TestPass"
747
743
  _job_class, job_model = get_job_class_and_model(module, name)
748
744
  self.assertFalse(job_model.is_job_hook_receiver)
749
745
 
750
746
  with self.subTest(expected=True):
751
- module = "test_job_hook_receiver"
747
+ module = "job_hook_receiver"
752
748
  name = "TestJobHookReceiverLog"
753
749
  _job_class, job_model = get_job_class_and_model(module, name)
754
750
  self.assertTrue(job_model.is_job_hook_receiver)
755
751
 
756
752
  def test_object_change_context(self):
757
- module = "test_job_hook_receiver"
753
+ module = "job_hook_receiver"
758
754
  name = "TestJobHookReceiverChange"
759
- 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)
760
756
  test_location = Location.objects.get(name="test_jhr")
761
757
  oc = get_changes_for_model(test_location).first()
762
758
  self.assertEqual(oc.change_context, ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK)
763
- self.assertEqual(oc.user_id, self.user.pk)
759
+ self.assertEqual(oc.user_id, job_result.user.pk)
764
760
 
765
761
  def test_missing_receive_job_hook_method(self):
766
- module = "test_job_hook_receiver"
762
+ module = "job_hook_receiver"
767
763
  name = "TestJobHookReceiverFail"
768
- logging.disable(logging.ERROR)
769
- job_result = create_job_result_and_run_job(module, name, data=self.data, commit=False)
770
- logging.disable(logging.NOTSET)
764
+ job_result = create_job_result_and_run_job(module, name, **self.data)
771
765
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
772
766
 
773
767
 
@@ -779,7 +773,7 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
779
773
  def setUp(self):
780
774
  super().setUp()
781
775
 
782
- module = "test_job_hook_receiver"
776
+ module = "job_hook_receiver"
783
777
  name = "TestJobHookReceiverLog"
784
778
  self.job_class, self.job_model = get_job_class_and_model(module, name)
785
779
  job_hook = JobHook(
@@ -794,13 +788,16 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
794
788
 
795
789
  def test_enqueue_job_hook(self):
796
790
  with web_request_context(user=self.user):
797
- 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)
798
793
  job_result = JobResult.objects.get(job_model=self.job_model)
799
794
  expected_log_messages = [
795
+ ("info", "Running job"),
800
796
  ("info", f"change: dcim | location Test Job Hook Location 1 created by {self.user.username}"),
801
797
  ("info", "action: create"),
802
- ("info", f"request.user: {self.user.username}"),
803
- ("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"),
804
801
  ]
805
802
  log_messages = JobLogEntry.objects.filter(job_result=job_result).values_list("log_level", "message")
806
803
  self.assertSequenceEqual(log_messages, expected_log_messages)
@@ -808,8 +805,9 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
808
805
  @mock.patch.object(JobResult, "enqueue_job")
809
806
  def test_enqueue_job_hook_skipped(self, mock_enqueue_job):
810
807
  change_context = JobHookChangeContext(user=self.user)
808
+ status = Status.objects.get_for_model(Location).first()
811
809
  with change_logging(change_context):
812
- 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)
813
811
 
814
812
  self.assertFalse(mock_enqueue_job.called)
815
813
 
@@ -819,8 +817,8 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
819
817
  for i in range(1, 7):
820
818
  ScheduledJob.objects.create(
821
819
  name=f"test{i}",
822
- task="nautobot.extras.jobs.scheduled_job_handler",
823
- job_class="local/test_pass/TestPass",
820
+ task="pass.TestPass",
821
+ job_class="pass.TestPass",
824
822
  interval=JobExecutionType.TYPE_FUTURE,
825
823
  user=self.user,
826
824
  start_time=timezone.now() - datetime.timedelta(days=i * 30),
@@ -829,8 +827,8 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
829
827
 
830
828
  ScheduledJob.objects.create(
831
829
  name="test7",
832
- task="nautobot.extras.jobs.scheduled_job_handler",
833
- job_class="local/test_pass/TestPass",
830
+ task="pass.TestPass",
831
+ job_class="pass.TestPass",
834
832
  interval=JobExecutionType.TYPE_DAILY,
835
833
  user=self.user,
836
834
  start_time=timezone.now() - datetime.timedelta(days=180),
@@ -858,8 +856,8 @@ class ScheduledJobIntervalTestCase(TestCase):
858
856
  start_time = timezone.now() + datetime.timedelta(days=6)
859
857
  scheduled_job = ScheduledJob.objects.create(
860
858
  name="weekly_interval",
861
- task="nautobot.extras.jobs.scheduled_job_handler",
862
- job_class="local/test_pass/TestPass",
859
+ task="pass.TestPass",
860
+ job_class="pass.TestPass",
863
861
  interval=JobExecutionType.TYPE_WEEKLY,
864
862
  user=self.user,
865
863
  start_time=start_time,