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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (780) hide show
  1. nautobot/apps/api.py +6 -8
  2. nautobot/apps/forms.py +0 -2
  3. nautobot/apps/ui.py +0 -8
  4. nautobot/circuits/api/serializers.py +9 -117
  5. nautobot/circuits/api/urls.py +1 -1
  6. nautobot/circuits/api/views.py +0 -1
  7. nautobot/circuits/forms.py +0 -65
  8. nautobot/circuits/migrations/0014_related_name_changes.py +1 -1
  9. nautobot/circuits/migrations/0016_tagsfield.py +34 -0
  10. nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
  11. nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
  12. nautobot/circuits/models.py +3 -87
  13. nautobot/circuits/navigation.py +14 -69
  14. nautobot/circuits/signals.py +0 -2
  15. nautobot/circuits/tables.py +39 -1
  16. nautobot/circuits/tests/integration/test_relationships.py +9 -9
  17. nautobot/circuits/tests/test_api.py +4 -8
  18. nautobot/circuits/tests/test_filters.py +10 -4
  19. nautobot/circuits/tests/test_models.py +5 -1
  20. nautobot/circuits/tests/test_views.py +27 -5
  21. nautobot/circuits/views.py +18 -10
  22. nautobot/core/api/__init__.py +8 -2
  23. nautobot/core/api/fields.py +15 -6
  24. nautobot/core/api/filter_backends.py +3 -2
  25. nautobot/core/api/metadata.py +237 -30
  26. nautobot/core/api/mixins.py +94 -0
  27. nautobot/core/api/pagination.py +4 -0
  28. nautobot/core/api/parsers.py +154 -0
  29. nautobot/core/api/renderers.py +153 -2
  30. nautobot/core/api/schema.py +46 -2
  31. nautobot/core/api/serializers.py +377 -35
  32. nautobot/core/api/urls.py +11 -3
  33. nautobot/core/api/utils.py +174 -2
  34. nautobot/core/api/versioning.py +32 -10
  35. nautobot/core/api/views.py +266 -72
  36. nautobot/core/apps/__init__.py +138 -220
  37. nautobot/core/celery/__init__.py +112 -41
  38. nautobot/core/celery/backends.py +19 -12
  39. nautobot/core/celery/control.py +46 -0
  40. nautobot/core/celery/encoders.py +53 -0
  41. nautobot/core/celery/log.py +38 -0
  42. nautobot/core/celery/schedulers.py +23 -4
  43. nautobot/core/celery/task.py +1 -16
  44. nautobot/core/checks.py +0 -27
  45. nautobot/core/choices.py +0 -113
  46. nautobot/core/{cli.py → cli/__init__.py} +1 -1
  47. nautobot/core/cli/__main__.py +3 -0
  48. nautobot/core/constants.py +0 -24
  49. nautobot/core/context_processors.py +12 -0
  50. nautobot/core/filters.py +2 -2
  51. nautobot/core/forms/__init__.py +0 -4
  52. nautobot/core/forms/fields.py +38 -65
  53. nautobot/core/forms/forms.py +4 -1
  54. nautobot/core/forms/utils.py +0 -52
  55. nautobot/core/graphql/schema.py +4 -27
  56. nautobot/core/jobs/__init__.py +75 -0
  57. nautobot/core/management/commands/build_ui.py +255 -0
  58. nautobot/core/management/commands/generate_test_data.py +3 -2
  59. nautobot/core/management/commands/post_upgrade.py +24 -24
  60. nautobot/core/models/__init__.py +26 -1
  61. nautobot/core/models/fields.py +24 -5
  62. nautobot/core/models/generics.py +2 -42
  63. nautobot/core/models/managers.py +5 -0
  64. nautobot/core/models/name_color_content_types.py +0 -14
  65. nautobot/core/models/tree_queries.py +14 -4
  66. nautobot/core/models/utils.py +5 -6
  67. nautobot/core/models/validators.py +17 -8
  68. nautobot/core/releases.py +8 -10
  69. nautobot/core/settings.py +80 -42
  70. nautobot/core/tables.py +5 -5
  71. nautobot/core/tasks.py +4 -7
  72. nautobot/core/templates/base.html +1 -49
  73. nautobot/core/templates/base_django.html +49 -0
  74. nautobot/core/templates/base_react.html +55 -0
  75. nautobot/core/templates/buttons/export.html +6 -4
  76. nautobot/core/templates/generic/object_bulk_create.html +10 -21
  77. nautobot/core/templates/generic/object_list.html +3 -1
  78. nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
  79. nautobot/core/templates/inc/footer.html +1 -0
  80. nautobot/core/templates/inc/javascript.html +0 -14
  81. nautobot/core/templates/inc/nav_menu.html +28 -33
  82. nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
  83. nautobot/core/templates/inc/relationships_table_rows.html +2 -2
  84. nautobot/core/templates/nautobot_config.py.j2 +8 -20
  85. nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
  86. nautobot/core/templates/rest_framework/api.html +8 -0
  87. nautobot/core/templatetags/buttons.py +32 -28
  88. nautobot/core/testing/__init__.py +47 -44
  89. nautobot/core/testing/api.py +362 -47
  90. nautobot/core/testing/filters.py +1 -1
  91. nautobot/core/testing/migrations.py +2 -0
  92. nautobot/core/testing/mixins.py +22 -9
  93. nautobot/core/testing/schema.py +2 -1
  94. nautobot/core/testing/views.py +21 -46
  95. nautobot/core/tests/integration/test_filters.py +17 -8
  96. nautobot/core/tests/integration/test_navbar.py +11 -34
  97. nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
  98. nautobot/core/tests/nautobot_config.py +2 -3
  99. nautobot/core/tests/test_api.py +290 -21
  100. nautobot/core/tests/test_checks.py +0 -7
  101. nautobot/core/tests/test_filters.py +107 -59
  102. nautobot/core/tests/test_forms.py +26 -92
  103. nautobot/core/tests/test_graphql.py +110 -77
  104. nautobot/core/tests/test_logging.py +4 -0
  105. nautobot/core/tests/test_managers.py +3 -1
  106. nautobot/core/tests/test_models.py +2 -0
  107. nautobot/core/tests/test_paginator.py +3 -1
  108. nautobot/core/tests/test_releases.py +12 -12
  109. nautobot/core/tests/test_templatetags_helpers.py +4 -4
  110. nautobot/core/tests/test_utils.py +32 -68
  111. nautobot/core/tests/test_views.py +12 -15
  112. nautobot/core/utils/data.py +17 -0
  113. nautobot/core/utils/deprecation.py +9 -6
  114. nautobot/core/utils/filtering.py +8 -3
  115. nautobot/core/utils/git.py +12 -4
  116. nautobot/core/utils/lookup.py +3 -1
  117. nautobot/core/utils/requests.py +1 -104
  118. nautobot/core/views/__init__.py +1 -0
  119. nautobot/core/views/generic.py +75 -110
  120. nautobot/core/views/mixins.py +52 -61
  121. nautobot/core/views/renderers.py +6 -7
  122. nautobot/core/views/utils.py +80 -0
  123. nautobot/dcim/api/serializers.py +160 -667
  124. nautobot/dcim/api/urls.py +1 -1
  125. nautobot/dcim/api/views.py +7 -44
  126. nautobot/dcim/choices.py +2 -0
  127. nautobot/dcim/filters/__init__.py +21 -0
  128. nautobot/dcim/form_mixins.py +1 -27
  129. nautobot/dcim/forms.py +19 -765
  130. nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -1
  131. nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -13
  132. nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -1
  133. nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -1
  134. nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
  135. nautobot/dcim/migrations/0035_related_name_changes.py +1 -1
  136. nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -1
  137. nautobot/dcim/migrations/0040_tagsfield.py +109 -0
  138. nautobot/dcim/migrations/{0040_ipam__namespaces.py → 0041_ipam__namespaces.py} +1 -1
  139. nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
  140. nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
  141. nautobot/dcim/models/cables.py +3 -33
  142. nautobot/dcim/models/device_component_templates.py +6 -0
  143. nautobot/dcim/models/device_components.py +12 -198
  144. nautobot/dcim/models/devices.py +30 -143
  145. nautobot/dcim/models/locations.py +3 -64
  146. nautobot/dcim/models/power.py +3 -50
  147. nautobot/dcim/models/racks.py +7 -84
  148. nautobot/dcim/navigation.py +141 -467
  149. nautobot/dcim/signals.py +0 -2
  150. nautobot/dcim/tables/locations.py +2 -2
  151. nautobot/dcim/tables/power.py +1 -2
  152. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
  153. nautobot/dcim/templates/dcim/devicetype.html +2 -2
  154. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
  155. nautobot/dcim/templates/dcim/location.html +16 -1
  156. nautobot/dcim/templates/dcim/locationtype.html +15 -0
  157. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
  158. nautobot/dcim/templates/dcim/rackgroup.html +0 -12
  159. nautobot/dcim/tests/test_api.py +166 -81
  160. nautobot/dcim/tests/test_cablepaths.py +41 -35
  161. nautobot/dcim/tests/test_filters.py +67 -23
  162. nautobot/dcim/tests/test_forms.py +5 -205
  163. nautobot/dcim/tests/test_graphql.py +7 -2
  164. nautobot/dcim/tests/test_migrations.py +6 -11
  165. nautobot/dcim/tests/test_models.py +182 -110
  166. nautobot/dcim/tests/test_natural_ordering.py +11 -8
  167. nautobot/dcim/tests/test_signals.py +6 -3
  168. nautobot/dcim/tests/test_views.py +197 -175
  169. nautobot/dcim/urls.py +11 -16
  170. nautobot/dcim/views.py +7 -134
  171. nautobot/docs/additional-features/caching.md +6 -87
  172. nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
  173. nautobot/docs/additional-features/jobs.md +177 -195
  174. nautobot/docs/administration/nautobot-server.md +6 -21
  175. nautobot/docs/administration/replicating-nautobot.md +0 -10
  176. nautobot/docs/configuration/optional-settings.md +32 -41
  177. nautobot/docs/configuration/required-settings.md +11 -52
  178. nautobot/docs/development/application-registry.md +2 -13
  179. nautobot/docs/development/extending-models.md +15 -17
  180. nautobot/docs/development/generic-views.md +0 -2
  181. nautobot/docs/development/getting-started.md +55 -5
  182. nautobot/docs/development/navigation-menu.md +22 -93
  183. nautobot/docs/development/react-ui.md +105 -0
  184. nautobot/docs/development/role-internals.md +1 -3
  185. nautobot/docs/development/style-guide.md +6 -4
  186. nautobot/docs/index.md +3 -2
  187. nautobot/docs/installation/migrating-from-netbox.md +11 -42
  188. nautobot/docs/installation/nautobot.md +1 -1
  189. nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  190. nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  191. nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  192. nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  193. nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
  194. nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  195. nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  196. nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  197. nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  198. nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  199. nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  200. nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  201. nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  202. nautobot/docs/installation/upgrading-from-nautobot-v1.md +170 -747
  203. nautobot/docs/models/dcim/device.md +3 -0
  204. nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
  205. nautobot/docs/models/extras/computedfield.md +4 -4
  206. nautobot/docs/models/extras/gitrepository.md +3 -0
  207. nautobot/docs/models/extras/job.md +1 -0
  208. nautobot/docs/models/extras/jobbutton.md +18 -13
  209. nautobot/docs/models/extras/jobhook.md +7 -4
  210. nautobot/docs/models/extras/jobresult.md +6 -2
  211. nautobot/docs/models/extras/relationship.md +2 -2
  212. nautobot/docs/models/extras/status.md +6 -19
  213. nautobot/docs/models/ipam/ipaddress.md +3 -0
  214. nautobot/docs/models/virtualization/virtualmachine.md +3 -0
  215. nautobot/docs/plugins/development.md +83 -21
  216. nautobot/docs/release-notes/version-1.5.md +53 -0
  217. nautobot/docs/release-notes/version-2.0.md +180 -0
  218. nautobot/docs/requirements.txt +1 -0
  219. nautobot/docs/rest-api/overview.md +384 -215
  220. nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
  221. nautobot/extras/admin.py +3 -5
  222. nautobot/extras/api/customfields.py +15 -39
  223. nautobot/extras/api/fields.py +0 -11
  224. nautobot/extras/api/mixins.py +45 -0
  225. nautobot/extras/api/relationships.py +63 -158
  226. nautobot/extras/api/serializers.py +165 -700
  227. nautobot/extras/api/urls.py +1 -1
  228. nautobot/extras/api/views.py +294 -280
  229. nautobot/extras/apps.py +4 -7
  230. nautobot/extras/choices.py +11 -9
  231. nautobot/extras/constants.py +9 -3
  232. nautobot/extras/datasources/__init__.py +2 -0
  233. nautobot/extras/datasources/git.py +135 -186
  234. nautobot/extras/datasources/registry.py +25 -35
  235. nautobot/extras/filters/__init__.py +20 -19
  236. nautobot/extras/filters/mixins.py +4 -4
  237. nautobot/extras/forms/forms.py +63 -127
  238. nautobot/extras/forms/mixins.py +23 -51
  239. nautobot/extras/health_checks.py +0 -33
  240. nautobot/extras/jobs.py +387 -565
  241. nautobot/extras/management/commands/runjob.py +24 -62
  242. nautobot/extras/managers.py +30 -7
  243. nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
  244. nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -1
  245. nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -1
  246. nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -7
  247. nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -32
  248. nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -1
  249. nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -1
  250. nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
  251. nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -1
  252. nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -2
  253. nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -1
  254. nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -1
  255. nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
  256. nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -1
  257. nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
  258. nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -9
  259. nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -1
  260. nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
  261. nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
  262. nautobot/extras/migrations/{0076_remove_name_field_and_make_label_field_non_nullable.py → 0077_remove_name_field_and_make_label_field_non_nullable.py} +1 -1
  263. nautobot/extras/migrations/{0077_remove_slug.py → 0078_remove_slug.py} +1 -5
  264. nautobot/extras/migrations/0079_tagsfield.py +28 -0
  265. nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
  266. nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
  267. nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
  268. nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
  269. nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
  270. nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
  271. nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
  272. nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
  273. nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
  274. nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
  275. nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
  276. nautobot/extras/models/__init__.py +2 -3
  277. nautobot/extras/models/change_logging.py +0 -36
  278. nautobot/extras/models/customfields.py +39 -33
  279. nautobot/extras/models/datasources.py +48 -50
  280. nautobot/extras/models/groups.py +5 -6
  281. nautobot/extras/models/jobs.py +189 -321
  282. nautobot/extras/models/mixins.py +0 -71
  283. nautobot/extras/models/models.py +0 -19
  284. nautobot/extras/models/relationships.py +19 -13
  285. nautobot/extras/models/roles.py +0 -34
  286. nautobot/extras/models/secrets.py +2 -26
  287. nautobot/extras/models/statuses.py +6 -5
  288. nautobot/extras/models/tags.py +2 -17
  289. nautobot/extras/navigation.py +89 -307
  290. nautobot/extras/plugins/__init__.py +3 -120
  291. nautobot/extras/plugins/utils.py +0 -3
  292. nautobot/extras/plugins/validators.py +5 -4
  293. nautobot/extras/plugins/views.py +16 -3
  294. nautobot/extras/querysets.py +1 -7
  295. nautobot/extras/registry.py +3 -0
  296. nautobot/extras/signals.py +26 -60
  297. nautobot/extras/tables.py +34 -40
  298. nautobot/extras/tasks.py +0 -12
  299. nautobot/extras/templates/extras/configcontext.html +1 -1
  300. nautobot/extras/templates/extras/configcontextschema.html +16 -1
  301. nautobot/extras/templates/extras/customfield.html +0 -13
  302. nautobot/extras/templates/extras/gitrepository.html +3 -3
  303. nautobot/extras/templates/extras/inc/jobresult.html +10 -0
  304. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  305. nautobot/extras/templates/extras/job.html +35 -25
  306. nautobot/extras/templates/extras/job_approval_request.html +15 -30
  307. nautobot/extras/templates/extras/job_detail.html +13 -31
  308. nautobot/extras/templates/extras/job_edit.html +15 -17
  309. nautobot/extras/templates/extras/jobresult.html +24 -6
  310. nautobot/extras/templates/extras/scheduledjob.html +2 -2
  311. nautobot/extras/templates/extras/secret.html +28 -0
  312. nautobot/extras/templatetags/job_buttons.py +1 -0
  313. nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
  314. nautobot/extras/test_jobs/atomic_transaction.py +53 -0
  315. nautobot/extras/test_jobs/dry_run.py +29 -0
  316. nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
  317. nautobot/extras/test_jobs/duplicate_name2.py +9 -0
  318. nautobot/extras/test_jobs/fail.py +23 -0
  319. nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
  320. nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
  321. nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
  322. nautobot/extras/test_jobs/file_upload_pass.py +25 -0
  323. nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
  324. nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
  325. nautobot/extras/test_jobs/job_button_receiver.py +28 -0
  326. nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
  327. nautobot/extras/test_jobs/job_variables.py +88 -0
  328. nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
  329. nautobot/extras/test_jobs/log_redaction.py +20 -0
  330. nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
  331. nautobot/extras/test_jobs/modify_db.py +25 -0
  332. nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
  333. nautobot/extras/test_jobs/object_var_optional.py +21 -0
  334. nautobot/extras/test_jobs/object_var_required.py +21 -0
  335. nautobot/extras/test_jobs/object_vars.py +26 -0
  336. nautobot/extras/test_jobs/pass.py +25 -0
  337. nautobot/extras/test_jobs/profiling.py +32 -0
  338. nautobot/extras/test_jobs/read_only_job.py +15 -0
  339. nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
  340. nautobot/extras/{tests/example_jobs/test_soft_time_limit_greater_than_time_limit.py → test_jobs/soft_time_limit_greater_than_time_limit.py} +5 -1
  341. nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
  342. nautobot/extras/tests/integration/test_computedfields.py +1 -1
  343. nautobot/extras/tests/integration/test_configcontextschema.py +5 -3
  344. nautobot/extras/tests/integration/test_customfields.py +4 -2
  345. nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
  346. nautobot/extras/tests/integration/test_jobs.py +25 -27
  347. nautobot/extras/tests/integration/test_notes.py +8 -4
  348. nautobot/extras/tests/integration/test_relationships.py +2 -2
  349. nautobot/extras/tests/test_api.py +649 -642
  350. nautobot/extras/tests/test_changelog.py +3 -3
  351. nautobot/extras/tests/test_context_managers.py +5 -3
  352. nautobot/extras/tests/test_customfields.py +92 -50
  353. nautobot/extras/tests/test_datasources.py +189 -112
  354. nautobot/extras/tests/test_dynamicgroups.py +7 -8
  355. nautobot/extras/tests/test_filters.py +137 -89
  356. nautobot/extras/tests/test_forms.py +73 -75
  357. nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
  358. nautobot/extras/tests/test_jobs.py +262 -263
  359. nautobot/extras/tests/test_migrations.py +4 -3
  360. nautobot/extras/tests/test_models.py +116 -161
  361. nautobot/extras/tests/test_plugins.py +38 -60
  362. nautobot/extras/tests/test_relationships.py +167 -120
  363. nautobot/extras/tests/test_tags.py +6 -11
  364. nautobot/extras/tests/test_utils.py +31 -1
  365. nautobot/extras/tests/test_views.py +201 -145
  366. nautobot/extras/tests/test_webhooks.py +6 -2
  367. nautobot/extras/urls.py +42 -42
  368. nautobot/extras/utils.py +137 -163
  369. nautobot/extras/views.py +78 -152
  370. nautobot/ipam/api/fields.py +17 -0
  371. nautobot/ipam/api/serializers.py +58 -164
  372. nautobot/ipam/api/urls.py +1 -1
  373. nautobot/ipam/api/views.py +3 -2
  374. nautobot/ipam/apps.py +1 -2
  375. nautobot/ipam/filters.py +1 -10
  376. nautobot/ipam/forms.py +4 -177
  377. nautobot/ipam/lookups.py +1 -0
  378. nautobot/ipam/management/commands/__init__.py +0 -0
  379. nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
  380. nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -1
  381. nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -38
  382. nautobot/ipam/migrations/0020_related_name_changes.py +1 -1
  383. nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +2 -2
  384. nautobot/ipam/migrations/0028_tagsfield.py +44 -0
  385. nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
  386. nautobot/ipam/migrations/{0028_ipam__namespaces.py → 0030_ipam__namespaces.py} +77 -28
  387. nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
  388. nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
  389. nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
  390. nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
  391. nautobot/ipam/models.py +100 -236
  392. nautobot/ipam/navigation.py +36 -181
  393. nautobot/ipam/querysets.py +20 -25
  394. nautobot/ipam/signals.py +49 -6
  395. nautobot/ipam/tables.py +10 -3
  396. nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
  397. nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
  398. nautobot/ipam/templates/ipam/namespace_retrieve.html +17 -4
  399. nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
  400. nautobot/ipam/templates/ipam/prefix.html +1 -1
  401. nautobot/ipam/templates/ipam/vlangroup.html +0 -13
  402. nautobot/ipam/templates/ipam/vrf_edit.html +6 -0
  403. nautobot/ipam/tests/integration/test_prefixes.py +3 -26
  404. nautobot/ipam/tests/test_api.py +22 -19
  405. nautobot/ipam/tests/test_filters.py +59 -23
  406. nautobot/ipam/tests/test_migrations.py +6 -10
  407. nautobot/ipam/tests/test_models.py +323 -198
  408. nautobot/ipam/tests/test_ordering.py +2 -2
  409. nautobot/ipam/tests/test_querysets.py +44 -24
  410. nautobot/ipam/tests/test_views.py +73 -26
  411. nautobot/ipam/urls.py +16 -0
  412. nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
  413. nautobot/ipam/utils/migrations.py +713 -0
  414. nautobot/ipam/views.py +137 -20
  415. nautobot/project-static/docs/404.html +1178 -10
  416. nautobot/project-static/docs/additional-features/caching.html +1224 -159
  417. nautobot/project-static/docs/additional-features/change-logging.html +1180 -12
  418. nautobot/project-static/docs/additional-features/config-contexts.html +1180 -12
  419. nautobot/project-static/docs/additional-features/graphql.html +1179 -11
  420. nautobot/project-static/docs/additional-features/healthcheck.html +1180 -12
  421. nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1184 -12
  422. nautobot/project-static/docs/additional-features/jobs.html +1514 -328
  423. nautobot/project-static/docs/additional-features/napalm.html +1180 -12
  424. nautobot/project-static/docs/additional-features/prometheus-metrics.html +1180 -12
  425. nautobot/project-static/docs/additional-features/template-filters.html +1180 -12
  426. nautobot/project-static/docs/administration/celery-queues.html +1178 -10
  427. nautobot/project-static/docs/administration/nautobot-server.html +1451 -304
  428. nautobot/project-static/docs/administration/nautobot-shell.html +1178 -10
  429. nautobot/project-static/docs/administration/permissions.html +1178 -10
  430. nautobot/project-static/docs/administration/replicating-nautobot.html +1262 -113
  431. nautobot/project-static/docs/apps/index.html +1178 -10
  432. nautobot/project-static/docs/apps/nautobot-apps.html +1178 -10
  433. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1580 -426
  434. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1178 -10
  435. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3481 -1838
  436. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1178 -10
  437. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1178 -10
  438. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1185 -11
  439. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1719 -551
  440. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2062 -930
  441. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1946 -659
  442. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1180 -12
  443. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1189 -21
  444. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9283 -6218
  445. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2734 -2122
  446. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1178 -10
  447. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2337 -1300
  448. nautobot/project-static/docs/configuration/authentication/ldap.html +1178 -10
  449. nautobot/project-static/docs/configuration/authentication/remote.html +1178 -10
  450. nautobot/project-static/docs/configuration/authentication/sso.html +1178 -10
  451. nautobot/project-static/docs/configuration/index.html +1178 -10
  452. nautobot/project-static/docs/configuration/optional-settings.html +1311 -160
  453. nautobot/project-static/docs/configuration/required-settings.html +1312 -211
  454. nautobot/project-static/docs/core-functionality/circuits.html +1178 -10
  455. nautobot/project-static/docs/core-functionality/device-types.html +1178 -10
  456. nautobot/project-static/docs/core-functionality/devices.html +1182 -10
  457. nautobot/project-static/docs/core-functionality/ipam.html +1182 -10
  458. nautobot/project-static/docs/core-functionality/power.html +1178 -10
  459. nautobot/project-static/docs/core-functionality/secrets.html +1178 -10
  460. nautobot/project-static/docs/core-functionality/services.html +1178 -10
  461. nautobot/project-static/docs/core-functionality/sites-and-racks.html +1178 -10
  462. nautobot/project-static/docs/core-functionality/tenancy.html +1178 -10
  463. nautobot/project-static/docs/core-functionality/virtualization.html +1182 -10
  464. nautobot/project-static/docs/core-functionality/vlans.html +1179 -11
  465. nautobot/project-static/docs/development/application-registry.html +1190 -42
  466. nautobot/project-static/docs/development/best-practices.html +1178 -10
  467. nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1178 -10
  468. nautobot/project-static/docs/development/extending-models.html +1238 -83
  469. nautobot/project-static/docs/development/generic-views.html +1180 -14
  470. nautobot/project-static/docs/development/getting-started.html +1365 -90
  471. nautobot/project-static/docs/development/homepage.html +1178 -10
  472. nautobot/project-static/docs/development/index.html +1178 -10
  473. nautobot/project-static/docs/development/model-features.html +1178 -10
  474. nautobot/project-static/docs/development/natural-keys.html +1178 -10
  475. nautobot/project-static/docs/development/navigation-menu.html +1215 -125
  476. nautobot/project-static/docs/development/react-ui.html +4199 -0
  477. nautobot/project-static/docs/development/release-checklist.html +1178 -10
  478. nautobot/project-static/docs/development/role-internals.html +1179 -12
  479. nautobot/project-static/docs/development/style-guide.html +1188 -19
  480. nautobot/project-static/docs/development/templates.html +1178 -10
  481. nautobot/project-static/docs/development/testing.html +1178 -10
  482. nautobot/project-static/docs/development/user-preferences.html +1178 -10
  483. nautobot/project-static/docs/docker/index.html +1178 -10
  484. nautobot/project-static/docs/index.html +1183 -12
  485. nautobot/project-static/docs/installation/centos.html +1178 -10
  486. nautobot/project-static/docs/installation/external-authentication.html +1178 -10
  487. nautobot/project-static/docs/installation/http-server.html +1178 -10
  488. nautobot/project-static/docs/installation/index.html +1178 -10
  489. nautobot/project-static/docs/installation/migrating-from-netbox.html +1305 -189
  490. nautobot/project-static/docs/installation/migrating-from-postgresql.html +1178 -10
  491. nautobot/project-static/docs/installation/nautobot.html +1179 -11
  492. nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1178 -10
  493. nautobot/project-static/docs/installation/selinux-troubleshooting.html +1178 -10
  494. nautobot/project-static/docs/installation/services.html +1178 -10
  495. nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  496. nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  497. nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  498. nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  499. nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
  500. nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  501. nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  502. nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  503. nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  504. nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  505. nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  506. nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  507. nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  508. nautobot/project-static/docs/installation/ubuntu.html +1178 -10
  509. nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +3823 -2152
  510. nautobot/project-static/docs/installation/upgrading.html +1178 -10
  511. nautobot/project-static/docs/models/circuits/circuit.html +1293 -103
  512. nautobot/project-static/docs/models/circuits/circuittermination.html +1293 -103
  513. nautobot/project-static/docs/models/circuits/circuittype.html +1293 -103
  514. nautobot/project-static/docs/models/circuits/provider.html +1293 -103
  515. nautobot/project-static/docs/models/circuits/providernetwork.html +1293 -103
  516. nautobot/project-static/docs/models/dcim/cable.html +1324 -103
  517. nautobot/project-static/docs/models/dcim/consoleport.html +1293 -103
  518. nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1293 -103
  519. nautobot/project-static/docs/models/dcim/consoleserverport.html +1293 -103
  520. nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1293 -103
  521. nautobot/project-static/docs/models/dcim/device.html +1326 -132
  522. nautobot/project-static/docs/models/dcim/devicebay.html +1293 -103
  523. nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1293 -103
  524. nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1379 -97
  525. nautobot/project-static/docs/models/dcim/devicetype.html +1293 -103
  526. nautobot/project-static/docs/models/dcim/frontport.html +1293 -103
  527. nautobot/project-static/docs/models/dcim/frontporttemplate.html +1293 -103
  528. nautobot/project-static/docs/models/dcim/interface.html +1293 -103
  529. nautobot/project-static/docs/models/dcim/interfacetemplate.html +1293 -103
  530. nautobot/project-static/docs/models/dcim/inventoryitem.html +1293 -103
  531. nautobot/project-static/docs/models/dcim/location.html +1293 -103
  532. nautobot/project-static/docs/models/dcim/locationtype.html +1293 -103
  533. nautobot/project-static/docs/models/dcim/manufacturer.html +1292 -102
  534. nautobot/project-static/docs/models/dcim/platform.html +1272 -82
  535. nautobot/project-static/docs/models/dcim/powerfeed.html +1270 -80
  536. nautobot/project-static/docs/models/dcim/poweroutlet.html +1272 -82
  537. nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1272 -82
  538. nautobot/project-static/docs/models/dcim/powerpanel.html +1270 -80
  539. nautobot/project-static/docs/models/dcim/powerport.html +1272 -82
  540. nautobot/project-static/docs/models/dcim/powerporttemplate.html +1272 -82
  541. nautobot/project-static/docs/models/dcim/rack.html +1272 -82
  542. nautobot/project-static/docs/models/dcim/rackgroup.html +1272 -82
  543. nautobot/project-static/docs/models/dcim/rackreservation.html +1272 -82
  544. nautobot/project-static/docs/models/dcim/rearport.html +1286 -96
  545. nautobot/project-static/docs/models/dcim/rearporttemplate.html +1286 -96
  546. nautobot/project-static/docs/models/dcim/region.html +1178 -10
  547. nautobot/project-static/docs/models/dcim/site.html +1178 -10
  548. nautobot/project-static/docs/models/dcim/virtualchassis.html +1284 -94
  549. nautobot/project-static/docs/models/extras/computedfield.html +1184 -16
  550. nautobot/project-static/docs/models/extras/configcontext.html +1314 -86
  551. nautobot/project-static/docs/models/extras/configcontextschema.html +1276 -86
  552. nautobot/project-static/docs/models/extras/customfield.html +1180 -12
  553. nautobot/project-static/docs/models/extras/customlink.html +1180 -12
  554. nautobot/project-static/docs/models/extras/dynamicgroup.html +1180 -12
  555. nautobot/project-static/docs/models/extras/exporttemplate.html +1180 -12
  556. nautobot/project-static/docs/models/extras/gitrepository.html +1184 -12
  557. nautobot/project-static/docs/models/extras/graphqlquery.html +1321 -86
  558. nautobot/project-static/docs/models/extras/imageattachment.html +1276 -86
  559. nautobot/project-static/docs/models/extras/job.html +1277 -86
  560. nautobot/project-static/docs/models/extras/jobbutton.html +1201 -29
  561. nautobot/project-static/docs/models/extras/jobhook.html +1188 -16
  562. nautobot/project-static/docs/models/extras/joblogentry.html +1274 -84
  563. nautobot/project-static/docs/models/extras/jobresult.html +1364 -169
  564. nautobot/project-static/docs/models/extras/note.html +1180 -12
  565. nautobot/project-static/docs/models/extras/relationship.html +1182 -14
  566. nautobot/project-static/docs/models/extras/role.html +1320 -86
  567. nautobot/project-static/docs/models/extras/secret.html +1314 -86
  568. nautobot/project-static/docs/models/extras/secretsgroup.html +1276 -86
  569. nautobot/project-static/docs/models/extras/status.html +1188 -59
  570. nautobot/project-static/docs/models/extras/tag.html +1180 -12
  571. nautobot/project-static/docs/models/extras/webhook.html +1180 -12
  572. nautobot/project-static/docs/models/ipam/ipaddress.html +1327 -102
  573. nautobot/project-static/docs/models/ipam/prefix.html +1276 -86
  574. nautobot/project-static/docs/models/ipam/rir.html +1276 -86
  575. nautobot/project-static/docs/models/ipam/routetarget.html +1276 -86
  576. nautobot/project-static/docs/models/ipam/service.html +1276 -86
  577. nautobot/project-static/docs/models/ipam/vlan.html +1276 -86
  578. nautobot/project-static/docs/models/ipam/vlangroup.html +1276 -86
  579. nautobot/project-static/docs/models/ipam/vrf.html +1276 -86
  580. nautobot/project-static/docs/models/tenancy/tenant.html +1276 -86
  581. nautobot/project-static/docs/models/tenancy/tenantgroup.html +1276 -86
  582. nautobot/project-static/docs/models/users/objectpermission.html +1314 -86
  583. nautobot/project-static/docs/models/users/token.html +1276 -86
  584. nautobot/project-static/docs/models/virtualization/cluster.html +1276 -86
  585. nautobot/project-static/docs/models/virtualization/clustergroup.html +1276 -86
  586. nautobot/project-static/docs/models/virtualization/clustertype.html +1276 -86
  587. nautobot/project-static/docs/models/virtualization/virtualmachine.html +1321 -127
  588. nautobot/project-static/docs/models/virtualization/vminterface.html +1276 -86
  589. nautobot/project-static/docs/objects.inv +0 -0
  590. nautobot/project-static/docs/plugins/development.html +1726 -495
  591. nautobot/project-static/docs/plugins/index.html +1178 -10
  592. nautobot/project-static/docs/plugins/porting-from-netbox.html +1178 -10
  593. nautobot/project-static/docs/release-notes/index.html +1178 -10
  594. nautobot/project-static/docs/release-notes/version-1.0.html +1178 -10
  595. nautobot/project-static/docs/release-notes/version-1.1.html +1178 -10
  596. nautobot/project-static/docs/release-notes/version-1.2.html +1178 -10
  597. nautobot/project-static/docs/release-notes/version-1.3.html +1178 -10
  598. nautobot/project-static/docs/release-notes/version-1.4.html +1178 -10
  599. nautobot/project-static/docs/release-notes/version-1.5.html +1608 -225
  600. nautobot/project-static/docs/release-notes/version-2.0.html +1547 -47
  601. nautobot/project-static/docs/requirements.txt +1 -0
  602. nautobot/project-static/docs/rest-api/authentication.html +1179 -11
  603. nautobot/project-static/docs/rest-api/filtering.html +1178 -10
  604. nautobot/project-static/docs/rest-api/overview.html +1841 -446
  605. nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
  606. nautobot/project-static/docs/search/search_index.json +1 -1
  607. nautobot/project-static/docs/sitemap.xml +197 -187
  608. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  609. nautobot/project-static/docs/user-guides/custom-fields.html +1178 -10
  610. nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1178 -10
  611. nautobot/project-static/docs/user-guides/getting-started/index.html +1178 -10
  612. nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1178 -10
  613. nautobot/project-static/docs/user-guides/getting-started/ipam.html +1178 -10
  614. nautobot/project-static/docs/user-guides/getting-started/platforms.html +1178 -10
  615. nautobot/project-static/docs/user-guides/getting-started/regions.html +1178 -10
  616. nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1178 -10
  617. nautobot/project-static/docs/user-guides/getting-started/tenants.html +1178 -10
  618. nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1178 -10
  619. nautobot/project-static/docs/user-guides/git-data-source.html +1178 -10
  620. nautobot/project-static/docs/user-guides/graphql.html +1178 -10
  621. nautobot/project-static/docs/user-guides/relationships.html +1178 -10
  622. nautobot/project-static/docs/user-guides/s3-django-storage.html +1178 -10
  623. nautobot/project-static/js/forms.js +16 -9
  624. nautobot/project-static/js/theme.js +5 -0
  625. nautobot/tenancy/api/serializers.py +4 -32
  626. nautobot/tenancy/api/urls.py +1 -1
  627. nautobot/tenancy/forms.py +0 -28
  628. nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
  629. nautobot/tenancy/models.py +0 -25
  630. nautobot/tenancy/navigation.py +6 -39
  631. nautobot/tenancy/templates/tenancy/tenant.html +12 -12
  632. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  633. nautobot/tenancy/tests/test_api.py +1 -3
  634. nautobot/tenancy/tests/test_filters.py +10 -5
  635. nautobot/tenancy/views.py +0 -2
  636. nautobot/ui/.eslintignore +6 -0
  637. nautobot/ui/.gitignore +10 -0
  638. nautobot/ui/.prettierignore +9 -0
  639. nautobot/ui/.prettierrc +4 -0
  640. nautobot/ui/README.md +33 -0
  641. nautobot/ui/app_imports.js.j2 +7 -0
  642. nautobot/ui/craco.config.js +46 -0
  643. nautobot/ui/jsconfig-base.json +11 -0
  644. nautobot/ui/jsconfig.json +5 -0
  645. nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
  646. nautobot/ui/package-lock.json +21451 -0
  647. nautobot/ui/package.json +70 -0
  648. nautobot/ui/public/index.html +47 -0
  649. nautobot/ui/public/logo192.png +0 -0
  650. nautobot/ui/public/logo512.png +0 -0
  651. nautobot/ui/public/manifest.json +25 -0
  652. nautobot/ui/public/nautobot_logo.svg +131 -0
  653. nautobot/ui/public/robots.txt +3 -0
  654. nautobot/ui/src/App.js +71 -0
  655. nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
  656. nautobot/ui/src/components/AppTab.js +40 -0
  657. nautobot/ui/src/components/Apps.js +60 -0
  658. nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
  659. nautobot/ui/src/components/HomePanel.js +58 -0
  660. nautobot/ui/src/components/JobHistoryTable.js +78 -0
  661. nautobot/ui/src/components/Layout.js +53 -0
  662. nautobot/ui/src/components/LoadingWidget.js +25 -0
  663. nautobot/ui/src/components/Navbar.js +116 -0
  664. nautobot/ui/src/components/NotificationPopover.js +27 -0
  665. nautobot/ui/src/components/ObjectListTable.js +209 -0
  666. nautobot/ui/src/components/ReferenceDataTag.js +35 -0
  667. nautobot/ui/src/components/RouterButton.js +10 -0
  668. nautobot/ui/src/components/RouterLink.js +10 -0
  669. nautobot/ui/src/components/SidebarNav.js +147 -0
  670. nautobot/ui/src/components/Table.js +48 -0
  671. nautobot/ui/src/components/TableItem.js +71 -0
  672. nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
  673. nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
  674. nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
  675. nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
  676. nautobot/ui/src/components/__tests__/Table.test.js +36 -0
  677. nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
  678. nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
  679. nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
  680. nautobot/ui/src/components/pagination.js +93 -0
  681. nautobot/ui/src/components/paginator.js +79 -0
  682. nautobot/ui/src/components/paginator_form.js +43 -0
  683. nautobot/ui/src/components/usePagination.js +57 -0
  684. nautobot/ui/src/constants/apiPath.js +10 -0
  685. nautobot/ui/src/constants/icons.js +15 -0
  686. nautobot/ui/src/constants/size.js +15 -0
  687. nautobot/ui/src/index.js +65 -0
  688. nautobot/ui/src/reportWebVitals.js +15 -0
  689. nautobot/ui/src/router.js +77 -0
  690. nautobot/ui/src/utils/api.js +131 -0
  691. nautobot/ui/src/utils/app-import.js +15 -0
  692. nautobot/ui/src/utils/color.js +15 -0
  693. nautobot/ui/src/utils/date.js +14 -0
  694. nautobot/ui/src/utils/index.js +15 -0
  695. nautobot/ui/src/utils/navigation.js +32 -0
  696. nautobot/ui/src/utils/session.js +64 -0
  697. nautobot/ui/src/utils/store.js +242 -0
  698. nautobot/ui/src/utils/string.js +6 -0
  699. nautobot/ui/src/utils/url.js +4 -0
  700. nautobot/ui/src/views/Home.js +138 -0
  701. nautobot/ui/src/views/InstalledApps.js +80 -0
  702. nautobot/ui/src/views/Login.js +48 -0
  703. nautobot/ui/src/views/Logout.js +20 -0
  704. nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
  705. nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
  706. nautobot/ui/src/views/__tests__/Login.test.js +15 -0
  707. nautobot/ui/src/views/generic/GenericView.js +142 -0
  708. nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
  709. nautobot/ui/src/views/generic/ObjectList.js +127 -0
  710. nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
  711. nautobot/users/admin.py +1 -1
  712. nautobot/users/api/serializers.py +51 -61
  713. nautobot/users/api/urls.py +1 -1
  714. nautobot/users/api/views.py +53 -2
  715. nautobot/users/tests/test_api.py +110 -25
  716. nautobot/virtualization/api/serializers.py +18 -130
  717. nautobot/virtualization/api/urls.py +1 -1
  718. nautobot/virtualization/api/views.py +1 -22
  719. nautobot/virtualization/forms.py +13 -99
  720. nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -1
  721. nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -11
  722. nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -1
  723. nautobot/virtualization/migrations/0018_related_name_changes.py +1 -1
  724. nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
  725. nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
  726. nautobot/virtualization/migrations/{0021_ipam__namespaces.py → 0023_ipam__namespaces.py} +2 -2
  727. nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
  728. nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
  729. nautobot/virtualization/models.py +31 -123
  730. nautobot/virtualization/navigation.py +18 -99
  731. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -1
  732. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
  733. nautobot/virtualization/tests/test_api.py +25 -26
  734. nautobot/virtualization/tests/test_filters.py +41 -15
  735. nautobot/virtualization/tests/test_models.py +31 -7
  736. nautobot/virtualization/tests/test_views.py +42 -25
  737. nautobot/virtualization/views.py +7 -6
  738. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +3 -7
  739. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +744 -602
  740. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
  741. nautobot/circuits/api/nested_serializers.py +0 -69
  742. nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
  743. nautobot/dcim/api/nested_serializers.py +0 -356
  744. nautobot/dcim/templates/dcim/device_import.html +0 -5
  745. nautobot/dcim/templates/dcim/device_import_child.html +0 -5
  746. nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
  747. nautobot/extras/api/nested_serializers.py +0 -353
  748. nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -41
  749. nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -46
  750. nautobot/extras/reports.py +0 -60
  751. nautobot/extras/scripts.py +0 -72
  752. nautobot/extras/tests/example_jobs/script_variables.py +0 -67
  753. nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
  754. nautobot/extras/tests/example_jobs/test_fail.py +0 -16
  755. nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
  756. nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
  757. nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
  758. nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
  759. nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
  760. nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
  761. nautobot/extras/tests/example_jobs/test_modify_db.py +0 -18
  762. nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
  763. nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
  764. nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
  765. nautobot/extras/tests/example_jobs/test_pass.py +0 -19
  766. nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
  767. nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
  768. nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
  769. nautobot/ipam/api/nested_serializers.py +0 -159
  770. nautobot/ipam/migrations/0029_ipam__prefix__add_parent.py +0 -31
  771. nautobot/ipam/migrations/0030_ipam__prefix__data_migration.py +0 -13
  772. nautobot/ipam/migrations/0031_ipam__ipaddress__add_parent.py +0 -41
  773. nautobot/ipam/migrations/0032_ipam__ipaddress__data_migration.py +0 -11
  774. nautobot/tenancy/api/nested_serializers.py +0 -31
  775. nautobot/users/api/nested_serializers.py +0 -67
  776. nautobot/virtualization/api/nested_serializers.py +0 -65
  777. /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
  778. /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
  779. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
  780. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -1,31 +1,35 @@
1
1
  import json
2
- import logging
3
2
  import os
3
+ import sys
4
4
  import tempfile
5
5
  from unittest import mock
6
6
  import uuid
7
7
 
8
- import yaml
9
-
8
+ from celery.exceptions import NotRegistered
10
9
  from django.contrib.contenttypes.models import ContentType
11
10
  from django.test import RequestFactory
11
+ import yaml
12
12
 
13
- from nautobot.core.testing import TransactionTestCase
14
- from nautobot.dcim.models import Device, DeviceType, LocationType, Location, Manufacturer
15
- from nautobot.ipam.models import VLAN
13
+ from nautobot.core.jobs import GitRepositoryDryRun, GitRepositorySync
14
+ from nautobot.core.testing import (
15
+ TransactionTestCase,
16
+ create_job_result_and_run_job,
17
+ run_job_for_testing,
18
+ )
19
+ from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
16
20
  from nautobot.extras.choices import (
17
21
  JobResultStatusChoices,
18
22
  LogLevelChoices,
19
23
  SecretsGroupAccessTypeChoices,
20
24
  SecretsGroupSecretTypeChoices,
21
25
  )
22
- from nautobot.extras.datasources.git import pull_git_repository_and_refresh_data, git_repository_diff_origin_and_local
23
26
  from nautobot.extras.datasources.registry import get_datasource_contents
24
27
  from nautobot.extras.models import (
25
28
  ConfigContext,
26
29
  ConfigContextSchema,
27
30
  ExportTemplate,
28
31
  GitRepository,
32
+ Job,
29
33
  JobLogEntry,
30
34
  JobResult,
31
35
  Role,
@@ -34,6 +38,7 @@ from nautobot.extras.models import (
34
38
  SecretsGroupAssociation,
35
39
  Status,
36
40
  )
41
+ from nautobot.ipam.models import VLAN
37
42
 
38
43
 
39
44
  @mock.patch("nautobot.extras.datasources.git.GitRepo")
@@ -58,19 +63,22 @@ class GitTest(TransactionTestCase):
58
63
 
59
64
  self.location_type = LocationType.objects.create(name="Test Location Type", slug="test-location-type")
60
65
  self.location_type.content_types.add(ContentType.objects.get_for_model(Device))
61
- self.location = Location.objects.create(location_type=self.location_type, name="Test Location")
66
+ status = Status.objects.create(name="Active Test")
67
+ status.content_types.add(ContentType.objects.get_for_model(Location))
68
+ self.location = Location.objects.create(location_type=self.location_type, name="Test Location", status=status)
62
69
  self.manufacturer = Manufacturer.objects.create(name="Manufacturer 1")
63
70
  self.device_type = DeviceType.objects.create(
64
71
  manufacturer=self.manufacturer, model="Frobozz 1000", slug="frobozz1000"
65
72
  )
66
- self.role = Role.objects.get_for_model(Device).first()
67
- self.device_status = Status.objects.get_for_model(Device).first()
73
+ role = Role.objects.create(name="Active Test")
74
+ role.content_types.add(ContentType.objects.get_for_model(Device))
75
+ status.content_types.add(ContentType.objects.get_for_model(Device))
68
76
  self.device = Device.objects.create(
69
77
  name="test-device",
70
- role=self.role,
78
+ role=role,
71
79
  device_type=self.device_type,
72
80
  location=self.location,
73
- status=self.device_status,
81
+ status=status,
74
82
  )
75
83
 
76
84
  self.repo = GitRepository(
@@ -80,13 +88,9 @@ class GitTest(TransactionTestCase):
80
88
  # Provide everything we know we can provide
81
89
  provided_contents=[entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")],
82
90
  )
83
- self.repo.save(trigger_resync=False)
91
+ self.repo.save()
84
92
 
85
- self.job_result = JobResult.objects.create(
86
- name=self.repo.name,
87
- obj_type=ContentType.objects.get_for_model(GitRepository),
88
- task_id=uuid.uuid4(),
89
- )
93
+ self.job_result = JobResult.objects.create(name=self.repo.name)
90
94
 
91
95
  self.config_context_schema = {
92
96
  "_metadata": {
@@ -114,15 +118,26 @@ class GitTest(TransactionTestCase):
114
118
  },
115
119
  }
116
120
 
121
+ def tearDown(self):
122
+ if f"{self.repo.slug}.jobs" in sys.modules:
123
+ del sys.modules[f"{self.repo.slug}.jobs"]
124
+ if f"{self.repo.slug}" in sys.modules:
125
+ del sys.modules[f"{self.repo.slug}"]
126
+
117
127
  def populate_repo(self, path, url, *args, **kwargs):
118
128
  os.makedirs(path)
119
- # TODO(Glenn): populate Jobs as well?
129
+
120
130
  os.makedirs(os.path.join(path, "config_contexts"))
121
131
  os.makedirs(os.path.join(path, "config_contexts", "devices"))
122
132
  os.makedirs(os.path.join(path, "config_contexts", "locations"))
123
133
  os.makedirs(os.path.join(path, "config_context_schemas"))
124
134
  os.makedirs(os.path.join(path, "export_templates", "dcim", "device"))
125
135
  os.makedirs(os.path.join(path, "export_templates", "ipam", "vlan"))
136
+ os.makedirs(os.path.join(path, "jobs"))
137
+
138
+ with open(os.path.join(path, "__init__.py"), "w") as fd:
139
+ # Required for job importing
140
+ pass
126
141
 
127
142
  with open(os.path.join(path, "config_contexts", "context.yaml"), "w") as fd:
128
143
  yaml.dump(
@@ -164,9 +179,16 @@ class GitTest(TransactionTestCase):
164
179
  with open(os.path.join(path, "export_templates", "ipam", "vlan", "template.j2"), "w") as fd:
165
180
  fd.write("{% for vlan in queryset %}\n{{ vlan.name }}\n{% endfor %}")
166
181
 
182
+ with open(os.path.join(path, "jobs", "__init__.py"), "w") as fd:
183
+ fd.write("from nautobot.core.celery import register_jobs\nfrom .my_job import MyJob\nregister_jobs(MyJob)")
184
+
185
+ with open(os.path.join(path, "jobs", "my_job.py"), "w") as fd:
186
+ fd.write("from nautobot.extras.jobs import Job\nclass MyJob(Job):\n def run(self):\n pass")
187
+
167
188
  return mock.DEFAULT
168
189
 
169
190
  def empty_repo(self, path, url, *args, **kwargs):
191
+ os.remove(os.path.join(path, "__init__.py"))
170
192
  os.remove(os.path.join(path, "config_contexts", "context.yaml"))
171
193
  os.remove(os.path.join(path, "config_contexts", "locations", f"{self.location.slug}.json"))
172
194
  os.remove(os.path.join(path, "config_contexts", "devices", f"{self.device.name}.json"))
@@ -174,8 +196,16 @@ class GitTest(TransactionTestCase):
174
196
  os.remove(os.path.join(path, "export_templates", "dcim", "device", "template.j2"))
175
197
  os.remove(os.path.join(path, "export_templates", "dcim", "device", "template2.html"))
176
198
  os.remove(os.path.join(path, "export_templates", "ipam", "vlan", "template.j2"))
199
+ os.remove(os.path.join(path, "jobs", "__init__.py"))
200
+ os.remove(os.path.join(path, "jobs", "my_job.py"))
177
201
  return mock.DEFAULT
178
202
 
203
+ def assert_repo_slug_valid_python_package_name(self):
204
+ git_repository = GitRepository.objects.create(
205
+ name="1 Very-Bad Git_____Repo Name (2)", remote_url="http://localhost/git.git"
206
+ )
207
+ self.assertEqual(git_repository.slug, "a1_very_bad_git_____repo_name_2")
208
+
179
209
  def assert_config_context_schema_record_exists(self, name):
180
210
  """Helper Func to assert ConfigContextSchema with name=name exists"""
181
211
  config_context_schema_record = ConfigContextSchema.objects.get(
@@ -260,9 +290,26 @@ class GitTest(TransactionTestCase):
260
290
  )
261
291
  self.assertIsNotNone(export_template_vlan)
262
292
 
293
+ def assert_job_exists(self, installed=True):
294
+ """Helper function to assert JobModel and registerd Job exist."""
295
+ # Is it registered correctly in the database?
296
+ job_model = Job.objects.get(name="MyJob", module_name=f"{self.repo.slug}.jobs.my_job", job_class_name="MyJob")
297
+ self.assertIsNotNone(job_model)
298
+ if installed:
299
+ self.assertTrue(job_model.installed)
300
+ # Is the in-memory code accessible?
301
+ self.assertIsNotNone(job_model.job_class)
302
+ # Is it registered properly with Celery?
303
+ self.assertIsNotNone(job_model.job_task)
304
+ else:
305
+ self.assertFalse(job_model.installed)
306
+ self.assertIsNone(job_model.job_class)
307
+ with self.assertRaises(NotRegistered):
308
+ job_model.job_task
309
+
263
310
  def test_pull_git_repository_and_refresh_data_with_no_data(self, MockGitRepo):
264
311
  """
265
- The pull_git_repository_and_refresh_data job should succeed if the given repo is empty.
312
+ The pull_git_repository_and_refresh_data job should fail if the given repo is empty.
266
313
  """
267
314
  with tempfile.TemporaryDirectory() as tempdir:
268
315
  with self.settings(GIT_ROOT=tempdir):
@@ -272,24 +319,32 @@ class GitTest(TransactionTestCase):
272
319
  return mock.DEFAULT
273
320
 
274
321
  MockGitRepo.side_effect = create_empty_repo
275
- MockGitRepo.return_value.checkout.return_value = self.COMMIT_HEXSHA
322
+ MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
276
323
 
277
324
  # Run the Git operation and refresh the object from the DB
278
- pull_git_repository_and_refresh_data(self.repo.pk, self.mock_request, self.job_result.pk)
279
- self.job_result.refresh_from_db()
325
+ # pull_git_repository_and_refresh_data(self.repo.pk, self.mock_request, self.job_result.pk)
326
+ job_result = create_job_result_and_run_job(
327
+ module="nautobot.core.jobs",
328
+ name="GitRepositorySync",
329
+ source="system",
330
+ repository=self.repo.pk,
331
+ )
280
332
 
281
333
  self.assertEqual(
282
- self.job_result.status,
283
- JobResultStatusChoices.STATUS_SUCCESS,
284
- self.job_result.data,
334
+ job_result.status,
335
+ JobResultStatusChoices.STATUS_FAILURE,
336
+ (
337
+ job_result.result,
338
+ list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
339
+ ),
285
340
  )
286
341
  self.repo.refresh_from_db()
287
- self.assertEqual(self.repo.current_head, self.COMMIT_HEXSHA, self.job_result.data)
342
+ self.assertEqual(self.repo.current_head, self.COMMIT_HEXSHA, job_result.result)
288
343
  MockGitRepo.assert_called_with(os.path.join(tempdir, self.repo.slug), "http://localhost/git.git")
289
344
 
290
- log_entries = JobLogEntry.objects.filter(job_result=self.job_result)
291
- warning_logs = log_entries.filter(log_level=LogLevelChoices.LOG_WARNING)
292
- warning_logs.get(grouping="jobs", message__contains="No `jobs` subdirectory found")
345
+ log_entries = JobLogEntry.objects.filter(job_result=job_result)
346
+ failure_logs = log_entries.filter(log_level=LogLevelChoices.LOG_ERROR)
347
+ failure_logs.get(grouping="jobs", message__contains="Error in loading Jobs from Git repository: ")
293
348
 
294
349
  def test_pull_git_repository_and_refresh_data_with_secrets(self, MockGitRepo):
295
350
  """
@@ -303,7 +358,7 @@ class GitTest(TransactionTestCase):
303
358
  return mock.DEFAULT
304
359
 
305
360
  MockGitRepo.side_effect = create_empty_repo
306
- MockGitRepo.return_value.checkout.return_value = self.COMMIT_HEXSHA
361
+ MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
307
362
 
308
363
  with open(os.path.join(tempdir, "username.txt"), "wt") as handle:
309
364
  handle.write("núñez")
@@ -336,24 +391,27 @@ class GitTest(TransactionTestCase):
336
391
  )
337
392
 
338
393
  self.repo.secrets_group = secrets_group
339
- self.repo.save(trigger_resync=False)
394
+ self.repo.provided_contents.remove("extras.job") # avoid failing due to lack of jobs module
395
+ self.repo.save()
340
396
 
341
397
  self.mock_request.id = uuid.uuid4()
342
- self.job_result = JobResult.objects.create(
343
- name=self.repo.name,
344
- obj_type=ContentType.objects.get_for_model(GitRepository),
345
- task_id=uuid.uuid4(),
346
- )
347
398
 
348
- # Run the Git operation and refresh the object from the DB
349
- pull_git_repository_and_refresh_data(self.repo.pk, self.mock_request, self.job_result.pk)
350
- self.job_result.refresh_from_db()
399
+ job_result = create_job_result_and_run_job(
400
+ module="nautobot.core.jobs",
401
+ name="GitRepositorySync",
402
+ source="system",
403
+ repository=self.repo.pk,
404
+ )
351
405
 
352
406
  self.assertEqual(
353
- self.job_result.status,
407
+ job_result.status,
354
408
  JobResultStatusChoices.STATUS_SUCCESS,
355
- self.job_result.data,
409
+ (
410
+ job_result.result,
411
+ list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
412
+ ),
356
413
  )
414
+ self.repo.refresh_from_db()
357
415
  MockGitRepo.assert_called_with(
358
416
  os.path.join(tempdir, self.repo.slug),
359
417
  "http://n%C3%BA%C3%B1ez:1%3A3%40%2F%3F%3Dab%40@localhost/git.git",
@@ -366,16 +424,19 @@ class GitTest(TransactionTestCase):
366
424
  with tempfile.TemporaryDirectory() as tempdir:
367
425
  with self.settings(GIT_ROOT=tempdir):
368
426
  MockGitRepo.side_effect = self.populate_repo
369
- MockGitRepo.return_value.checkout.return_value = self.COMMIT_HEXSHA
427
+ MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
370
428
 
371
429
  # Run the Git operation and refresh the object from the DB
372
- pull_git_repository_and_refresh_data(self.repo.pk, self.mock_request, self.job_result.pk)
373
- self.job_result.refresh_from_db()
374
-
430
+ job_model = GitRepositorySync().job_model
431
+ job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
432
+ job_result.refresh_from_db()
375
433
  self.assertEqual(
376
- self.job_result.status,
434
+ job_result.status,
377
435
  JobResultStatusChoices.STATUS_SUCCESS,
378
- self.job_result.data,
436
+ (
437
+ job_result.result,
438
+ list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
439
+ ),
379
440
  )
380
441
 
381
442
  # Make sure explicit ConfigContext was successfully loaded from file
@@ -399,24 +460,25 @@ class GitTest(TransactionTestCase):
399
460
  # Case when ContentType.model != ContentType.name, template was added and deleted during sync (#570)
400
461
  self.assert_export_template_vlan_exists("template.j2")
401
462
 
463
+ # Make sure Job was successfully loaded from file and registered as a JobModel
464
+ self.assert_job_exists()
465
+
402
466
  # Now "resync" the repository, but now those files no longer exist in the repository
403
467
  MockGitRepo.side_effect = self.empty_repo
468
+
404
469
  # For verisimilitude, don't re-use the old request and job_result
405
470
  self.mock_request.id = uuid.uuid4()
406
- self.job_result = JobResult.objects.create(
407
- name=self.repo.name,
408
- obj_type=ContentType.objects.get_for_model(GitRepository),
409
- task_id=uuid.uuid4(),
410
- )
411
471
 
412
472
  # Run the Git operation and refresh the object from the DB
413
- pull_git_repository_and_refresh_data(self.repo.pk, self.mock_request, self.job_result.pk)
414
- self.job_result.refresh_from_db()
415
-
473
+ job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
474
+ job_result.refresh_from_db()
416
475
  self.assertEqual(
417
- self.job_result.status,
476
+ job_result.status,
418
477
  JobResultStatusChoices.STATUS_SUCCESS,
419
- self.job_result.data,
478
+ (
479
+ job_result.result,
480
+ list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
481
+ ),
420
482
  )
421
483
 
422
484
  # Verify that objects have been removed from the database
@@ -442,6 +504,9 @@ class GitTest(TransactionTestCase):
442
504
  self.assertIsNone(device.local_config_context_data)
443
505
  self.assertIsNone(device.local_config_context_data_owner)
444
506
 
507
+ # Verify that Job database record still exists but code is no longer installed/loaded
508
+ self.assert_job_exists(installed=False)
509
+
445
510
  def test_pull_git_repository_and_refresh_data_with_bad_data(self, MockGitRepo):
446
511
  """
447
512
  The test_pull_git_repository_and_refresh_data job should gracefully handle bad data in the Git repository
@@ -460,6 +525,8 @@ class GitTest(TransactionTestCase):
460
525
  # Incorrect directories
461
526
  os.makedirs(os.path.join(path, "devices"))
462
527
  os.makedirs(os.path.join(path, "dcim"))
528
+ with open(os.path.join(path, "__init__.py"), "w") as fd:
529
+ pass
463
530
  # Malformed JSON
464
531
  with open(os.path.join(path, "config_contexts", "context.json"), "w") as fd:
465
532
  fd.write('{"data": ')
@@ -487,27 +554,31 @@ class GitTest(TransactionTestCase):
487
554
  fd.write("print(")
488
555
  with open(os.path.join(path, "jobs", "importerror.py"), "w") as fd:
489
556
  fd.write("import nosuchmodule")
557
+ with open(os.path.join(path, "jobs", "__init__.py"), "w") as fd:
558
+ fd.write("import .syntaxerror\nimport .importerror")
490
559
  return mock.DEFAULT
491
560
 
492
561
  MockGitRepo.side_effect = populate_repo
493
- MockGitRepo.return_value.checkout.return_value = self.COMMIT_HEXSHA
562
+ MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
494
563
 
495
564
  # Run the Git operation and refresh the object from the DB
496
- logging.disable(logging.ERROR)
497
- pull_git_repository_and_refresh_data(self.repo.pk, self.mock_request, self.job_result.pk)
498
- logging.disable(logging.NOTSET)
499
- self.job_result.refresh_from_db()
565
+ job_model = GitRepositorySync().job_model
566
+ job_result = run_job_for_testing(
567
+ job=job_model,
568
+ repository=self.repo.pk,
569
+ )
570
+ job_result.refresh_from_db()
500
571
 
501
572
  self.assertEqual(
502
- self.job_result.status,
573
+ job_result.status,
503
574
  JobResultStatusChoices.STATUS_FAILURE,
504
- self.job_result.data,
575
+ job_result.result,
505
576
  )
506
577
 
507
578
  # Check for specific log messages
508
- log_entries = JobLogEntry.objects.filter(job_result=self.job_result)
579
+ log_entries = JobLogEntry.objects.filter(job_result=job_result)
509
580
  warning_logs = log_entries.filter(log_level=LogLevelChoices.LOG_WARNING)
510
- failure_logs = log_entries.filter(log_level=LogLevelChoices.LOG_FAILURE)
581
+ failure_logs = log_entries.filter(log_level=LogLevelChoices.LOG_ERROR)
511
582
 
512
583
  warning_logs.get(
513
584
  grouping="config contexts", message__contains='Found "devices" directory in the repository root'
@@ -555,11 +626,7 @@ class GitTest(TransactionTestCase):
555
626
  failure_logs.get(
556
627
  grouping="jobs",
557
628
  # The specific exception message differs between Python versions
558
- message__contains="Error in loading Jobs from `syntaxerror`: ",
559
- )
560
- failure_logs.get(
561
- grouping="jobs",
562
- message__contains="Error in loading Jobs from `importerror`: `No module named 'nosuchmodule'`",
629
+ message__contains="Error in loading Jobs from Git repository: ",
563
630
  )
564
631
 
565
632
  def test_delete_git_repository_cleanup(self, MockGitRepo):
@@ -571,11 +638,13 @@ class GitTest(TransactionTestCase):
571
638
 
572
639
  def populate_repo(path, url):
573
640
  os.makedirs(path)
574
- # Just make config_contexts and export_templates directories as we don't load jobs
575
641
  os.makedirs(os.path.join(path, "config_contexts"))
576
642
  os.makedirs(os.path.join(path, "config_contexts", "devices"))
577
643
  os.makedirs(os.path.join(path, "config_context_schemas"))
578
644
  os.makedirs(os.path.join(path, "export_templates", "dcim", "device"))
645
+ os.makedirs(os.path.join(path, "jobs"))
646
+ with open(os.path.join(path, "__init__.py"), "w") as fd:
647
+ pass
579
648
  with open(os.path.join(path, "config_contexts", "context.yaml"), "w") as fd:
580
649
  yaml.dump(
581
650
  {
@@ -606,19 +675,36 @@ class GitTest(TransactionTestCase):
606
675
  "w",
607
676
  ) as fd:
608
677
  fd.write("{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
678
+ with open(os.path.join(path, "jobs", "__init__.py"), "w") as fd:
679
+ fd.write(
680
+ "from nautobot.core.celery import register_jobs\nfrom .my_job import MyJob\nregister_jobs(MyJob)"
681
+ )
682
+
683
+ with open(os.path.join(path, "jobs", "my_job.py"), "w") as fd:
684
+ fd.write(
685
+ "from nautobot.extras.jobs import Job\nclass MyJob(Job):\n def run(self):\n pass"
686
+ )
687
+
609
688
  return mock.DEFAULT
610
689
 
611
690
  MockGitRepo.side_effect = populate_repo
612
- MockGitRepo.return_value.checkout.return_value = self.COMMIT_HEXSHA
691
+ MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
613
692
 
614
693
  # Run the Git operation and refresh the object from the DB
615
- pull_git_repository_and_refresh_data(self.repo.pk, self.mock_request, self.job_result.pk)
616
- self.job_result.refresh_from_db()
694
+ job_model = GitRepositorySync().job_model
695
+ job_result = run_job_for_testing(
696
+ job=job_model,
697
+ repository=self.repo.pk,
698
+ )
699
+ job_result.refresh_from_db()
617
700
 
618
701
  self.assertEqual(
619
- self.job_result.status,
702
+ job_result.status,
620
703
  JobResultStatusChoices.STATUS_SUCCESS,
621
- self.job_result.data,
704
+ (
705
+ job_result.result,
706
+ list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
707
+ ),
622
708
  )
623
709
 
624
710
  # Make sure ConfigContext was successfully loaded from file
@@ -637,51 +723,34 @@ class GitTest(TransactionTestCase):
637
723
  )
638
724
 
639
725
  # Make sure ConfigContextSchema was successfully loaded from file
640
- config_context_schema_record = ConfigContextSchema.objects.get(
641
- name="Config Context Schema 1",
642
- owner_object_id=self.repo.pk,
643
- owner_content_type=ContentType.objects.get_for_model(GitRepository),
644
- )
645
- self.assertEqual(config_context_schema_record, config_context.config_context_schema)
646
-
647
- config_context_schema = self.config_context_schema
648
- config_context_schema_metadata = config_context_schema["_metadata"]
649
- self.assertIsNotNone(config_context_schema_record)
650
- self.assertEqual(config_context_schema_metadata["name"], config_context_schema_record.name)
651
- self.assertEqual(config_context_schema["data_schema"], config_context_schema_record.data_schema)
726
+ self.assert_config_context_schema_record_exists("Config Context Schema 1")
652
727
 
653
728
  # Make sure Device local config context was successfully populated from file
654
- device = Device.objects.get(name=self.device.name)
655
- self.assertIsNotNone(device.local_config_context_data)
656
- self.assertEqual({"dns-servers": ["8.8.8.8"]}, device.local_config_context_data)
657
- self.assertEqual(device.local_config_context_data_owner, self.repo)
729
+ self.assert_device_exists(self.device.name)
658
730
 
659
731
  # Make sure ExportTemplate was successfully loaded from file
660
- export_template = ExportTemplate.objects.get(
661
- owner_object_id=self.repo.pk,
662
- owner_content_type=ContentType.objects.get_for_model(GitRepository),
663
- content_type=ContentType.objects.get_for_model(Device),
664
- name="template.j2",
665
- )
666
- self.assertIsNotNone(export_template)
732
+ self.assert_export_template_device("template.j2")
733
+
734
+ # Make sure Job is loaded and registered into Celery as well as the database
735
+ self.assert_job_exists()
667
736
 
668
737
  # Now delete the GitRepository
669
738
  self.repo.delete()
670
739
 
671
740
  with self.assertRaises(ConfigContext.DoesNotExist):
672
- config_context = ConfigContext.objects.get(
741
+ ConfigContext.objects.get(
673
742
  owner_object_id=self.repo.pk,
674
743
  owner_content_type=ContentType.objects.get_for_model(GitRepository),
675
744
  )
676
745
 
677
746
  with self.assertRaises(ConfigContextSchema.DoesNotExist):
678
- config_context_schema = ConfigContextSchema.objects.get(
747
+ ConfigContextSchema.objects.get(
679
748
  owner_object_id=self.repo.pk,
680
749
  owner_content_type=ContentType.objects.get_for_model(GitRepository),
681
750
  )
682
751
 
683
752
  with self.assertRaises(ExportTemplate.DoesNotExist):
684
- export_template = ExportTemplate.objects.get(
753
+ ExportTemplate.objects.get(
685
754
  owner_object_id=self.repo.pk,
686
755
  owner_content_type=ContentType.objects.get_for_model(GitRepository),
687
756
  )
@@ -690,6 +759,8 @@ class GitTest(TransactionTestCase):
690
759
  self.assertIsNone(device.local_config_context_data)
691
760
  self.assertIsNone(device.local_config_context_data_owner)
692
761
 
762
+ self.assert_job_exists(installed=False)
763
+
693
764
  def test_git_dry_run(self, MockGitRepo):
694
765
  with tempfile.TemporaryDirectory() as tempdir:
695
766
  with self.settings(GIT_ROOT=tempdir):
@@ -701,16 +772,22 @@ class GitTest(TransactionTestCase):
701
772
  MockGitRepo.side_effect = create_empty_repo
702
773
 
703
774
  self.mock_request.id = uuid.uuid4()
704
- self.job_result = JobResult.objects.create(
705
- name=self.repo.name,
706
- obj_type=ContentType.objects.get_for_model(GitRepository),
707
- task_id=uuid.uuid4(),
708
- )
709
775
 
710
- git_repository_diff_origin_and_local(self.repo.pk, self.mock_request, self.job_result.pk)
711
- self.job_result.refresh_from_db()
776
+ job_model = GitRepositoryDryRun().job_model
777
+ job_result = run_job_for_testing(
778
+ job=job_model,
779
+ repository=self.repo.pk,
780
+ )
781
+ job_result.refresh_from_db()
712
782
 
713
- self.assertEqual(self.job_result.status, JobResultStatusChoices.STATUS_SUCCESS, self.job_result.data)
783
+ self.assertEqual(
784
+ job_result.status,
785
+ JobResultStatusChoices.STATUS_SUCCESS,
786
+ (
787
+ job_result.result,
788
+ list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
789
+ ),
790
+ )
714
791
 
715
792
  MockGitRepo.return_value.checkout.assert_not_called()
716
793
  MockGitRepo.assert_called_with(
@@ -1,5 +1,4 @@
1
1
  import random
2
- from unittest import skip
3
2
 
4
3
  from django.contrib.contenttypes.models import ContentType
5
4
  from django.core.exceptions import ValidationError
@@ -47,11 +46,12 @@ class DynamicGroupTestBase(TestCase):
47
46
  cls.dynamicgroup_ct = ContentType.objects.get_for_model(DynamicGroup)
48
47
  cls.lt = LocationType.objects.get(name="Campus")
49
48
 
49
+ loc_status = Status.objects.get_for_model(Location).first()
50
50
  cls.locations = [
51
- Location.objects.create(name="Location 1", slug="location-1", location_type=cls.lt),
52
- Location.objects.create(name="Location 2", slug="location-2", location_type=cls.lt),
53
- Location.objects.create(name="Location 3", slug="location-3", location_type=cls.lt),
54
- Location.objects.create(name="Location 4", slug="location-4", location_type=cls.lt),
51
+ Location.objects.create(name="Location 1", slug="location-1", location_type=cls.lt, status=loc_status),
52
+ Location.objects.create(name="Location 2", slug="location-2", location_type=cls.lt, status=loc_status),
53
+ Location.objects.create(name="Location 3", slug="location-3", location_type=cls.lt, status=loc_status),
54
+ Location.objects.create(name="Location 4", slug="location-4", location_type=cls.lt, status=loc_status),
55
55
  ]
56
56
 
57
57
  cls.manufacturer = Manufacturer.objects.first()
@@ -187,7 +187,6 @@ class DynamicGroupTestBase(TestCase):
187
187
  )
188
188
 
189
189
 
190
- @skip(reason="Content Types are BROKEN")
191
190
  class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mixin?
192
191
  """DynamicGroup model tests."""
193
192
 
@@ -832,8 +831,8 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
832
831
  prefix = Prefix.objects.first()
833
832
 
834
833
  relationship = Relationship(
835
- name="Device to Prefix",
836
- slug="device_to_prefix",
834
+ label="Device to Prefix",
835
+ key="device_to_prefix",
837
836
  source_type=self.device_ct,
838
837
  source_label="My Prefixes",
839
838
  source_filter=None,