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,4 +1,4 @@
1
- from datetime import datetime, timedelta
1
+ from datetime import timedelta
2
2
  import urllib.parse
3
3
  import uuid
4
4
 
@@ -8,7 +8,7 @@ from django.core.exceptions import ValidationError
8
8
  from django.test import override_settings
9
9
  from django.urls import reverse
10
10
  from django.utils import timezone
11
- from unittest import mock, skip
11
+ from unittest import mock
12
12
 
13
13
  from nautobot.core.choices import ColorChoices
14
14
  from nautobot.core.models.fields import slugify_dashes_to_underscores
@@ -19,7 +19,6 @@ from nautobot.dcim.tests import test_views
19
19
  from nautobot.extras.choices import (
20
20
  CustomFieldTypeChoices,
21
21
  JobExecutionType,
22
- JobSourceChoices,
23
22
  ObjectChangeActionChoices,
24
23
  SecretsGroupAccessTypeChoices,
25
24
  SecretsGroupSecretTypeChoices,
@@ -52,7 +51,7 @@ from nautobot.extras.models import (
52
51
  ComputedField,
53
52
  )
54
53
  from nautobot.extras.tests.test_relationships import RequiredRelationshipTestMixin
55
- from nautobot.extras.utils import get_job_content_type, TaggableClassesQuery
54
+ from nautobot.extras.utils import TaggableClassesQuery
56
55
  from nautobot.ipam.factory import VLANFactory
57
56
  from nautobot.ipam.models import VLAN, VLANGroup
58
57
  from nautobot.users.models import ObjectPermission
@@ -62,7 +61,6 @@ from nautobot.users.models import ObjectPermission
62
61
  User = get_user_model()
63
62
 
64
63
 
65
- @skip(reason="Content Types are BROKEN")
66
64
  class ComputedFieldTestCase(
67
65
  ViewTestCases.BulkDeleteObjectsViewTestCase,
68
66
  ViewTestCases.CreateObjectViewTestCase,
@@ -84,14 +82,14 @@ class ComputedFieldTestCase(
84
82
  ComputedField(
85
83
  content_type=obj_type,
86
84
  label="Computed Field One",
87
- slug="computed_field_one",
85
+ key="computed_field_one",
88
86
  template="Location name is {{ obj.name }}",
89
87
  fallback_value="Template error",
90
88
  weight=100,
91
89
  ),
92
90
  ComputedField(
93
91
  content_type=obj_type,
94
- slug="computed_field_two",
92
+ key="computed_field_two",
95
93
  label="Computed Field Two",
96
94
  template="Location name is {{ obj.name }}",
97
95
  fallback_value="Template error",
@@ -99,7 +97,7 @@ class ComputedFieldTestCase(
99
97
  ),
100
98
  ComputedField(
101
99
  content_type=obj_type,
102
- slug="computed_field_three",
100
+ key="computed_field_three",
103
101
  label="Computed Field Three",
104
102
  template="Location name is {{ obj.name }}",
105
103
  weight=100,
@@ -113,7 +111,8 @@ class ComputedFieldTestCase(
113
111
  ),
114
112
  )
115
113
  cls.location_type = LocationType.objects.get(name="Campus")
116
- cls.location1 = Location(name="NYC", location_type=cls.location_type)
114
+ status = Status.objects.get_for_model(Location).first()
115
+ cls.location1 = Location(name="NYC", location_type=cls.location_type, status=status)
117
116
  cls.location1.save()
118
117
 
119
118
  for cf in computed_fields:
@@ -121,7 +120,7 @@ class ComputedFieldTestCase(
121
120
 
122
121
  cls.form_data = {
123
122
  "content_type": obj_type.pk,
124
- "slug": "computed_field_four",
123
+ "key": "computed_field_four",
125
124
  "label": "Computed Field Four",
126
125
  "template": "{{ obj.name }} is the best Location!",
127
126
  "fallback_value": ":skull_emoji:",
@@ -287,7 +286,7 @@ class ConfigContextSchemaTestCase(
287
286
  cls.form_data = {
288
287
  "name": "Schema X",
289
288
  "slug": "schema-x",
290
- "data_schema": '{"type": "object", "properties": {"baz": {"type": "string"}}}',
289
+ "data_schema": '{"type": "object","properties": {"baz": {"type": "string"}}}', # Intentionally misformatted (missing space) to ensure proper formatting on output
291
290
  }
292
291
 
293
292
  cls.bulk_edit_data = {
@@ -450,7 +449,8 @@ class CustomLinkTest(TestCase):
450
449
  )
451
450
  customlink.save()
452
451
  location_type = LocationType.objects.get(name="Campus")
453
- location = Location(name="Test Location", slug="test-location", location_type=location_type)
452
+ status = Status.objects.get_for_model(Location).first()
453
+ location = Location(name="Test Location", slug="test-location", location_type=location_type, status=status)
454
454
  location.save()
455
455
 
456
456
  response = self.client.get(location.get_absolute_url(), follow=True)
@@ -518,6 +518,17 @@ class DynamicGroupTestCase(
518
518
  instance.refresh_from_db()
519
519
  self.assertEqual(instance.filter, {"serial": data["filter-serial"]})
520
520
 
521
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
522
+ def test_filter_by_content_type(self):
523
+ """
524
+ Test that filtering by `content_type` in the UI succeeds.
525
+
526
+ This is a regression test for https://github.com/nautobot/nautobot/issues/3612
527
+ """
528
+ path = self._get_url("list")
529
+ response = self.client.get(path + "?content_type=dcim.device")
530
+ self.assertHttpStatus(response, 200)
531
+
521
532
 
522
533
  class ExportTemplateTestCase(
523
534
  ViewTestCases.CreateObjectViewTestCase,
@@ -572,6 +583,7 @@ class GitRepositoryTestCase(
572
583
  ViewTestCases.ListObjectsViewTestCase,
573
584
  ):
574
585
  model = GitRepository
586
+ slugify_function = staticmethod(slugify_dashes_to_underscores)
575
587
 
576
588
  @classmethod
577
589
  def setUpTestData(cls):
@@ -582,17 +594,17 @@ class GitRepositoryTestCase(
582
594
 
583
595
  # Create four GitRepository records
584
596
  repos = (
585
- GitRepository(name="Repo 1", slug="repo-1", remote_url="https://example.com/repo1.git"),
586
- GitRepository(name="Repo 2", slug="repo-2", remote_url="https://example.com/repo2.git"),
587
- GitRepository(name="Repo 3", slug="repo-3", remote_url="https://example.com/repo3.git"),
597
+ GitRepository(name="Repo 1", slug="repo_1", remote_url="https://example.com/repo1.git"),
598
+ GitRepository(name="Repo 2", slug="repo_2", remote_url="https://example.com/repo2.git"),
599
+ GitRepository(name="Repo 3", slug="repo_3", remote_url="https://example.com/repo3.git"),
588
600
  GitRepository(name="Repo 4", remote_url="https://example.com/repo4.git", secrets_group=secrets_groups[0]),
589
601
  )
590
602
  for repo in repos:
591
- repo.save(trigger_resync=False)
603
+ repo.validated_save()
592
604
 
593
605
  cls.form_data = {
594
606
  "name": "A new Git repository",
595
- "slug": "a-new-git-repository",
607
+ "slug": "a_new_git_repository",
596
608
  "remote_url": "http://example.com/a_new_git_repository.git",
597
609
  "branch": "develop",
598
610
  "_token": "1234567890abcdef1234567890abcdef",
@@ -606,14 +618,28 @@ class GitRepositoryTestCase(
606
618
 
607
619
  cls.csv_data = (
608
620
  "name,slug,remote_url,branch,secrets_group,provided_contents",
609
- "Git Repository 5,git-repo-5,https://example.com,main,,extras.configcontext",
610
- "Git Repository 6,git-repo-6,https://example.com,develop,Secrets Group 2,",
611
- 'Git Repository 7,git-repo-7,https://example.com,next,Secrets Group 2,"extras.job,extras.configcontext"',
621
+ "Git Repository 5,git_repo_5,https://example.com,main,,extras.configcontext",
622
+ "Git Repository 6,git_repo_6,https://example.com,develop,Secrets Group 2,",
623
+ 'Git Repository 7,git_repo_7,https://example.com,next,Secrets Group 2,"extras.job,extras.configcontext"',
612
624
  )
613
625
 
614
626
  cls.slug_source = "name"
615
627
  cls.slug_test_object = "Repo 4"
616
628
 
629
+ def test_edit_object_with_permission(self):
630
+ instance = self._get_queryset().first()
631
+ form_data = self.form_data.copy()
632
+ form_data["slug"] = instance.slug # Slug is not editable
633
+ self.form_data = form_data
634
+ super().test_edit_object_with_permission()
635
+
636
+ def test_edit_object_with_constrained_permission(self):
637
+ instance = self._get_queryset().first()
638
+ form_data = self.form_data.copy()
639
+ form_data["slug"] = instance.slug # Slug is not editable
640
+ self.form_data = form_data
641
+ super().test_edit_object_with_constrained_permission()
642
+
617
643
 
618
644
  class NoteTestCase(
619
645
  ViewTestCases.CreateObjectViewTestCase,
@@ -934,27 +960,27 @@ class ScheduledJobTestCase(
934
960
  user = User.objects.create(username="user1", is_active=True)
935
961
  ScheduledJob.objects.create(
936
962
  name="test1",
937
- task="nautobot.extras.jobs.scheduled_job_handler",
938
- job_class="local/test_pass/TestPass",
963
+ task="pass.TestPass",
964
+ job_class="pass.TestPass",
939
965
  interval=JobExecutionType.TYPE_IMMEDIATELY,
940
966
  user=user,
941
- start_time=datetime.now(),
967
+ start_time=timezone.now(),
942
968
  )
943
969
  ScheduledJob.objects.create(
944
970
  name="test2",
945
- task="nautobot.extras.jobs.scheduled_job_handler",
946
- job_class="local/test_pass/TestPass",
971
+ task="pass.TestPass",
972
+ job_class="pass.TestPass",
947
973
  interval=JobExecutionType.TYPE_IMMEDIATELY,
948
974
  user=user,
949
- start_time=datetime.now(),
975
+ start_time=timezone.now(),
950
976
  )
951
977
  ScheduledJob.objects.create(
952
978
  name="test3",
953
- task="nautobot.extras.jobs.scheduled_job_handler",
954
- job_class="local/test_pass/TestPass",
979
+ task="pass.TestPass",
980
+ job_class="pass.TestPass",
955
981
  interval=JobExecutionType.TYPE_IMMEDIATELY,
956
982
  user=user,
957
- start_time=datetime.now(),
983
+ start_time=timezone.now(),
958
984
  )
959
985
 
960
986
  def test_only_enabled_is_listed(self):
@@ -964,11 +990,11 @@ class ScheduledJobTestCase(
964
990
  ScheduledJob.objects.create(
965
991
  enabled=False,
966
992
  name="test4",
967
- task="nautobot.extras.jobs.scheduled_job_handler",
968
- job_class="local/test_pass/TestPass",
993
+ task="pass.TestPass",
994
+ job_class="pass.TestPass",
969
995
  interval=JobExecutionType.TYPE_IMMEDIATELY,
970
996
  user=self.user,
971
- start_time=datetime.now(),
997
+ start_time=timezone.now(),
972
998
  )
973
999
 
974
1000
  response = self.client.get(self._get_url("list"))
@@ -982,8 +1008,8 @@ class ScheduledJobTestCase(
982
1008
  ScheduledJob.objects.create(
983
1009
  enabled=True,
984
1010
  name=name,
985
- task="nautobot.extras.jobs.scheduled_job_handler",
986
- job_class="local/test_pass/TestPass",
1011
+ task="pass.TestPass",
1012
+ job_class="pass.TestPass",
987
1013
  interval=JobExecutionType.TYPE_CUSTOM,
988
1014
  user=self.user,
989
1015
  start_time=timezone.now(),
@@ -1014,11 +1040,11 @@ class ScheduledJobTestCase(
1014
1040
  ScheduledJob.objects.create(
1015
1041
  enabled=True,
1016
1042
  name="test11",
1017
- task="nautobot.extras.jobs.scheduled_job_handler",
1018
- job_class="local/test_pass/TestPass",
1043
+ task="pass.TestPass",
1044
+ job_class="pass.TestPass",
1019
1045
  interval=JobExecutionType.TYPE_CUSTOM,
1020
1046
  user=self.user,
1021
- start_time=datetime.now(),
1047
+ start_time=timezone.now(),
1022
1048
  crontab="*/15 9,17 3 * 1-5",
1023
1049
  )
1024
1050
 
@@ -1045,39 +1071,28 @@ class ApprovalQueueTestCase(
1045
1071
 
1046
1072
  def setUp(self):
1047
1073
  super().setUp()
1048
- self.job_model = Job.objects.get_for_class_path("local/test_pass/TestPass")
1049
- self.job_model_2 = Job.objects.get_for_class_path("local/test_fail/TestFail")
1050
- self.job_model_3 = Job.objects.get_for_class_path("local/test_read_only_pass/TestReadOnlyPass")
1074
+ self.job_model = Job.objects.get_for_class_path("dry_run.TestDryRun")
1075
+ self.job_model_2 = Job.objects.get_for_class_path("fail.TestFail")
1051
1076
 
1052
1077
  ScheduledJob.objects.create(
1053
1078
  name="test1",
1054
- task="nautobot.extras.jobs.scheduled_job_handler",
1079
+ task="dry_run.TestDryRun",
1055
1080
  job_model=self.job_model,
1056
1081
  job_class=self.job_model.class_path,
1057
1082
  interval=JobExecutionType.TYPE_IMMEDIATELY,
1058
1083
  user=self.user,
1059
1084
  approval_required=True,
1060
- start_time=datetime.now(),
1085
+ start_time=timezone.now(),
1061
1086
  )
1062
1087
  ScheduledJob.objects.create(
1063
1088
  name="test2",
1064
- task="nautobot.extras.jobs.scheduled_job_handler",
1089
+ task="fail.TestFail",
1065
1090
  job_model=self.job_model_2,
1066
1091
  job_class=self.job_model_2.class_path,
1067
1092
  interval=JobExecutionType.TYPE_IMMEDIATELY,
1068
1093
  user=self.user,
1069
1094
  approval_required=True,
1070
- start_time=datetime.now(),
1071
- )
1072
- ScheduledJob.objects.create(
1073
- name="test3",
1074
- task="nautobot.extras.jobs.scheduled_job_handler",
1075
- job_model=self.job_model_3,
1076
- job_class=self.job_model_3.class_path,
1077
- interval=JobExecutionType.TYPE_IMMEDIATELY,
1078
- user=self.user,
1079
- approval_required=True,
1080
- start_time=datetime.now(),
1095
+ start_time=timezone.now(),
1081
1096
  )
1082
1097
 
1083
1098
  def test_only_approvable_is_listed(self):
@@ -1085,13 +1100,13 @@ class ApprovalQueueTestCase(
1085
1100
 
1086
1101
  ScheduledJob.objects.create(
1087
1102
  name="test4",
1088
- task="nautobot.extras.jobs.scheduled_job_handler",
1103
+ task="pass.TestPass",
1089
1104
  job_model=self.job_model,
1090
1105
  job_class=self.job_model.class_path,
1091
1106
  interval=JobExecutionType.TYPE_IMMEDIATELY,
1092
1107
  user=self.user,
1093
1108
  approval_required=False,
1094
- start_time=datetime.now(),
1109
+ start_time=timezone.now(),
1095
1110
  )
1096
1111
 
1097
1112
  response = self.client.get(self._get_url("list"))
@@ -1229,10 +1244,30 @@ class ApprovalQueueTestCase(
1229
1244
 
1230
1245
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1231
1246
  @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1232
- def test_post_dry_run_success(self, _):
1247
+ def test_post_dry_run_not_supported(self, _):
1248
+ """Request a dry run on a job that doesn't support dryrun."""
1249
+ self.add_permissions("extras.view_scheduledjob")
1250
+ instance = ScheduledJob.objects.filter(name="test2").first()
1251
+ instance.job_model.enabled = True
1252
+ instance.job_model.save()
1253
+ obj_perm = ObjectPermission(name="Test permission", constraints={"pk": instance.job_model.pk}, actions=["run"])
1254
+ obj_perm.save()
1255
+ obj_perm.users.add(self.user)
1256
+ obj_perm.object_types.add(ContentType.objects.get_for_model(Job))
1257
+ data = {"_dry_run": True}
1258
+
1259
+ response = self.client.post(self._get_url("view", instance), data)
1260
+ # Job was not submitted
1261
+ self.assertFalse(JobResult.objects.filter(name=instance.job_model.class_path).exists())
1262
+ self.assertContains(response, "This job does not support dryrun")
1263
+
1264
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1265
+ @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1266
+ @mock.patch("nautobot.extras.models.jobs.JobResult.enqueue_job")
1267
+ def test_post_dry_run_success(self, mock_enqueue_job, _):
1233
1268
  """Successfully request a dry run based on object-based run_job permissions."""
1234
1269
  self.add_permissions("extras.view_scheduledjob")
1235
- instance = self._get_queryset().first()
1270
+ instance = ScheduledJob.objects.filter(name="test1").first()
1236
1271
  instance.job_model.enabled = True
1237
1272
  instance.job_model.save()
1238
1273
  obj_perm = ObjectPermission(name="Test permission", constraints={"pk": instance.job_model.pk}, actions=["run"])
@@ -1241,15 +1276,12 @@ class ApprovalQueueTestCase(
1241
1276
  obj_perm.object_types.add(ContentType.objects.get_for_model(Job))
1242
1277
  data = {"_dry_run": True}
1243
1278
 
1279
+ mock_enqueue_job.side_effect = lambda job_model, *args, **kwargs: JobResult.objects.create(name=job_model.name)
1280
+
1244
1281
  response = self.client.post(self._get_url("view", instance), data)
1245
1282
  # Job was submitted
1246
- self.assertTrue(
1247
- JobResult.objects.filter(name=instance.job_model.class_path).exists(),
1248
- msg=extract_page_body(response.content.decode(response.charset)),
1249
- )
1250
- job_result = JobResult.objects.get(name=instance.job_model.class_path)
1251
- self.assertEqual(job_result.job_model, instance.job_model)
1252
- self.assertEqual(job_result.user, self.user)
1283
+ mock_enqueue_job.assert_called_once()
1284
+ job_result = JobResult.objects.get(name=instance.job_model.name)
1253
1285
  self.assertRedirects(response, reverse("extras:jobresult", kwargs={"pk": job_result.pk}))
1254
1286
 
1255
1287
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
@@ -1423,22 +1455,8 @@ class JobResultTestCase(
1423
1455
 
1424
1456
  @classmethod
1425
1457
  def setUpTestData(cls):
1426
- obj_type = get_job_content_type()
1427
- JobResult.objects.create(
1428
- name="local/test_pass/TestPass",
1429
- task_id=uuid.uuid4(),
1430
- obj_type=obj_type,
1431
- )
1432
- JobResult.objects.create(
1433
- name="local/test_fail/TestFail",
1434
- task_id=uuid.uuid4(),
1435
- obj_type=obj_type,
1436
- )
1437
- JobResult.objects.create(
1438
- name="local/test_read_only_fail/TestReadOnlyFail",
1439
- task_id=uuid.uuid4(),
1440
- obj_type=obj_type,
1441
- )
1458
+ JobResult.objects.create(name="pass.TestPass")
1459
+ JobResult.objects.create(name="fail.TestFail")
1442
1460
 
1443
1461
 
1444
1462
  class JobTestCase(
@@ -1473,8 +1491,8 @@ class JobTestCase(
1473
1491
  cls.run_urls = (
1474
1492
  # Legacy URL (job class path based)
1475
1493
  reverse("extras:job", kwargs={"class_path": cls.test_pass.class_path}),
1476
- # Current URL (job model slug based)
1477
- reverse("extras:job_run", kwargs={"slug": cls.test_pass.slug}),
1494
+ # Current URL (job model pk based)
1495
+ reverse("extras:job_run", kwargs={"pk": cls.test_pass.pk}),
1478
1496
  )
1479
1497
 
1480
1498
  cls.test_required_args = Job.objects.get(job_class_name="TestRequired")
@@ -1484,13 +1502,12 @@ class JobTestCase(
1484
1502
  cls.extra_run_urls = (
1485
1503
  # Legacy URL (job class path based)
1486
1504
  reverse("extras:job", kwargs={"class_path": cls.test_required_args.class_path}),
1487
- # Current URL (job model slug based)
1488
- reverse("extras:job_run", kwargs={"slug": cls.test_required_args.slug}),
1505
+ # Current URL (job model pk based)
1506
+ reverse("extras:job_run", kwargs={"pk": cls.test_required_args.pk}),
1489
1507
  )
1490
1508
 
1491
1509
  # Create an entry for a non-installed Job as well
1492
1510
  cls.test_not_installed = Job(
1493
- source=JobSourceChoices.SOURCE_LOCAL,
1494
1511
  module_name="nonexistent",
1495
1512
  job_class_name="NoSuchJob",
1496
1513
  grouping="Nonexistent Jobs",
@@ -1505,7 +1522,6 @@ class JobTestCase(
1505
1522
  }
1506
1523
 
1507
1524
  cls.form_data = {
1508
- "slug": "custom-job-slug",
1509
1525
  "enabled": True,
1510
1526
  "grouping_override": True,
1511
1527
  "grouping": "Overridden Grouping",
@@ -1513,12 +1529,10 @@ class JobTestCase(
1513
1529
  "name": "Overridden Name",
1514
1530
  "description_override": True,
1515
1531
  "description": "This is an overridden description of a job.",
1516
- "commit_default_override": True,
1517
- "commit_default": False,
1532
+ "dryrun_default_override": True,
1533
+ "dryrun_default": True,
1518
1534
  "hidden_override": True,
1519
1535
  "hidden": False,
1520
- "read_only_override": True,
1521
- "read_only": False,
1522
1536
  "approval_required_override": True,
1523
1537
  "approval_required": True,
1524
1538
  "soft_time_limit_override": True,
@@ -1644,7 +1658,7 @@ class JobTestCase(
1644
1658
 
1645
1659
  for run_url in (
1646
1660
  reverse("extras:job", kwargs={"class_path": self.test_not_installed.class_path}),
1647
- reverse("extras:job_run", kwargs={"slug": self.test_not_installed.slug}),
1661
+ reverse("extras:job_run", kwargs={"pk": self.test_not_installed.pk}),
1648
1662
  ):
1649
1663
  response = self.client.post(run_url, self.data_run_immediately)
1650
1664
  self.assertEqual(response.status_code, 200, msg=run_url)
@@ -1658,14 +1672,14 @@ class JobTestCase(
1658
1672
  self.add_permissions("extras.run_job")
1659
1673
 
1660
1674
  for run_url in (
1661
- reverse("extras:job", kwargs={"class_path": "local/test_fail/TestFail"}),
1662
- reverse("extras:job_run", kwargs={"slug": Job.objects.get(job_class_name="TestFail").slug}),
1675
+ reverse("extras:job", kwargs={"class_path": "fail.TestFail"}),
1676
+ reverse("extras:job_run", kwargs={"pk": Job.objects.get(job_class_name="TestFail").pk}),
1663
1677
  ):
1664
1678
  response = self.client.post(run_url, self.data_run_immediately)
1665
1679
  self.assertEqual(response.status_code, 200, msg=run_url)
1666
1680
  response_body = extract_page_body(response.content.decode(response.charset))
1667
1681
  self.assertIn("Job is not enabled to be run", response_body)
1668
- self.assertFalse(JobResult.objects.filter(name="local/test_fail/TestFail").exists())
1682
+ self.assertFalse(JobResult.objects.filter(name="fail.TestFail").exists())
1669
1683
 
1670
1684
  def test_run_now_missing_args(self):
1671
1685
  self.add_permissions("extras.run_job")
@@ -1693,6 +1707,37 @@ class JobTestCase(
1693
1707
  result = JobResult.objects.latest()
1694
1708
  self.assertRedirects(response, reverse("extras:jobresult", kwargs={"pk": result.pk}))
1695
1709
 
1710
+ @mock.patch("nautobot.extras.jobs.task_queues_as_choices")
1711
+ def test_rerun_job(self, mock_task_queues_as_choices):
1712
+ self.add_permissions("extras.run_job")
1713
+ self.add_permissions("extras.view_jobresult")
1714
+
1715
+ mock_task_queues_as_choices.return_value = [("default", ""), ("queue1", ""), ("uniquequeue", "")]
1716
+ job_celery_kwargs = {
1717
+ "nautobot_job_job_model_id": self.test_required_args.id,
1718
+ "nautobot_job_profile": True,
1719
+ "nautobot_job_user_id": self.user.id,
1720
+ "queue": "uniquequeue",
1721
+ }
1722
+
1723
+ previous_result = JobResult.objects.create(
1724
+ job_model=self.test_required_args,
1725
+ user=self.user,
1726
+ task_kwargs={"var": "456"},
1727
+ celery_kwargs=job_celery_kwargs,
1728
+ )
1729
+
1730
+ run_url = reverse("extras:job_run", kwargs={"pk": self.test_required_args.pk})
1731
+ response = self.client.get(f"{run_url}?kwargs_from_job_result={previous_result.pk!s}")
1732
+ content = extract_page_body(response.content.decode(response.charset))
1733
+
1734
+ self.assertInHTML('<option value="uniquequeue" selected>', content)
1735
+ self.assertInHTML(
1736
+ '<input type="text" name="var" value="456" class="form-control form-control" required placeholder="None" id="id_var">',
1737
+ content,
1738
+ )
1739
+ self.assertInHTML('<input type="hidden" name="_profile" value="True" id="id__profile">', content)
1740
+
1696
1741
  @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1697
1742
  def test_run_later_missing_name(self, _):
1698
1743
  self.add_permissions("extras.run_job")
@@ -1736,7 +1781,7 @@ class JobTestCase(
1736
1781
  data = {
1737
1782
  "_schedule_type": "future",
1738
1783
  "_schedule_name": "test",
1739
- "_schedule_start_time": str(datetime.now() - timedelta(minutes=1)),
1784
+ "_schedule_start_time": str(timezone.now() - timedelta(minutes=1)),
1740
1785
  }
1741
1786
 
1742
1787
  for run_url in self.run_urls:
@@ -1771,25 +1816,6 @@ class JobTestCase(
1771
1816
  self.assertEqual(scheduled.name, "test")
1772
1817
  self.assertEqual(scheduled.start_time, start_time)
1773
1818
 
1774
- @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1775
- def test_run_later_sets_scheduled_job_kwargs_pk(self, _):
1776
- self.add_permissions("extras.run_job")
1777
- self.add_permissions("extras.view_scheduledjob")
1778
-
1779
- start_time = timezone.now() + timedelta(minutes=1)
1780
- data = {
1781
- "_schedule_type": "future",
1782
- "_schedule_name": "test",
1783
- "_schedule_start_time": str(start_time),
1784
- }
1785
-
1786
- for run_url in self.run_urls:
1787
- response = self.client.post(run_url, data)
1788
- self.assertRedirects(response, reverse("extras:scheduledjob_list"))
1789
-
1790
- scheduled = ScheduledJob.objects.last()
1791
- self.assertEqual(scheduled.kwargs["scheduled_job_pk"], str(scheduled.pk))
1792
-
1793
1819
  @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1794
1820
  def test_run_job_with_sensitive_variables_for_future(self, _):
1795
1821
  self.add_permissions("extras.run_job")
@@ -1945,7 +1971,8 @@ class ObjectChangeTestCase(TestCase):
1945
1971
  @classmethod
1946
1972
  def setUpTestData(cls):
1947
1973
  location_type = LocationType.objects.get(name="Campus")
1948
- location = Location(name="Location 1", slug="location-1", location_type=location_type)
1974
+ location_status = Status.objects.get_for_model(Location).first()
1975
+ location = Location(name="Location 1", slug="location-1", location_type=location_type, status=location_status)
1949
1976
  location.save()
1950
1977
 
1951
1978
  # Create three ObjectChanges
@@ -1971,7 +1998,6 @@ class ObjectChangeTestCase(TestCase):
1971
1998
  self.assertHttpStatus(response, 200)
1972
1999
 
1973
2000
 
1974
- @skip(reason="Content Types are BROKEN")
1975
2001
  class RelationshipTestCase(
1976
2002
  ViewTestCases.CreateObjectViewTestCase,
1977
2003
  ViewTestCases.DeleteObjectViewTestCase,
@@ -1983,7 +2009,7 @@ class RelationshipTestCase(
1983
2009
  RequiredRelationshipTestMixin,
1984
2010
  ):
1985
2011
  model = Relationship
1986
- slug_source = "name"
2012
+ slug_source = "label"
1987
2013
  slugify_function = staticmethod(slugify_dashes_to_underscores)
1988
2014
 
1989
2015
  @classmethod
@@ -1994,29 +2020,29 @@ class RelationshipTestCase(
1994
2020
  status = Status.objects.get_for_model(Interface).first()
1995
2021
 
1996
2022
  Relationship(
1997
- name="Device VLANs",
1998
- slug="device-vlans",
2023
+ label="Device VLANs",
2024
+ key="device_vlans",
1999
2025
  type="many-to-many",
2000
2026
  source_type=device_type,
2001
2027
  destination_type=vlan_type,
2002
2028
  ).validated_save()
2003
2029
  Relationship(
2004
- name="Primary VLAN",
2005
- slug="primary-vlan",
2030
+ label="Primary VLAN",
2031
+ key="primary_vlan",
2006
2032
  type="one-to-many",
2007
2033
  source_type=vlan_type,
2008
2034
  destination_type=device_type,
2009
2035
  ).validated_save()
2010
2036
  Relationship(
2011
- name="Primary Interface",
2037
+ label="Primary Interface",
2012
2038
  type="one-to-one",
2013
2039
  source_type=device_type,
2014
2040
  destination_type=interface_type,
2015
2041
  ).validated_save()
2016
2042
 
2017
2043
  cls.form_data = {
2018
- "name": "VLAN-to-Interface",
2019
- "slug": "vlan-to-interface",
2044
+ "label": "VLAN-to-Interface",
2045
+ "key": "vlan_to_interface",
2020
2046
  "type": "many-to-many",
2021
2047
  "source_type": vlan_type.pk,
2022
2048
  "source_label": "Interfaces",
@@ -2081,7 +2107,7 @@ class RelationshipTestCase(
2081
2107
  reverse("ipam:vlan_bulk_edit"),
2082
2108
  data={
2083
2109
  "pk": [str(vlan.id) for vlan in vlans],
2084
- "add_cr_vlans-devices-m2m__source": [str(device_for_association.id)],
2110
+ "add_cr_vlans_devices_m2m__source": [str(device_for_association.id)],
2085
2111
  "_apply": [""],
2086
2112
  },
2087
2113
  follow=True,
@@ -2093,7 +2119,7 @@ class RelationshipTestCase(
2093
2119
  reverse("ipam:vlan_bulk_edit"),
2094
2120
  data={
2095
2121
  "pk": [str(vlan.id) for vlan in vlans],
2096
- "remove_cr_vlans-devices-m2m__source": [str(device_for_association.id)],
2122
+ "remove_cr_vlans_devices_m2m__source": [str(device_for_association.id)],
2097
2123
  "_apply": [""],
2098
2124
  },
2099
2125
  )
@@ -2103,7 +2129,6 @@ class RelationshipTestCase(
2103
2129
  )
2104
2130
 
2105
2131
 
2106
- @skip(reason="Content Types are BROKEN")
2107
2132
  class RelationshipAssociationTestCase(
2108
2133
  # TODO? ViewTestCases.CreateObjectViewTestCase,
2109
2134
  ViewTestCases.DeleteObjectViewTestCase,
@@ -2120,26 +2145,35 @@ class RelationshipAssociationTestCase(
2120
2145
  vlan_type = ContentType.objects.get_for_model(VLAN)
2121
2146
 
2122
2147
  relationship = Relationship(
2123
- name="Device VLANs",
2124
- slug="device-vlans",
2148
+ label="Device VLANs",
2149
+ key="device_vlans",
2125
2150
  type="many-to-many",
2126
2151
  source_type=device_type,
2127
2152
  destination_type=vlan_type,
2128
2153
  )
2154
+ cls.relationship = relationship
2129
2155
  relationship.validated_save()
2130
2156
  manufacturer = Manufacturer.objects.first()
2131
2157
  devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1", slug="device-type-1")
2132
2158
  devicerole = Role.objects.get_for_model(Device).first()
2159
+ devicestatus = Status.objects.get_for_model(Device).first()
2133
2160
  location = Location.objects.first()
2134
2161
  devices = (
2135
- Device.objects.create(name="Device 1", device_type=devicetype, role=devicerole, location=location),
2136
- Device.objects.create(name="Device 2", device_type=devicetype, role=devicerole, location=location),
2137
- Device.objects.create(name="Device 3", device_type=devicetype, role=devicerole, location=location),
2162
+ Device.objects.create(
2163
+ name="Device 1", device_type=devicetype, role=devicerole, location=location, status=devicestatus
2164
+ ),
2165
+ Device.objects.create(
2166
+ name="Device 2", device_type=devicetype, role=devicerole, location=location, status=devicestatus
2167
+ ),
2168
+ Device.objects.create(
2169
+ name="Device 3", device_type=devicetype, role=devicerole, location=location, status=devicestatus
2170
+ ),
2138
2171
  )
2172
+ vlan_status = Status.objects.get_for_model(VLAN).first()
2139
2173
  vlans = (
2140
- VLAN.objects.create(vid=1, name="VLAN 1"),
2141
- VLAN.objects.create(vid=2, name="VLAN 2"),
2142
- VLAN.objects.create(vid=3, name="VLAN 3"),
2174
+ VLAN.objects.create(vid=1, name="VLAN 1", status=vlan_status),
2175
+ VLAN.objects.create(vid=2, name="VLAN 2", status=vlan_status),
2176
+ VLAN.objects.create(vid=3, name="VLAN 3", status=vlan_status),
2143
2177
  )
2144
2178
 
2145
2179
  RelationshipAssociation(
@@ -2164,6 +2198,28 @@ class RelationshipAssociationTestCase(
2164
2198
  destination_id=vlans[2].pk,
2165
2199
  ).validated_save()
2166
2200
 
2201
+ def test_list_objects_with_constrained_permission(self):
2202
+ instance1, instance2 = self.relationship.relationship_associations.all()[:2]
2203
+
2204
+ # Add object-level permission
2205
+ obj_perm = ObjectPermission(
2206
+ name="Test permission",
2207
+ constraints={"pk": instance1.pk},
2208
+ actions=["view"],
2209
+ )
2210
+ obj_perm.save()
2211
+ obj_perm.users.add(self.user)
2212
+ obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
2213
+
2214
+ response = self.client.get(self._get_url("list"))
2215
+ self.assertHttpStatus(response, 200)
2216
+ content = extract_page_body(response.content.decode(response.charset))
2217
+ # TODO: it'd make test failures more readable if we strip the page headers/footers from the content
2218
+ self.assertIn(instance1.source.name, content, msg=content)
2219
+ self.assertIn(instance1.destination.name, content, msg=content)
2220
+ self.assertNotIn(instance2.source.name, content, msg=content)
2221
+ self.assertNotIn(instance2.destination.name, content, msg=content)
2222
+
2167
2223
 
2168
2224
  class StatusTestCase(
2169
2225
  ViewTestCases.CreateObjectViewTestCase,
@@ -2189,10 +2245,10 @@ class StatusTestCase(
2189
2245
 
2190
2246
  cls.csv_data = (
2191
2247
  "name,color,content_types"
2192
- 'test_status1,ffffff,"dcim.device"'
2193
- 'test_status2,ffffff,"dcim.device"'
2194
- 'test_status3,ffffff,"dcim.device"'
2195
- 'test_status4,ffffff,"dcim.device"'
2248
+ "test_status1,ffffff,dcim.device"
2249
+ 'test_status2,ffffff,"dcim.device,dcim.location"'
2250
+ "test_status3,ffffff,dcim.device"
2251
+ "test_status4,ffffff,dcim.device"
2196
2252
  )
2197
2253
 
2198
2254
  cls.bulk_edit_data = {
@@ -2214,10 +2270,10 @@ class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
2214
2270
  }
2215
2271
 
2216
2272
  cls.csv_data = (
2217
- "name,slug,color,description",
2218
- "Tag 4,tag-4,ff0000,Fourth tag",
2219
- "Tag 5,tag-5,00ff00,Fifth tag",
2220
- "Tag 6,tag-6,0000ff,Sixth tag",
2273
+ "name,slug,color,description,content_types",
2274
+ "Tag 4,tag-4,ff0000,Fourth tag,dcim.device",
2275
+ 'Tag 5,tag-5,00ff00,Fifth tag,"dcim.device,dcim.location"',
2276
+ "Tag 6,tag-6,0000ff,Sixth tag,dcim.location",
2221
2277
  )
2222
2278
 
2223
2279
  cls.bulk_edit_data = {
@@ -2358,7 +2414,7 @@ class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
2358
2414
 
2359
2415
  cls.csv_data = (
2360
2416
  "name,weight,color,content_types,description",
2361
- 'test_role1,1000,ffffff,"dcim.device",A Role',
2417
+ "test_role1,1000,ffffff,dcim.device,A Role",
2362
2418
  'test_role2,200,ffffff,"dcim.device,dcim.rack",A Role',
2363
2419
  'test_role3,100,ffffff,"dcim.device,ipam.prefix",A Role',
2364
2420
  'test_role4,50,ffffff,"ipam.ipaddress,ipam.vlan",A Role',