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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (780) hide show
  1. nautobot/apps/api.py +6 -8
  2. nautobot/apps/forms.py +0 -2
  3. nautobot/apps/ui.py +0 -8
  4. nautobot/circuits/api/serializers.py +9 -117
  5. nautobot/circuits/api/urls.py +1 -1
  6. nautobot/circuits/api/views.py +0 -1
  7. nautobot/circuits/forms.py +0 -65
  8. nautobot/circuits/migrations/0014_related_name_changes.py +1 -1
  9. nautobot/circuits/migrations/0016_tagsfield.py +34 -0
  10. nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
  11. nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
  12. nautobot/circuits/models.py +3 -87
  13. nautobot/circuits/navigation.py +14 -69
  14. nautobot/circuits/signals.py +0 -2
  15. nautobot/circuits/tables.py +39 -1
  16. nautobot/circuits/tests/integration/test_relationships.py +9 -9
  17. nautobot/circuits/tests/test_api.py +4 -8
  18. nautobot/circuits/tests/test_filters.py +10 -4
  19. nautobot/circuits/tests/test_models.py +5 -1
  20. nautobot/circuits/tests/test_views.py +27 -5
  21. nautobot/circuits/views.py +18 -10
  22. nautobot/core/api/__init__.py +8 -2
  23. nautobot/core/api/fields.py +15 -6
  24. nautobot/core/api/filter_backends.py +3 -2
  25. nautobot/core/api/metadata.py +237 -30
  26. nautobot/core/api/mixins.py +94 -0
  27. nautobot/core/api/pagination.py +4 -0
  28. nautobot/core/api/parsers.py +154 -0
  29. nautobot/core/api/renderers.py +153 -2
  30. nautobot/core/api/schema.py +46 -2
  31. nautobot/core/api/serializers.py +377 -35
  32. nautobot/core/api/urls.py +11 -3
  33. nautobot/core/api/utils.py +174 -2
  34. nautobot/core/api/versioning.py +32 -10
  35. nautobot/core/api/views.py +266 -72
  36. nautobot/core/apps/__init__.py +138 -220
  37. nautobot/core/celery/__init__.py +112 -41
  38. nautobot/core/celery/backends.py +19 -12
  39. nautobot/core/celery/control.py +46 -0
  40. nautobot/core/celery/encoders.py +53 -0
  41. nautobot/core/celery/log.py +38 -0
  42. nautobot/core/celery/schedulers.py +23 -4
  43. nautobot/core/celery/task.py +1 -16
  44. nautobot/core/checks.py +0 -27
  45. nautobot/core/choices.py +0 -113
  46. nautobot/core/{cli.py → cli/__init__.py} +1 -1
  47. nautobot/core/cli/__main__.py +3 -0
  48. nautobot/core/constants.py +0 -24
  49. nautobot/core/context_processors.py +12 -0
  50. nautobot/core/filters.py +2 -2
  51. nautobot/core/forms/__init__.py +0 -4
  52. nautobot/core/forms/fields.py +38 -65
  53. nautobot/core/forms/forms.py +4 -1
  54. nautobot/core/forms/utils.py +0 -52
  55. nautobot/core/graphql/schema.py +4 -27
  56. nautobot/core/jobs/__init__.py +75 -0
  57. nautobot/core/management/commands/build_ui.py +255 -0
  58. nautobot/core/management/commands/generate_test_data.py +3 -2
  59. nautobot/core/management/commands/post_upgrade.py +24 -24
  60. nautobot/core/models/__init__.py +26 -1
  61. nautobot/core/models/fields.py +24 -5
  62. nautobot/core/models/generics.py +2 -42
  63. nautobot/core/models/managers.py +5 -0
  64. nautobot/core/models/name_color_content_types.py +0 -14
  65. nautobot/core/models/tree_queries.py +14 -4
  66. nautobot/core/models/utils.py +5 -6
  67. nautobot/core/models/validators.py +17 -8
  68. nautobot/core/releases.py +8 -10
  69. nautobot/core/settings.py +80 -42
  70. nautobot/core/tables.py +5 -5
  71. nautobot/core/tasks.py +4 -7
  72. nautobot/core/templates/base.html +1 -49
  73. nautobot/core/templates/base_django.html +49 -0
  74. nautobot/core/templates/base_react.html +55 -0
  75. nautobot/core/templates/buttons/export.html +6 -4
  76. nautobot/core/templates/generic/object_bulk_create.html +10 -21
  77. nautobot/core/templates/generic/object_list.html +3 -1
  78. nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
  79. nautobot/core/templates/inc/footer.html +1 -0
  80. nautobot/core/templates/inc/javascript.html +0 -14
  81. nautobot/core/templates/inc/nav_menu.html +28 -33
  82. nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
  83. nautobot/core/templates/inc/relationships_table_rows.html +2 -2
  84. nautobot/core/templates/nautobot_config.py.j2 +8 -20
  85. nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
  86. nautobot/core/templates/rest_framework/api.html +8 -0
  87. nautobot/core/templatetags/buttons.py +32 -28
  88. nautobot/core/testing/__init__.py +47 -44
  89. nautobot/core/testing/api.py +362 -47
  90. nautobot/core/testing/filters.py +1 -1
  91. nautobot/core/testing/migrations.py +2 -0
  92. nautobot/core/testing/mixins.py +22 -9
  93. nautobot/core/testing/schema.py +2 -1
  94. nautobot/core/testing/views.py +21 -46
  95. nautobot/core/tests/integration/test_filters.py +17 -8
  96. nautobot/core/tests/integration/test_navbar.py +11 -34
  97. nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
  98. nautobot/core/tests/nautobot_config.py +2 -3
  99. nautobot/core/tests/test_api.py +290 -21
  100. nautobot/core/tests/test_checks.py +0 -7
  101. nautobot/core/tests/test_filters.py +107 -59
  102. nautobot/core/tests/test_forms.py +26 -92
  103. nautobot/core/tests/test_graphql.py +110 -77
  104. nautobot/core/tests/test_logging.py +4 -0
  105. nautobot/core/tests/test_managers.py +3 -1
  106. nautobot/core/tests/test_models.py +2 -0
  107. nautobot/core/tests/test_paginator.py +3 -1
  108. nautobot/core/tests/test_releases.py +12 -12
  109. nautobot/core/tests/test_templatetags_helpers.py +4 -4
  110. nautobot/core/tests/test_utils.py +32 -68
  111. nautobot/core/tests/test_views.py +12 -15
  112. nautobot/core/utils/data.py +17 -0
  113. nautobot/core/utils/deprecation.py +9 -6
  114. nautobot/core/utils/filtering.py +8 -3
  115. nautobot/core/utils/git.py +12 -4
  116. nautobot/core/utils/lookup.py +3 -1
  117. nautobot/core/utils/requests.py +1 -104
  118. nautobot/core/views/__init__.py +1 -0
  119. nautobot/core/views/generic.py +75 -110
  120. nautobot/core/views/mixins.py +52 -61
  121. nautobot/core/views/renderers.py +6 -7
  122. nautobot/core/views/utils.py +80 -0
  123. nautobot/dcim/api/serializers.py +160 -667
  124. nautobot/dcim/api/urls.py +1 -1
  125. nautobot/dcim/api/views.py +7 -44
  126. nautobot/dcim/choices.py +2 -0
  127. nautobot/dcim/filters/__init__.py +21 -0
  128. nautobot/dcim/form_mixins.py +1 -27
  129. nautobot/dcim/forms.py +19 -765
  130. nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -1
  131. nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -13
  132. nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -1
  133. nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -1
  134. nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
  135. nautobot/dcim/migrations/0035_related_name_changes.py +1 -1
  136. nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -1
  137. nautobot/dcim/migrations/0040_tagsfield.py +109 -0
  138. nautobot/dcim/migrations/{0040_ipam__namespaces.py → 0041_ipam__namespaces.py} +1 -1
  139. nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
  140. nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
  141. nautobot/dcim/models/cables.py +3 -33
  142. nautobot/dcim/models/device_component_templates.py +6 -0
  143. nautobot/dcim/models/device_components.py +12 -198
  144. nautobot/dcim/models/devices.py +30 -143
  145. nautobot/dcim/models/locations.py +3 -64
  146. nautobot/dcim/models/power.py +3 -50
  147. nautobot/dcim/models/racks.py +7 -84
  148. nautobot/dcim/navigation.py +141 -467
  149. nautobot/dcim/signals.py +0 -2
  150. nautobot/dcim/tables/locations.py +2 -2
  151. nautobot/dcim/tables/power.py +1 -2
  152. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
  153. nautobot/dcim/templates/dcim/devicetype.html +2 -2
  154. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
  155. nautobot/dcim/templates/dcim/location.html +16 -1
  156. nautobot/dcim/templates/dcim/locationtype.html +15 -0
  157. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
  158. nautobot/dcim/templates/dcim/rackgroup.html +0 -12
  159. nautobot/dcim/tests/test_api.py +166 -81
  160. nautobot/dcim/tests/test_cablepaths.py +41 -35
  161. nautobot/dcim/tests/test_filters.py +67 -23
  162. nautobot/dcim/tests/test_forms.py +5 -205
  163. nautobot/dcim/tests/test_graphql.py +7 -2
  164. nautobot/dcim/tests/test_migrations.py +6 -11
  165. nautobot/dcim/tests/test_models.py +182 -110
  166. nautobot/dcim/tests/test_natural_ordering.py +11 -8
  167. nautobot/dcim/tests/test_signals.py +6 -3
  168. nautobot/dcim/tests/test_views.py +197 -175
  169. nautobot/dcim/urls.py +11 -16
  170. nautobot/dcim/views.py +7 -134
  171. nautobot/docs/additional-features/caching.md +6 -87
  172. nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
  173. nautobot/docs/additional-features/jobs.md +177 -195
  174. nautobot/docs/administration/nautobot-server.md +6 -21
  175. nautobot/docs/administration/replicating-nautobot.md +0 -10
  176. nautobot/docs/configuration/optional-settings.md +32 -41
  177. nautobot/docs/configuration/required-settings.md +11 -52
  178. nautobot/docs/development/application-registry.md +2 -13
  179. nautobot/docs/development/extending-models.md +15 -17
  180. nautobot/docs/development/generic-views.md +0 -2
  181. nautobot/docs/development/getting-started.md +55 -5
  182. nautobot/docs/development/navigation-menu.md +22 -93
  183. nautobot/docs/development/react-ui.md +105 -0
  184. nautobot/docs/development/role-internals.md +1 -3
  185. nautobot/docs/development/style-guide.md +6 -4
  186. nautobot/docs/index.md +3 -2
  187. nautobot/docs/installation/migrating-from-netbox.md +11 -42
  188. nautobot/docs/installation/nautobot.md +1 -1
  189. nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  190. nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  191. nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  192. nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  193. nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
  194. nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  195. nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  196. nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  197. nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  198. nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  199. nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  200. nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  201. nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  202. nautobot/docs/installation/upgrading-from-nautobot-v1.md +170 -747
  203. nautobot/docs/models/dcim/device.md +3 -0
  204. nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
  205. nautobot/docs/models/extras/computedfield.md +4 -4
  206. nautobot/docs/models/extras/gitrepository.md +3 -0
  207. nautobot/docs/models/extras/job.md +1 -0
  208. nautobot/docs/models/extras/jobbutton.md +18 -13
  209. nautobot/docs/models/extras/jobhook.md +7 -4
  210. nautobot/docs/models/extras/jobresult.md +6 -2
  211. nautobot/docs/models/extras/relationship.md +2 -2
  212. nautobot/docs/models/extras/status.md +6 -19
  213. nautobot/docs/models/ipam/ipaddress.md +3 -0
  214. nautobot/docs/models/virtualization/virtualmachine.md +3 -0
  215. nautobot/docs/plugins/development.md +83 -21
  216. nautobot/docs/release-notes/version-1.5.md +53 -0
  217. nautobot/docs/release-notes/version-2.0.md +180 -0
  218. nautobot/docs/requirements.txt +1 -0
  219. nautobot/docs/rest-api/overview.md +384 -215
  220. nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
  221. nautobot/extras/admin.py +3 -5
  222. nautobot/extras/api/customfields.py +15 -39
  223. nautobot/extras/api/fields.py +0 -11
  224. nautobot/extras/api/mixins.py +45 -0
  225. nautobot/extras/api/relationships.py +63 -158
  226. nautobot/extras/api/serializers.py +165 -700
  227. nautobot/extras/api/urls.py +1 -1
  228. nautobot/extras/api/views.py +294 -280
  229. nautobot/extras/apps.py +4 -7
  230. nautobot/extras/choices.py +11 -9
  231. nautobot/extras/constants.py +9 -3
  232. nautobot/extras/datasources/__init__.py +2 -0
  233. nautobot/extras/datasources/git.py +135 -186
  234. nautobot/extras/datasources/registry.py +25 -35
  235. nautobot/extras/filters/__init__.py +20 -19
  236. nautobot/extras/filters/mixins.py +4 -4
  237. nautobot/extras/forms/forms.py +63 -127
  238. nautobot/extras/forms/mixins.py +23 -51
  239. nautobot/extras/health_checks.py +0 -33
  240. nautobot/extras/jobs.py +387 -565
  241. nautobot/extras/management/commands/runjob.py +24 -62
  242. nautobot/extras/managers.py +30 -7
  243. nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
  244. nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -1
  245. nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -1
  246. nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -7
  247. nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -32
  248. nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -1
  249. nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -1
  250. nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
  251. nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -1
  252. nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -2
  253. nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -1
  254. nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -1
  255. nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
  256. nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -1
  257. nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
  258. nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -9
  259. nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -1
  260. nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
  261. nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
  262. nautobot/extras/migrations/{0076_remove_name_field_and_make_label_field_non_nullable.py → 0077_remove_name_field_and_make_label_field_non_nullable.py} +1 -1
  263. nautobot/extras/migrations/{0077_remove_slug.py → 0078_remove_slug.py} +1 -5
  264. nautobot/extras/migrations/0079_tagsfield.py +28 -0
  265. nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
  266. nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
  267. nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
  268. nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
  269. nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
  270. nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
  271. nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
  272. nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
  273. nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
  274. nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
  275. nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
  276. nautobot/extras/models/__init__.py +2 -3
  277. nautobot/extras/models/change_logging.py +0 -36
  278. nautobot/extras/models/customfields.py +39 -33
  279. nautobot/extras/models/datasources.py +48 -50
  280. nautobot/extras/models/groups.py +5 -6
  281. nautobot/extras/models/jobs.py +189 -321
  282. nautobot/extras/models/mixins.py +0 -71
  283. nautobot/extras/models/models.py +0 -19
  284. nautobot/extras/models/relationships.py +19 -13
  285. nautobot/extras/models/roles.py +0 -34
  286. nautobot/extras/models/secrets.py +2 -26
  287. nautobot/extras/models/statuses.py +6 -5
  288. nautobot/extras/models/tags.py +2 -17
  289. nautobot/extras/navigation.py +89 -307
  290. nautobot/extras/plugins/__init__.py +3 -120
  291. nautobot/extras/plugins/utils.py +0 -3
  292. nautobot/extras/plugins/validators.py +5 -4
  293. nautobot/extras/plugins/views.py +16 -3
  294. nautobot/extras/querysets.py +1 -7
  295. nautobot/extras/registry.py +3 -0
  296. nautobot/extras/signals.py +26 -60
  297. nautobot/extras/tables.py +34 -40
  298. nautobot/extras/tasks.py +0 -12
  299. nautobot/extras/templates/extras/configcontext.html +1 -1
  300. nautobot/extras/templates/extras/configcontextschema.html +16 -1
  301. nautobot/extras/templates/extras/customfield.html +0 -13
  302. nautobot/extras/templates/extras/gitrepository.html +3 -3
  303. nautobot/extras/templates/extras/inc/jobresult.html +10 -0
  304. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  305. nautobot/extras/templates/extras/job.html +35 -25
  306. nautobot/extras/templates/extras/job_approval_request.html +15 -30
  307. nautobot/extras/templates/extras/job_detail.html +13 -31
  308. nautobot/extras/templates/extras/job_edit.html +15 -17
  309. nautobot/extras/templates/extras/jobresult.html +24 -6
  310. nautobot/extras/templates/extras/scheduledjob.html +2 -2
  311. nautobot/extras/templates/extras/secret.html +28 -0
  312. nautobot/extras/templatetags/job_buttons.py +1 -0
  313. nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
  314. nautobot/extras/test_jobs/atomic_transaction.py +53 -0
  315. nautobot/extras/test_jobs/dry_run.py +29 -0
  316. nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
  317. nautobot/extras/test_jobs/duplicate_name2.py +9 -0
  318. nautobot/extras/test_jobs/fail.py +23 -0
  319. nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
  320. nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
  321. nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
  322. nautobot/extras/test_jobs/file_upload_pass.py +25 -0
  323. nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
  324. nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
  325. nautobot/extras/test_jobs/job_button_receiver.py +28 -0
  326. nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
  327. nautobot/extras/test_jobs/job_variables.py +88 -0
  328. nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
  329. nautobot/extras/test_jobs/log_redaction.py +20 -0
  330. nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
  331. nautobot/extras/test_jobs/modify_db.py +25 -0
  332. nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
  333. nautobot/extras/test_jobs/object_var_optional.py +21 -0
  334. nautobot/extras/test_jobs/object_var_required.py +21 -0
  335. nautobot/extras/test_jobs/object_vars.py +26 -0
  336. nautobot/extras/test_jobs/pass.py +25 -0
  337. nautobot/extras/test_jobs/profiling.py +32 -0
  338. nautobot/extras/test_jobs/read_only_job.py +15 -0
  339. nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
  340. nautobot/extras/{tests/example_jobs/test_soft_time_limit_greater_than_time_limit.py → test_jobs/soft_time_limit_greater_than_time_limit.py} +5 -1
  341. nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
  342. nautobot/extras/tests/integration/test_computedfields.py +1 -1
  343. nautobot/extras/tests/integration/test_configcontextschema.py +5 -3
  344. nautobot/extras/tests/integration/test_customfields.py +4 -2
  345. nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
  346. nautobot/extras/tests/integration/test_jobs.py +25 -27
  347. nautobot/extras/tests/integration/test_notes.py +8 -4
  348. nautobot/extras/tests/integration/test_relationships.py +2 -2
  349. nautobot/extras/tests/test_api.py +649 -642
  350. nautobot/extras/tests/test_changelog.py +3 -3
  351. nautobot/extras/tests/test_context_managers.py +5 -3
  352. nautobot/extras/tests/test_customfields.py +92 -50
  353. nautobot/extras/tests/test_datasources.py +189 -112
  354. nautobot/extras/tests/test_dynamicgroups.py +7 -8
  355. nautobot/extras/tests/test_filters.py +137 -89
  356. nautobot/extras/tests/test_forms.py +73 -75
  357. nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
  358. nautobot/extras/tests/test_jobs.py +262 -263
  359. nautobot/extras/tests/test_migrations.py +4 -3
  360. nautobot/extras/tests/test_models.py +116 -161
  361. nautobot/extras/tests/test_plugins.py +38 -60
  362. nautobot/extras/tests/test_relationships.py +167 -120
  363. nautobot/extras/tests/test_tags.py +6 -11
  364. nautobot/extras/tests/test_utils.py +31 -1
  365. nautobot/extras/tests/test_views.py +201 -145
  366. nautobot/extras/tests/test_webhooks.py +6 -2
  367. nautobot/extras/urls.py +42 -42
  368. nautobot/extras/utils.py +137 -163
  369. nautobot/extras/views.py +78 -152
  370. nautobot/ipam/api/fields.py +17 -0
  371. nautobot/ipam/api/serializers.py +58 -164
  372. nautobot/ipam/api/urls.py +1 -1
  373. nautobot/ipam/api/views.py +3 -2
  374. nautobot/ipam/apps.py +1 -2
  375. nautobot/ipam/filters.py +1 -10
  376. nautobot/ipam/forms.py +4 -177
  377. nautobot/ipam/lookups.py +1 -0
  378. nautobot/ipam/management/commands/__init__.py +0 -0
  379. nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
  380. nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -1
  381. nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -38
  382. nautobot/ipam/migrations/0020_related_name_changes.py +1 -1
  383. nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +2 -2
  384. nautobot/ipam/migrations/0028_tagsfield.py +44 -0
  385. nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
  386. nautobot/ipam/migrations/{0028_ipam__namespaces.py → 0030_ipam__namespaces.py} +77 -28
  387. nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
  388. nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
  389. nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
  390. nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
  391. nautobot/ipam/models.py +100 -236
  392. nautobot/ipam/navigation.py +36 -181
  393. nautobot/ipam/querysets.py +20 -25
  394. nautobot/ipam/signals.py +49 -6
  395. nautobot/ipam/tables.py +10 -3
  396. nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
  397. nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
  398. nautobot/ipam/templates/ipam/namespace_retrieve.html +17 -4
  399. nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
  400. nautobot/ipam/templates/ipam/prefix.html +1 -1
  401. nautobot/ipam/templates/ipam/vlangroup.html +0 -13
  402. nautobot/ipam/templates/ipam/vrf_edit.html +6 -0
  403. nautobot/ipam/tests/integration/test_prefixes.py +3 -26
  404. nautobot/ipam/tests/test_api.py +22 -19
  405. nautobot/ipam/tests/test_filters.py +59 -23
  406. nautobot/ipam/tests/test_migrations.py +6 -10
  407. nautobot/ipam/tests/test_models.py +323 -198
  408. nautobot/ipam/tests/test_ordering.py +2 -2
  409. nautobot/ipam/tests/test_querysets.py +44 -24
  410. nautobot/ipam/tests/test_views.py +73 -26
  411. nautobot/ipam/urls.py +16 -0
  412. nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
  413. nautobot/ipam/utils/migrations.py +713 -0
  414. nautobot/ipam/views.py +137 -20
  415. nautobot/project-static/docs/404.html +1178 -10
  416. nautobot/project-static/docs/additional-features/caching.html +1224 -159
  417. nautobot/project-static/docs/additional-features/change-logging.html +1180 -12
  418. nautobot/project-static/docs/additional-features/config-contexts.html +1180 -12
  419. nautobot/project-static/docs/additional-features/graphql.html +1179 -11
  420. nautobot/project-static/docs/additional-features/healthcheck.html +1180 -12
  421. nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1184 -12
  422. nautobot/project-static/docs/additional-features/jobs.html +1514 -328
  423. nautobot/project-static/docs/additional-features/napalm.html +1180 -12
  424. nautobot/project-static/docs/additional-features/prometheus-metrics.html +1180 -12
  425. nautobot/project-static/docs/additional-features/template-filters.html +1180 -12
  426. nautobot/project-static/docs/administration/celery-queues.html +1178 -10
  427. nautobot/project-static/docs/administration/nautobot-server.html +1451 -304
  428. nautobot/project-static/docs/administration/nautobot-shell.html +1178 -10
  429. nautobot/project-static/docs/administration/permissions.html +1178 -10
  430. nautobot/project-static/docs/administration/replicating-nautobot.html +1262 -113
  431. nautobot/project-static/docs/apps/index.html +1178 -10
  432. nautobot/project-static/docs/apps/nautobot-apps.html +1178 -10
  433. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1580 -426
  434. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1178 -10
  435. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3481 -1838
  436. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1178 -10
  437. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1178 -10
  438. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1185 -11
  439. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1719 -551
  440. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2062 -930
  441. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1946 -659
  442. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1180 -12
  443. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1189 -21
  444. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9283 -6218
  445. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2734 -2122
  446. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1178 -10
  447. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2337 -1300
  448. nautobot/project-static/docs/configuration/authentication/ldap.html +1178 -10
  449. nautobot/project-static/docs/configuration/authentication/remote.html +1178 -10
  450. nautobot/project-static/docs/configuration/authentication/sso.html +1178 -10
  451. nautobot/project-static/docs/configuration/index.html +1178 -10
  452. nautobot/project-static/docs/configuration/optional-settings.html +1311 -160
  453. nautobot/project-static/docs/configuration/required-settings.html +1312 -211
  454. nautobot/project-static/docs/core-functionality/circuits.html +1178 -10
  455. nautobot/project-static/docs/core-functionality/device-types.html +1178 -10
  456. nautobot/project-static/docs/core-functionality/devices.html +1182 -10
  457. nautobot/project-static/docs/core-functionality/ipam.html +1182 -10
  458. nautobot/project-static/docs/core-functionality/power.html +1178 -10
  459. nautobot/project-static/docs/core-functionality/secrets.html +1178 -10
  460. nautobot/project-static/docs/core-functionality/services.html +1178 -10
  461. nautobot/project-static/docs/core-functionality/sites-and-racks.html +1178 -10
  462. nautobot/project-static/docs/core-functionality/tenancy.html +1178 -10
  463. nautobot/project-static/docs/core-functionality/virtualization.html +1182 -10
  464. nautobot/project-static/docs/core-functionality/vlans.html +1179 -11
  465. nautobot/project-static/docs/development/application-registry.html +1190 -42
  466. nautobot/project-static/docs/development/best-practices.html +1178 -10
  467. nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1178 -10
  468. nautobot/project-static/docs/development/extending-models.html +1238 -83
  469. nautobot/project-static/docs/development/generic-views.html +1180 -14
  470. nautobot/project-static/docs/development/getting-started.html +1365 -90
  471. nautobot/project-static/docs/development/homepage.html +1178 -10
  472. nautobot/project-static/docs/development/index.html +1178 -10
  473. nautobot/project-static/docs/development/model-features.html +1178 -10
  474. nautobot/project-static/docs/development/natural-keys.html +1178 -10
  475. nautobot/project-static/docs/development/navigation-menu.html +1215 -125
  476. nautobot/project-static/docs/development/react-ui.html +4199 -0
  477. nautobot/project-static/docs/development/release-checklist.html +1178 -10
  478. nautobot/project-static/docs/development/role-internals.html +1179 -12
  479. nautobot/project-static/docs/development/style-guide.html +1188 -19
  480. nautobot/project-static/docs/development/templates.html +1178 -10
  481. nautobot/project-static/docs/development/testing.html +1178 -10
  482. nautobot/project-static/docs/development/user-preferences.html +1178 -10
  483. nautobot/project-static/docs/docker/index.html +1178 -10
  484. nautobot/project-static/docs/index.html +1183 -12
  485. nautobot/project-static/docs/installation/centos.html +1178 -10
  486. nautobot/project-static/docs/installation/external-authentication.html +1178 -10
  487. nautobot/project-static/docs/installation/http-server.html +1178 -10
  488. nautobot/project-static/docs/installation/index.html +1178 -10
  489. nautobot/project-static/docs/installation/migrating-from-netbox.html +1305 -189
  490. nautobot/project-static/docs/installation/migrating-from-postgresql.html +1178 -10
  491. nautobot/project-static/docs/installation/nautobot.html +1179 -11
  492. nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1178 -10
  493. nautobot/project-static/docs/installation/selinux-troubleshooting.html +1178 -10
  494. nautobot/project-static/docs/installation/services.html +1178 -10
  495. nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  496. nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  497. nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  498. nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  499. nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
  500. nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  501. nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  502. nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  503. nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  504. nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  505. nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  506. nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  507. nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  508. nautobot/project-static/docs/installation/ubuntu.html +1178 -10
  509. nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +3823 -2152
  510. nautobot/project-static/docs/installation/upgrading.html +1178 -10
  511. nautobot/project-static/docs/models/circuits/circuit.html +1293 -103
  512. nautobot/project-static/docs/models/circuits/circuittermination.html +1293 -103
  513. nautobot/project-static/docs/models/circuits/circuittype.html +1293 -103
  514. nautobot/project-static/docs/models/circuits/provider.html +1293 -103
  515. nautobot/project-static/docs/models/circuits/providernetwork.html +1293 -103
  516. nautobot/project-static/docs/models/dcim/cable.html +1324 -103
  517. nautobot/project-static/docs/models/dcim/consoleport.html +1293 -103
  518. nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1293 -103
  519. nautobot/project-static/docs/models/dcim/consoleserverport.html +1293 -103
  520. nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1293 -103
  521. nautobot/project-static/docs/models/dcim/device.html +1326 -132
  522. nautobot/project-static/docs/models/dcim/devicebay.html +1293 -103
  523. nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1293 -103
  524. nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1379 -97
  525. nautobot/project-static/docs/models/dcim/devicetype.html +1293 -103
  526. nautobot/project-static/docs/models/dcim/frontport.html +1293 -103
  527. nautobot/project-static/docs/models/dcim/frontporttemplate.html +1293 -103
  528. nautobot/project-static/docs/models/dcim/interface.html +1293 -103
  529. nautobot/project-static/docs/models/dcim/interfacetemplate.html +1293 -103
  530. nautobot/project-static/docs/models/dcim/inventoryitem.html +1293 -103
  531. nautobot/project-static/docs/models/dcim/location.html +1293 -103
  532. nautobot/project-static/docs/models/dcim/locationtype.html +1293 -103
  533. nautobot/project-static/docs/models/dcim/manufacturer.html +1292 -102
  534. nautobot/project-static/docs/models/dcim/platform.html +1272 -82
  535. nautobot/project-static/docs/models/dcim/powerfeed.html +1270 -80
  536. nautobot/project-static/docs/models/dcim/poweroutlet.html +1272 -82
  537. nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1272 -82
  538. nautobot/project-static/docs/models/dcim/powerpanel.html +1270 -80
  539. nautobot/project-static/docs/models/dcim/powerport.html +1272 -82
  540. nautobot/project-static/docs/models/dcim/powerporttemplate.html +1272 -82
  541. nautobot/project-static/docs/models/dcim/rack.html +1272 -82
  542. nautobot/project-static/docs/models/dcim/rackgroup.html +1272 -82
  543. nautobot/project-static/docs/models/dcim/rackreservation.html +1272 -82
  544. nautobot/project-static/docs/models/dcim/rearport.html +1286 -96
  545. nautobot/project-static/docs/models/dcim/rearporttemplate.html +1286 -96
  546. nautobot/project-static/docs/models/dcim/region.html +1178 -10
  547. nautobot/project-static/docs/models/dcim/site.html +1178 -10
  548. nautobot/project-static/docs/models/dcim/virtualchassis.html +1284 -94
  549. nautobot/project-static/docs/models/extras/computedfield.html +1184 -16
  550. nautobot/project-static/docs/models/extras/configcontext.html +1314 -86
  551. nautobot/project-static/docs/models/extras/configcontextschema.html +1276 -86
  552. nautobot/project-static/docs/models/extras/customfield.html +1180 -12
  553. nautobot/project-static/docs/models/extras/customlink.html +1180 -12
  554. nautobot/project-static/docs/models/extras/dynamicgroup.html +1180 -12
  555. nautobot/project-static/docs/models/extras/exporttemplate.html +1180 -12
  556. nautobot/project-static/docs/models/extras/gitrepository.html +1184 -12
  557. nautobot/project-static/docs/models/extras/graphqlquery.html +1321 -86
  558. nautobot/project-static/docs/models/extras/imageattachment.html +1276 -86
  559. nautobot/project-static/docs/models/extras/job.html +1277 -86
  560. nautobot/project-static/docs/models/extras/jobbutton.html +1201 -29
  561. nautobot/project-static/docs/models/extras/jobhook.html +1188 -16
  562. nautobot/project-static/docs/models/extras/joblogentry.html +1274 -84
  563. nautobot/project-static/docs/models/extras/jobresult.html +1364 -169
  564. nautobot/project-static/docs/models/extras/note.html +1180 -12
  565. nautobot/project-static/docs/models/extras/relationship.html +1182 -14
  566. nautobot/project-static/docs/models/extras/role.html +1320 -86
  567. nautobot/project-static/docs/models/extras/secret.html +1314 -86
  568. nautobot/project-static/docs/models/extras/secretsgroup.html +1276 -86
  569. nautobot/project-static/docs/models/extras/status.html +1188 -59
  570. nautobot/project-static/docs/models/extras/tag.html +1180 -12
  571. nautobot/project-static/docs/models/extras/webhook.html +1180 -12
  572. nautobot/project-static/docs/models/ipam/ipaddress.html +1327 -102
  573. nautobot/project-static/docs/models/ipam/prefix.html +1276 -86
  574. nautobot/project-static/docs/models/ipam/rir.html +1276 -86
  575. nautobot/project-static/docs/models/ipam/routetarget.html +1276 -86
  576. nautobot/project-static/docs/models/ipam/service.html +1276 -86
  577. nautobot/project-static/docs/models/ipam/vlan.html +1276 -86
  578. nautobot/project-static/docs/models/ipam/vlangroup.html +1276 -86
  579. nautobot/project-static/docs/models/ipam/vrf.html +1276 -86
  580. nautobot/project-static/docs/models/tenancy/tenant.html +1276 -86
  581. nautobot/project-static/docs/models/tenancy/tenantgroup.html +1276 -86
  582. nautobot/project-static/docs/models/users/objectpermission.html +1314 -86
  583. nautobot/project-static/docs/models/users/token.html +1276 -86
  584. nautobot/project-static/docs/models/virtualization/cluster.html +1276 -86
  585. nautobot/project-static/docs/models/virtualization/clustergroup.html +1276 -86
  586. nautobot/project-static/docs/models/virtualization/clustertype.html +1276 -86
  587. nautobot/project-static/docs/models/virtualization/virtualmachine.html +1321 -127
  588. nautobot/project-static/docs/models/virtualization/vminterface.html +1276 -86
  589. nautobot/project-static/docs/objects.inv +0 -0
  590. nautobot/project-static/docs/plugins/development.html +1726 -495
  591. nautobot/project-static/docs/plugins/index.html +1178 -10
  592. nautobot/project-static/docs/plugins/porting-from-netbox.html +1178 -10
  593. nautobot/project-static/docs/release-notes/index.html +1178 -10
  594. nautobot/project-static/docs/release-notes/version-1.0.html +1178 -10
  595. nautobot/project-static/docs/release-notes/version-1.1.html +1178 -10
  596. nautobot/project-static/docs/release-notes/version-1.2.html +1178 -10
  597. nautobot/project-static/docs/release-notes/version-1.3.html +1178 -10
  598. nautobot/project-static/docs/release-notes/version-1.4.html +1178 -10
  599. nautobot/project-static/docs/release-notes/version-1.5.html +1608 -225
  600. nautobot/project-static/docs/release-notes/version-2.0.html +1547 -47
  601. nautobot/project-static/docs/requirements.txt +1 -0
  602. nautobot/project-static/docs/rest-api/authentication.html +1179 -11
  603. nautobot/project-static/docs/rest-api/filtering.html +1178 -10
  604. nautobot/project-static/docs/rest-api/overview.html +1841 -446
  605. nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
  606. nautobot/project-static/docs/search/search_index.json +1 -1
  607. nautobot/project-static/docs/sitemap.xml +197 -187
  608. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  609. nautobot/project-static/docs/user-guides/custom-fields.html +1178 -10
  610. nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1178 -10
  611. nautobot/project-static/docs/user-guides/getting-started/index.html +1178 -10
  612. nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1178 -10
  613. nautobot/project-static/docs/user-guides/getting-started/ipam.html +1178 -10
  614. nautobot/project-static/docs/user-guides/getting-started/platforms.html +1178 -10
  615. nautobot/project-static/docs/user-guides/getting-started/regions.html +1178 -10
  616. nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1178 -10
  617. nautobot/project-static/docs/user-guides/getting-started/tenants.html +1178 -10
  618. nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1178 -10
  619. nautobot/project-static/docs/user-guides/git-data-source.html +1178 -10
  620. nautobot/project-static/docs/user-guides/graphql.html +1178 -10
  621. nautobot/project-static/docs/user-guides/relationships.html +1178 -10
  622. nautobot/project-static/docs/user-guides/s3-django-storage.html +1178 -10
  623. nautobot/project-static/js/forms.js +16 -9
  624. nautobot/project-static/js/theme.js +5 -0
  625. nautobot/tenancy/api/serializers.py +4 -32
  626. nautobot/tenancy/api/urls.py +1 -1
  627. nautobot/tenancy/forms.py +0 -28
  628. nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
  629. nautobot/tenancy/models.py +0 -25
  630. nautobot/tenancy/navigation.py +6 -39
  631. nautobot/tenancy/templates/tenancy/tenant.html +12 -12
  632. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  633. nautobot/tenancy/tests/test_api.py +1 -3
  634. nautobot/tenancy/tests/test_filters.py +10 -5
  635. nautobot/tenancy/views.py +0 -2
  636. nautobot/ui/.eslintignore +6 -0
  637. nautobot/ui/.gitignore +10 -0
  638. nautobot/ui/.prettierignore +9 -0
  639. nautobot/ui/.prettierrc +4 -0
  640. nautobot/ui/README.md +33 -0
  641. nautobot/ui/app_imports.js.j2 +7 -0
  642. nautobot/ui/craco.config.js +46 -0
  643. nautobot/ui/jsconfig-base.json +11 -0
  644. nautobot/ui/jsconfig.json +5 -0
  645. nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
  646. nautobot/ui/package-lock.json +21451 -0
  647. nautobot/ui/package.json +70 -0
  648. nautobot/ui/public/index.html +47 -0
  649. nautobot/ui/public/logo192.png +0 -0
  650. nautobot/ui/public/logo512.png +0 -0
  651. nautobot/ui/public/manifest.json +25 -0
  652. nautobot/ui/public/nautobot_logo.svg +131 -0
  653. nautobot/ui/public/robots.txt +3 -0
  654. nautobot/ui/src/App.js +71 -0
  655. nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
  656. nautobot/ui/src/components/AppTab.js +40 -0
  657. nautobot/ui/src/components/Apps.js +60 -0
  658. nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
  659. nautobot/ui/src/components/HomePanel.js +58 -0
  660. nautobot/ui/src/components/JobHistoryTable.js +78 -0
  661. nautobot/ui/src/components/Layout.js +53 -0
  662. nautobot/ui/src/components/LoadingWidget.js +25 -0
  663. nautobot/ui/src/components/Navbar.js +116 -0
  664. nautobot/ui/src/components/NotificationPopover.js +27 -0
  665. nautobot/ui/src/components/ObjectListTable.js +209 -0
  666. nautobot/ui/src/components/ReferenceDataTag.js +35 -0
  667. nautobot/ui/src/components/RouterButton.js +10 -0
  668. nautobot/ui/src/components/RouterLink.js +10 -0
  669. nautobot/ui/src/components/SidebarNav.js +147 -0
  670. nautobot/ui/src/components/Table.js +48 -0
  671. nautobot/ui/src/components/TableItem.js +71 -0
  672. nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
  673. nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
  674. nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
  675. nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
  676. nautobot/ui/src/components/__tests__/Table.test.js +36 -0
  677. nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
  678. nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
  679. nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
  680. nautobot/ui/src/components/pagination.js +93 -0
  681. nautobot/ui/src/components/paginator.js +79 -0
  682. nautobot/ui/src/components/paginator_form.js +43 -0
  683. nautobot/ui/src/components/usePagination.js +57 -0
  684. nautobot/ui/src/constants/apiPath.js +10 -0
  685. nautobot/ui/src/constants/icons.js +15 -0
  686. nautobot/ui/src/constants/size.js +15 -0
  687. nautobot/ui/src/index.js +65 -0
  688. nautobot/ui/src/reportWebVitals.js +15 -0
  689. nautobot/ui/src/router.js +77 -0
  690. nautobot/ui/src/utils/api.js +131 -0
  691. nautobot/ui/src/utils/app-import.js +15 -0
  692. nautobot/ui/src/utils/color.js +15 -0
  693. nautobot/ui/src/utils/date.js +14 -0
  694. nautobot/ui/src/utils/index.js +15 -0
  695. nautobot/ui/src/utils/navigation.js +32 -0
  696. nautobot/ui/src/utils/session.js +64 -0
  697. nautobot/ui/src/utils/store.js +242 -0
  698. nautobot/ui/src/utils/string.js +6 -0
  699. nautobot/ui/src/utils/url.js +4 -0
  700. nautobot/ui/src/views/Home.js +138 -0
  701. nautobot/ui/src/views/InstalledApps.js +80 -0
  702. nautobot/ui/src/views/Login.js +48 -0
  703. nautobot/ui/src/views/Logout.js +20 -0
  704. nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
  705. nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
  706. nautobot/ui/src/views/__tests__/Login.test.js +15 -0
  707. nautobot/ui/src/views/generic/GenericView.js +142 -0
  708. nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
  709. nautobot/ui/src/views/generic/ObjectList.js +127 -0
  710. nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
  711. nautobot/users/admin.py +1 -1
  712. nautobot/users/api/serializers.py +51 -61
  713. nautobot/users/api/urls.py +1 -1
  714. nautobot/users/api/views.py +53 -2
  715. nautobot/users/tests/test_api.py +110 -25
  716. nautobot/virtualization/api/serializers.py +18 -130
  717. nautobot/virtualization/api/urls.py +1 -1
  718. nautobot/virtualization/api/views.py +1 -22
  719. nautobot/virtualization/forms.py +13 -99
  720. nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -1
  721. nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -11
  722. nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -1
  723. nautobot/virtualization/migrations/0018_related_name_changes.py +1 -1
  724. nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
  725. nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
  726. nautobot/virtualization/migrations/{0021_ipam__namespaces.py → 0023_ipam__namespaces.py} +2 -2
  727. nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
  728. nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
  729. nautobot/virtualization/models.py +31 -123
  730. nautobot/virtualization/navigation.py +18 -99
  731. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -1
  732. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
  733. nautobot/virtualization/tests/test_api.py +25 -26
  734. nautobot/virtualization/tests/test_filters.py +41 -15
  735. nautobot/virtualization/tests/test_models.py +31 -7
  736. nautobot/virtualization/tests/test_views.py +42 -25
  737. nautobot/virtualization/views.py +7 -6
  738. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +3 -7
  739. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +744 -602
  740. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
  741. nautobot/circuits/api/nested_serializers.py +0 -69
  742. nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
  743. nautobot/dcim/api/nested_serializers.py +0 -356
  744. nautobot/dcim/templates/dcim/device_import.html +0 -5
  745. nautobot/dcim/templates/dcim/device_import_child.html +0 -5
  746. nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
  747. nautobot/extras/api/nested_serializers.py +0 -353
  748. nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -41
  749. nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -46
  750. nautobot/extras/reports.py +0 -60
  751. nautobot/extras/scripts.py +0 -72
  752. nautobot/extras/tests/example_jobs/script_variables.py +0 -67
  753. nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
  754. nautobot/extras/tests/example_jobs/test_fail.py +0 -16
  755. nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
  756. nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
  757. nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
  758. nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
  759. nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
  760. nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
  761. nautobot/extras/tests/example_jobs/test_modify_db.py +0 -18
  762. nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
  763. nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
  764. nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
  765. nautobot/extras/tests/example_jobs/test_pass.py +0 -19
  766. nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
  767. nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
  768. nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
  769. nautobot/ipam/api/nested_serializers.py +0 -159
  770. nautobot/ipam/migrations/0029_ipam__prefix__add_parent.py +0 -31
  771. nautobot/ipam/migrations/0030_ipam__prefix__data_migration.py +0 -13
  772. nautobot/ipam/migrations/0031_ipam__ipaddress__add_parent.py +0 -41
  773. nautobot/ipam/migrations/0032_ipam__ipaddress__data_migration.py +0 -11
  774. nautobot/tenancy/api/nested_serializers.py +0 -31
  775. nautobot/users/api/nested_serializers.py +0 -67
  776. nautobot/virtualization/api/nested_serializers.py +0 -65
  777. /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
  778. /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
  779. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
  780. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -5,6 +5,7 @@ import logging
5
5
  import mimetypes
6
6
  import os
7
7
  import re
8
+ import sys
8
9
  from urllib.parse import quote
9
10
 
10
11
  from django.conf import settings
@@ -14,13 +15,10 @@ from django.db import transaction
14
15
  from django.utils.text import slugify
15
16
  import yaml
16
17
 
17
- from nautobot.core.celery import nautobot_task
18
+ from nautobot.core.celery import app as celery_app
18
19
  from nautobot.core.utils.git import GitRepo
19
- from nautobot.core.utils.requests import copy_safe_request
20
20
  from nautobot.dcim.models import Device, DeviceType, Location, Platform
21
21
  from nautobot.extras.choices import (
22
- JobSourceChoices,
23
- JobResultStatusChoices,
24
22
  LogLevelChoices,
25
23
  SecretsGroupAccessTypeChoices,
26
24
  SecretsGroupSecretTypeChoices,
@@ -32,16 +30,14 @@ from nautobot.extras.models import (
32
30
  ExportTemplate,
33
31
  GitRepository,
34
32
  Job,
35
- JobLogEntry,
36
33
  JobResult,
37
34
  Role,
38
35
  Tag,
39
36
  )
40
37
  from nautobot.extras.registry import DatasourceContent, register_datasource_contents
41
- from nautobot.extras.utils import jobs_in_directory, refresh_job_model_from_job_class
38
+ from nautobot.extras.utils import refresh_job_model_from_job_class
42
39
  from nautobot.tenancy.models import TenantGroup, Tenant
43
40
  from nautobot.virtualization.models import ClusterGroup, Cluster, VirtualMachine
44
- from .registry import refresh_datasource_content
45
41
  from .utils import files_from_contenttype_directories
46
42
 
47
43
 
@@ -54,31 +50,29 @@ GitJobResult = namedtuple("GitJobResult", ["job_result", "repository_record"])
54
50
  GitRepoInfo = namedtuple("GitRepoInfo", ["from_url", "to_path", "from_branch"])
55
51
 
56
52
 
57
- def enqueue_git_repository_helper(repository, request, func, **kwargs):
53
+ def enqueue_git_repository_helper(repository, user, job_class, **kwargs):
58
54
  """
59
55
  Wrapper for JobResult.enqueue_job() to enqueue one of several possible Git repository functions.
60
56
  """
61
- git_repository_content_type = ContentType.objects.get_for_model(GitRepository)
62
- JobResult.enqueue_job(
63
- func,
64
- repository.name,
65
- git_repository_content_type,
66
- request.user,
67
- repository_pk=repository.pk,
68
- request=copy_safe_request(request),
69
- )
57
+ job_model = job_class().job_model
70
58
 
59
+ return JobResult.enqueue_job(job_model, user, repository=repository.pk)
71
60
 
72
- def enqueue_git_repository_diff_origin_and_local(repository, request):
61
+
62
+ def enqueue_git_repository_diff_origin_and_local(repository, user):
73
63
  """Convenience wrapper for JobResult.enqueue_job() to enqueue the git_repository_diff_origin_and_local job."""
74
- enqueue_git_repository_helper(repository, request, git_repository_diff_origin_and_local)
64
+ from nautobot.core.jobs import GitRepositoryDryRun
65
+
66
+ return enqueue_git_repository_helper(repository, user, GitRepositoryDryRun)
75
67
 
76
68
 
77
- def enqueue_pull_git_repository_and_refresh_data(repository, request):
69
+ def enqueue_pull_git_repository_and_refresh_data(repository, user):
78
70
  """
79
71
  Convenience wrapper for JobResult.enqueue_job() to enqueue the pull_git_repository_and_refresh_data job.
80
72
  """
81
- enqueue_git_repository_helper(repository, request, pull_git_repository_and_refresh_data)
73
+ from nautobot.core.jobs import GitRepositorySync
74
+
75
+ return enqueue_git_repository_helper(repository, user, GitRepositorySync)
82
76
 
83
77
 
84
78
  def get_job_result_and_repository_record(repository_pk, job_result_pk, logger): # pylint: disable=redefined-outer-name
@@ -97,121 +91,14 @@ def get_job_result_and_repository_record(repository_pk, job_result_pk, logger):
97
91
  if not repository_record:
98
92
  job_result.log(
99
93
  f"No GitRepository {repository_pk} found!",
100
- level_choice=LogLevelChoices.LOG_FAILURE,
94
+ level_choice=LogLevelChoices.LOG_ERROR,
101
95
  logger=logger,
102
96
  )
103
- job_result.set_status(JobResultStatusChoices.STATUS_FAILURE)
104
- job_result.save()
105
97
  return GitJobResult(job_result=job_result, repository_record=None)
106
98
 
107
99
  return GitJobResult(job_result=job_result, repository_record=repository_record)
108
100
 
109
101
 
110
- # TODO(jathan): This should likely be deleted since it's coercing state and this
111
- # should be managed by the database backend.
112
- def log_job_result_final_status(job_result, job_type):
113
- """Check Job status and save log to DB
114
- Args:
115
- job_result (JobResult): JobResult Instance
116
- job_type (str): job type which is used in log message, e.g dry run/synchronization etc.
117
- """
118
- if job_result.status not in JobResultStatusChoices.READY_STATES:
119
- if JobLogEntry.objects.filter(job_result__pk=job_result.pk, log_level=LogLevelChoices.LOG_FAILURE).exists():
120
- job_result.set_status(JobResultStatusChoices.STATUS_FAILURE)
121
- else:
122
- job_result.set_status(JobResultStatusChoices.STATUS_SUCCESS)
123
- job_result.log(
124
- f"Repository {job_type} completed in {job_result.duration}",
125
- level_choice=LogLevelChoices.LOG_INFO,
126
- logger=logger,
127
- )
128
- job_result.save()
129
-
130
-
131
- # TODO(jathan): The state transition stuff here should be deleted in exchange
132
- # for trusting the database backend.
133
- @nautobot_task
134
- def pull_git_repository_and_refresh_data(repository_pk, request, job_result_pk):
135
- """
136
- Worker function to clone and/or pull a Git repository into Nautobot, then invoke refresh_datasource_content().
137
- """
138
- job_result, repository_record = get_job_result_and_repository_record(
139
- repository_pk=repository_pk,
140
- job_result_pk=job_result_pk,
141
- logger=logger,
142
- )
143
-
144
- if not repository_record:
145
- return
146
-
147
- job_result.log(f'Creating/refreshing local copy of Git repository "{repository_record.name}"...', logger=logger)
148
- job_result.set_status(JobResultStatusChoices.STATUS_STARTED)
149
- job_result.save()
150
-
151
- try:
152
- if not os.path.exists(settings.GIT_ROOT):
153
- os.makedirs(settings.GIT_ROOT)
154
-
155
- ensure_git_repository(
156
- repository_record,
157
- job_result=job_result,
158
- logger=logger,
159
- )
160
-
161
- job_result.log(
162
- f'The current Git repository hash is "{repository_record.current_head}"',
163
- level_choice=LogLevelChoices.LOG_INFO,
164
- logger=logger,
165
- )
166
-
167
- refresh_datasource_content("extras.gitrepository", repository_record, request, job_result, delete=False)
168
-
169
- except Exception as exc:
170
- job_result.log(
171
- f"Error while refreshing {repository_record.name}: {exc}",
172
- level_choice=LogLevelChoices.LOG_FAILURE,
173
- )
174
- job_result.set_status(JobResultStatusChoices.STATUS_FAILURE)
175
-
176
- finally:
177
- log_job_result_final_status(job_result, "synchronization")
178
-
179
-
180
- # TODO(jathan): The state transition stuff here should be deleted in exchange
181
- # for trusting the database backend.
182
- @nautobot_task
183
- def git_repository_diff_origin_and_local(repository_pk, request, job_result_pk, **kwargs):
184
- """
185
- Worker function to run a dry run on a Git repository.
186
- """
187
- job_result, repository_record = get_job_result_and_repository_record(
188
- repository_pk,
189
- job_result_pk,
190
- logger=logger,
191
- )
192
- if not repository_record:
193
- return
194
-
195
- job_result.log(f'Running a Dry Run on Git repository "{repository_record.name}"...', logger=logger)
196
- job_result.set_status(JobResultStatusChoices.STATUS_STARTED)
197
- job_result.save()
198
- try:
199
- if not os.path.exists(settings.GIT_ROOT):
200
- os.makedirs(settings.GIT_ROOT)
201
-
202
- git_repository_dry_run(repository_record, job_result=job_result, logger=logger)
203
-
204
- except Exception as exc:
205
- job_result.log(
206
- f"Error while running a dry run on {repository_record.name}: {exc}",
207
- level_choice=LogLevelChoices.LOG_FAILURE,
208
- )
209
- job_result.set_status(JobResultStatusChoices.STATUS_FAILURE)
210
-
211
- finally:
212
- log_job_result_final_status(job_result, "dry run")
213
-
214
-
215
102
  def get_repo_from_url_to_path_and_from_branch(repository_record):
216
103
  """Returns the from_url, to_path and from_branch of a Git Repo
217
104
  Returns:
@@ -266,43 +153,58 @@ def ensure_git_repository(
266
153
  repository_record, job_result=None, logger=None, head=None # pylint: disable=redefined-outer-name
267
154
  ):
268
155
  """Ensure that the given Git repo is present, up-to-date, and has the correct branch selected.
269
- Note that this function may be called independently of the `pull_git_repository_and_refresh_data` job,
156
+
157
+ Note that this function may be called independently of the `GitRepositorySync` job,
270
158
  such as to ensure that different Nautobot instances and/or worker instances all have a local copy of the same HEAD.
159
+
271
160
  Args:
272
161
  repository_record (GitRepository): Repository to ensure the state of.
273
162
  job_result (JobResult): Optional JobResult to store results into.
274
163
  logger (logging.Logger): Optional Logger to additionally log results to.
275
164
  head (str): Optional Git commit hash to check out instead of pulling branch latest.
165
+
166
+ Returns:
167
+ bool: Whether any change to the local repo actually occurred.
276
168
  """
277
169
 
278
170
  from_url, to_path, from_branch = get_repo_from_url_to_path_and_from_branch(repository_record)
279
171
 
280
172
  try:
281
173
  repo_helper = GitRepo(to_path, from_url)
282
- head = repo_helper.checkout(from_branch, head)
174
+ head, changed = repo_helper.checkout(from_branch, head)
283
175
  if repository_record.current_head != head:
284
176
  repository_record.current_head = head
285
- # Make sure we don't recursively trigger a new resync of the repository!
286
- repository_record.save(trigger_resync=False)
177
+ repository_record.save()
287
178
 
179
+ # FIXME(jathan): As a part of jobs overhaul, this error-handling should be removed since this
180
+ # should only ever be called in the context of a Git sync job. Also, all logging directly from a
181
+ # JobResult should also be replaced with just trusting the logger to do the correct thing (such
182
+ # as from the Job class).
288
183
  except Exception as exc:
289
184
  if job_result:
290
- job_result.set_status(JobResultStatusChoices.STATUS_FAILURE)
291
- job_result.log(str(exc), level_choice=LogLevelChoices.LOG_FAILURE, logger=logger)
292
- job_result.save()
185
+ job_result.log(str(exc), level_choice=LogLevelChoices.LOG_ERROR, logger=logger)
293
186
  elif logger:
294
187
  logger.error(str(exc))
295
188
  raise
296
189
 
297
190
  if job_result:
191
+ if changed:
192
+ job_result.log(
193
+ "Repository successfully refreshed",
194
+ level_choice=LogLevelChoices.LOG_INFO,
195
+ logger=logger,
196
+ )
298
197
  job_result.log(
299
- "Repository successfully refreshed",
300
- level_choice=LogLevelChoices.LOG_SUCCESS,
198
+ f'The current Git repository hash is "{repository_record.current_head}"',
199
+ level_choice=LogLevelChoices.LOG_INFO,
301
200
  logger=logger,
302
201
  )
303
- job_result.save()
304
202
  elif logger:
305
- logger.info("Repository successfully refreshed")
203
+ if changed:
204
+ logger.info("Repository successfully refreshed")
205
+ logger.info(f'The current Git repository hash is "{repository_record.current_head}"')
206
+
207
+ return changed
306
208
 
307
209
 
308
210
  def git_repository_dry_run(repository_record, job_result=None, logger=None): # pylint: disable=redefined-outer-name
@@ -328,15 +230,13 @@ def git_repository_dry_run(repository_record, job_result=None, logger=None): #
328
230
 
329
231
  except Exception as exc:
330
232
  if job_result:
331
- job_result.set_status(JobResultStatusChoices.STATUS_FAILURE)
332
- job_result.log(str(exc), level_choice=LogLevelChoices.LOG_FAILURE, logger=logger)
333
- job_result.save()
233
+ job_result.log(str(exc), level_choice=LogLevelChoices.LOG_ERROR, logger=logger)
334
234
  elif logger:
335
235
  logger.error(str(exc))
336
236
  raise
337
237
 
338
238
  if job_result:
339
- job_result.log("Repository dry run successful", level_choice=LogLevelChoices.LOG_SUCCESS, logger=logger)
239
+ job_result.log("Repository dry run successful", level_choice=LogLevelChoices.LOG_INFO, logger=logger)
340
240
  elif logger:
341
241
  logger.info("Repository dry run successful")
342
242
 
@@ -392,11 +292,10 @@ def update_git_config_contexts(repository_record, job_result):
392
292
  except Exception as exc:
393
293
  job_result.log(
394
294
  f"Error in loading config context data from `{file_name}`: {exc}",
395
- level_choice=LogLevelChoices.LOG_FAILURE,
295
+ level_choice=LogLevelChoices.LOG_ERROR,
396
296
  grouping="config contexts",
397
297
  logger=logger,
398
298
  )
399
- job_result.save()
400
299
 
401
300
  # Next, handle the "filter/slug directory structure case - files in <filter_type>/<slug>.(json|yaml)
402
301
  for filter_type in (
@@ -446,11 +345,10 @@ def update_git_config_contexts(repository_record, job_result):
446
345
  except Exception as exc:
447
346
  job_result.log(
448
347
  f"Error in loading config context data from `{file_name}`: {exc}",
449
- level_choice=LogLevelChoices.LOG_FAILURE,
348
+ level_choice=LogLevelChoices.LOG_ERROR,
450
349
  grouping="config contexts",
451
350
  logger=logger,
452
351
  )
453
- job_result.save()
454
352
 
455
353
  # Finally, handle device- and virtual-machine-specific "local" context in (devices|virtual_machines)/<name>.(json|yaml)
456
354
  for local_type in ("devices", "virtual_machines"):
@@ -490,11 +388,10 @@ def update_git_config_contexts(repository_record, job_result):
490
388
  except Exception as exc:
491
389
  job_result.log(
492
390
  f"Error in loading local config context from `{local_type}/{file_name}`: {exc}",
493
- level_choice=LogLevelChoices.LOG_FAILURE,
391
+ level_choice=LogLevelChoices.LOG_ERROR,
494
392
  grouping="local config contexts",
495
393
  logger=logger,
496
394
  )
497
- job_result.save()
498
395
 
499
396
  # Delete any prior contexts that are owned by this repository but were not created/updated above
500
397
  delete_git_config_contexts(
@@ -616,7 +513,7 @@ def import_config_context(context_data, repository_record, job_result, logger):
616
513
  job_result.log(
617
514
  f"ConfigContextSchema {context_metadata['config_context_schema']} does not exist.",
618
515
  obj=context_record,
619
- level_choice=LogLevelChoices.LOG_FAILURE,
516
+ level_choice=LogLevelChoices.LOG_ERROR,
620
517
  grouping="config contexts",
621
518
  logger=logger,
622
519
  )
@@ -650,7 +547,7 @@ def import_config_context(context_data, repository_record, job_result, logger):
650
547
  job_result.log(
651
548
  "Successfully created config context",
652
549
  obj=context_record,
653
- level_choice=LogLevelChoices.LOG_SUCCESS,
550
+ level_choice=LogLevelChoices.LOG_INFO,
654
551
  grouping="config contexts",
655
552
  logger=logger,
656
553
  )
@@ -658,7 +555,7 @@ def import_config_context(context_data, repository_record, job_result, logger):
658
555
  job_result.log(
659
556
  "Successfully refreshed config context",
660
557
  obj=context_record,
661
- level_choice=LogLevelChoices.LOG_SUCCESS,
558
+ level_choice=LogLevelChoices.LOG_INFO,
662
559
  grouping="config contexts",
663
560
  logger=logger,
664
561
  )
@@ -699,7 +596,7 @@ def import_local_config_context(
699
596
  job_result.log(
700
597
  f"DATA CONFLICT: Local context data is owned by another owner, {record.local_config_context_data_owner}",
701
598
  obj=record,
702
- level_choice=LogLevelChoices.LOG_FAILURE,
599
+ level_choice=LogLevelChoices.LOG_ERROR,
703
600
  grouping="local config contexts",
704
601
  logger=logger,
705
602
  )
@@ -722,7 +619,7 @@ def import_local_config_context(
722
619
  job_result.log(
723
620
  "Successfully updated local config context",
724
621
  obj=record,
725
- level_choice=LogLevelChoices.LOG_SUCCESS,
622
+ level_choice=LogLevelChoices.LOG_INFO,
726
623
  grouping="local config contexts",
727
624
  logger=logger,
728
625
  )
@@ -821,11 +718,10 @@ def update_git_config_context_schemas(repository_record, job_result):
821
718
  except Exception as exc:
822
719
  job_result.log(
823
720
  f"Error in loading config context schema data from `{file_name}`: {exc}",
824
- level_choice=LogLevelChoices.LOG_FAILURE,
721
+ level_choice=LogLevelChoices.LOG_ERROR,
825
722
  grouping="config context schemas",
826
723
  logger=logger,
827
724
  )
828
- job_result.save()
829
725
 
830
726
  # Delete any prior contexts that are owned by this repository but were not created/updated above
831
727
  delete_git_config_context_schemas(
@@ -880,7 +776,7 @@ def import_config_context_schema(
880
776
  job_result.log(
881
777
  "Successfully created config context schema",
882
778
  obj=schema_record,
883
- level_choice=LogLevelChoices.LOG_SUCCESS,
779
+ level_choice=LogLevelChoices.LOG_INFO,
884
780
  grouping="config context schemas",
885
781
  logger=logger,
886
782
  )
@@ -889,7 +785,7 @@ def import_config_context_schema(
889
785
  job_result.log(
890
786
  "Successfully refreshed config context schema",
891
787
  obj=schema_record,
892
- level_choice=LogLevelChoices.LOG_SUCCESS,
788
+ level_choice=LogLevelChoices.LOG_INFO,
893
789
  grouping="config context schemas",
894
790
  logger=logger,
895
791
  )
@@ -927,34 +823,77 @@ def delete_git_config_context_schemas(repository_record, job_result, preserve=()
927
823
  #
928
824
 
929
825
 
826
+ def refresh_code_from_repository(repository_slug, consumer=None, skip_reimport=False):
827
+ """
828
+ After cloning/updating a GitRepository on disk, call this function to reload and reregister the repo's Python code.
829
+
830
+ Args:
831
+ repository_slug (str): Repository directory in GIT_ROOT that was refreshed.
832
+ consumer (celery.worker.Consumer): Celery Consumer to update as well
833
+ skip_reimport (bool): If True, unload existing code from this repository but do not re-import it.
834
+ """
835
+ if settings.GIT_ROOT not in sys.path:
836
+ sys.path.append(settings.GIT_ROOT)
837
+
838
+ app = consumer.app if consumer is not None else celery_app
839
+ # TODO: This is ugly, but when app.use_fast_trace_task is set (true by default), Celery calls
840
+ # celery.app.trace.fast_trace_task(...) which assumes that all tasks are cached and have a valid `__trace__()`
841
+ # function defined. In theory consumer.update_strategies() (below) should ensure this, but it doesn't
842
+ # go far enough (possibly a discrepancy between the main worker process and the prefork executors?)
843
+ # as we can and do still encounter errors where `task.__trace__` is unexpectedly None.
844
+ # For now, simply disabling use_fast_trace_task forces the task trace function to be rebuilt each time,
845
+ # which avoids the issue at the cost of very slight overhead.
846
+ app.use_fast_trace_task = False
847
+
848
+ # Unload any previous version of this module and its submodules if present
849
+ for module_name in list(sys.modules):
850
+ if module_name == repository_slug or module_name.startswith(f"{repository_slug}."):
851
+ logger.debug("Unloading module %s", module_name)
852
+ if module_name in app.loader.task_modules:
853
+ app.loader.task_modules.remove(module_name)
854
+ del sys.modules[module_name]
855
+
856
+ # Unregister any previous Celery tasks from this module
857
+ for task_name in list(app.tasks):
858
+ if task_name.startswith(f"{repository_slug}."):
859
+ logger.debug("Unregistering Celery task %s", task_name)
860
+ app.tasks.unregister(task_name)
861
+ if consumer is not None:
862
+ del consumer.strategies[task_name]
863
+
864
+ if not skip_reimport:
865
+ try:
866
+ repository = GitRepository.objects.get(slug=repository_slug)
867
+ if "extras.job" in repository.provided_contents:
868
+ # Re-import Celery tasks from this module
869
+ logger.debug("Importing Jobs from %s.jobs in GIT_ROOT", repository_slug)
870
+ app.loader.import_task_module(f"{repository_slug}.jobs")
871
+ if consumer is not None:
872
+ consumer.update_strategies()
873
+ except GitRepository.DoesNotExist as exc:
874
+ logger.error("Unable to reload Jobs from %s.jobs: %s", repository_slug, exc)
875
+ raise
876
+
877
+
930
878
  def refresh_git_jobs(repository_record, job_result, delete=False):
931
879
  """Callback function for GitRepository updates - refresh all Job records managed by this repository."""
932
880
  installed_jobs = []
933
881
  if "extras.job" in repository_record.provided_contents and not delete:
934
- jobs_path = os.path.join(repository_record.filesystem_path, "jobs")
935
- if os.path.isdir(jobs_path):
936
- for job_info in jobs_in_directory(jobs_path, report_errors=True):
937
- if job_info.error is not None:
938
- job_result.log(
939
- message=f"Error in loading Jobs from `{job_info.module_name}`: `{job_info.error}`",
940
- grouping="jobs",
941
- level_choice=LogLevelChoices.LOG_FAILURE,
942
- logger=logger,
943
- )
944
- continue
882
+ found_jobs = False
883
+ try:
884
+ refresh_code_from_repository(repository_record.slug)
945
885
 
946
- job_model, created = refresh_job_model_from_job_class(
947
- Job,
948
- JobSourceChoices.SOURCE_GIT,
949
- job_info.job_class,
950
- git_repository=repository_record,
951
- )
886
+ for task_name, task in celery_app.tasks.items():
887
+ if not task_name.startswith(f"{repository_record.slug}."):
888
+ continue
889
+ found_jobs = True
890
+ job_model, created = refresh_job_model_from_job_class(Job, task.__class__)
952
891
 
953
892
  if job_model is None:
954
893
  job_result.log(
955
894
  message="Failed to create Job record; check Nautobot logs for details",
956
895
  grouping="jobs",
957
- level_choice=LogLevelChoices.LOG_FAILURE,
896
+ level_choice=LogLevelChoices.LOG_ERROR,
958
897
  logger=logger,
959
898
  )
960
899
  continue
@@ -967,19 +906,30 @@ def refresh_git_jobs(repository_record, job_result, delete=False):
967
906
  message=message,
968
907
  obj=job_model,
969
908
  grouping="jobs",
970
- level_choice=LogLevelChoices.LOG_SUCCESS,
909
+ level_choice=LogLevelChoices.LOG_INFO,
971
910
  logger=logger,
972
911
  )
973
912
  installed_jobs.append(job_model)
974
- else:
913
+
914
+ if not found_jobs:
915
+ job_result.log(
916
+ "No jobs were registered on loading the `jobs` submodule. Did you miss a `register_jobs()` call?",
917
+ grouping="jobs",
918
+ level_choice=LogLevelChoices.LOG_WARNING,
919
+ logger=logger,
920
+ )
921
+ except Exception as exc:
975
922
  job_result.log(
976
- "No `jobs` subdirectory found in Git repository",
923
+ f"Error in loading Jobs from Git repository: {exc}",
977
924
  grouping="jobs",
978
- level_choice=LogLevelChoices.LOG_WARNING,
925
+ level_choice=LogLevelChoices.LOG_ERROR,
979
926
  logger=logger,
980
927
  )
928
+ else:
929
+ # Unload code from this repository, do not reimport it
930
+ refresh_code_from_repository(repository_record.slug, skip_reimport=True)
981
931
 
982
- for job_model in Job.objects.filter(source=JobSourceChoices.SOURCE_GIT, git_repository=repository_record):
932
+ for job_model in Job.objects.filter(module_name__startswith=f"{repository_record.slug}."):
983
933
  if job_model.installed and job_model not in installed_jobs:
984
934
  job_result.log(
985
935
  message="Marking Job record as no longer installed",
@@ -1093,7 +1043,7 @@ def update_git_export_templates(repository_record, job_result):
1093
1043
  job_result.log(
1094
1044
  "Successfully created export template",
1095
1045
  obj=template_record,
1096
- level_choice=LogLevelChoices.LOG_SUCCESS,
1046
+ level_choice=LogLevelChoices.LOG_INFO,
1097
1047
  grouping="export templates",
1098
1048
  logger=logger,
1099
1049
  )
@@ -1101,7 +1051,7 @@ def update_git_export_templates(repository_record, job_result):
1101
1051
  job_result.log(
1102
1052
  "Successfully refreshed export template",
1103
1053
  obj=template_record,
1104
- level_choice=LogLevelChoices.LOG_SUCCESS,
1054
+ level_choice=LogLevelChoices.LOG_INFO,
1105
1055
  grouping="export templates",
1106
1056
  logger=logger,
1107
1057
  )
@@ -1118,11 +1068,10 @@ def update_git_export_templates(repository_record, job_result):
1118
1068
  job_result.log(
1119
1069
  str(exc),
1120
1070
  obj=template_record,
1121
- level_choice=LogLevelChoices.LOG_FAILURE,
1071
+ level_choice=LogLevelChoices.LOG_ERROR,
1122
1072
  grouping="export templates",
1123
1073
  logger=logger,
1124
1074
  )
1125
- job_result.save()
1126
1075
 
1127
1076
  # Delete any prior templates that are owned by this repository but were not discovered above
1128
1077
  delete_git_export_templates(repository_record, job_result, preserve=managed_export_templates)
@@ -1,7 +1,6 @@
1
1
  """Registry-related APIs for datasources."""
2
2
 
3
- from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
4
- from nautobot.extras.context_managers import change_logging, JobChangeContext
3
+ from nautobot.extras.choices import LogLevelChoices
5
4
  from nautobot.extras.registry import registry
6
5
 
7
6
 
@@ -19,7 +18,7 @@ def get_datasource_content_choices(model_name):
19
18
  )
20
19
 
21
20
 
22
- def refresh_datasource_content(model_name, record, request, job_result, delete=False):
21
+ def refresh_datasource_content(model_name, record, user, job_result, delete=False):
23
22
  """Invoke the refresh callbacks for every content type registered for this model.
24
23
 
25
24
  Note that these callback functions are invoked regardless of whether a given model instance actually is flagged
@@ -29,39 +28,30 @@ def refresh_datasource_content(model_name, record, request, job_result, delete=F
29
28
  Args:
30
29
  model_name (str): Identifier of the datasource owner, such as "extras.gitrepository"
31
30
  record (models.Model): Datasource model instance, such as a GitRepository record
32
- request (HttpRequest): Initiating request for this refresh, optional, used for change logging if provided
31
+ user (User): Initiating user for this refresh, optional, used for change logging if provided
33
32
  job_result (JobResult): Passed through to the callback functions to use with logging their actions.
34
33
  delete (bool): True if the record is being deleted; False if it is being created/updated.
35
34
  """
36
35
  job_result.log(f"Refreshing data provided by {record}...", level_choice=LogLevelChoices.LOG_INFO)
37
- job_result.save()
38
- if request:
39
- change_context = JobChangeContext(user=request.user)
40
- # TODO(jathan): Review how we can eliminate these deliberate `set_status()` and `save()`
41
- # calls,moving them to Celery-friendly primitives instead.
42
- with change_logging(change_context):
43
- for entry in get_datasource_contents(model_name):
44
- job_result.log(f"Refreshing {entry.name}...", level_choice=LogLevelChoices.LOG_INFO)
45
- try:
46
- entry.callback(record, job_result, delete=delete)
47
- except Exception as exc:
48
- job_result.log(
49
- f"Error while refreshing {entry.name}: {exc}", level_choice=LogLevelChoices.LOG_FAILURE
50
- )
51
- job_result.set_status(JobResultStatusChoices.STATUS_FAILURE)
52
- job_result.save()
53
- job_result.log(f"Data refresh from {record} complete!", level_choice=LogLevelChoices.LOG_INFO)
54
- job_result.save()
55
- else:
56
- # TODO(jathan): Review how we can eliminate these deliberate `set_status()` and `save()`
57
- # calls,moving them to Celery-friendly primitives instead.
58
- for entry in get_datasource_contents(model_name):
59
- job_result.log(f"Refreshing {entry.name}...", level_choice=LogLevelChoices.LOG_INFO)
60
- try:
61
- entry.callback(record, job_result, delete=delete)
62
- except Exception as exc:
63
- job_result.log(f"Error while refreshing {entry.name}: {exc}", level_choice=LogLevelChoices.LOG_FAILURE)
64
- job_result.set_status(JobResultStatusChoices.STATUS_FAILURE)
65
- job_result.save()
66
- job_result.log(f"Data refresh from {record} complete!", level_choice=LogLevelChoices.LOG_INFO)
67
- job_result.save()
36
+
37
+ for entry in get_datasource_contents(model_name):
38
+ job_result.log(f"Refreshing {entry.name}...", level_choice=LogLevelChoices.LOG_INFO)
39
+ try:
40
+ entry.callback(record, job_result, delete=delete)
41
+ except Exception as exc:
42
+ job_result.log(f"Error while refreshing {entry.name}: {exc}", level_choice=LogLevelChoices.LOG_ERROR)
43
+ raise
44
+
45
+ # Now that any exception will fail a Job and Git Repository syncs are jobs,
46
+ # and we also cannot micro-manage the JobResult state, we had to add a final
47
+ # check here to ensure that any failed log events by the various content
48
+ # callbacks will result in this task "failing successfully" by raising an
49
+ # exception.
50
+ failure_logs = job_result.job_log_entries.filter(log_level=LogLevelChoices.LOG_ERROR)
51
+ if failure_logs.exists():
52
+ msg = f"Failed to refresh data provided by {record}. Please see logs."
53
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR)
54
+ raise RuntimeError(msg)
55
+
56
+ # Otherwise, log a friendly info message.
57
+ job_result.log(f"Data refresh from {record} complete!", level_choice=LogLevelChoices.LOG_INFO)