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
@@ -162,7 +162,7 @@ class ComputedFieldFilterSet(BaseFilterSet):
162
162
  model = ComputedField
163
163
  fields = (
164
164
  "content_type",
165
- "slug",
165
+ "key",
166
166
  "template",
167
167
  "fallback_value",
168
168
  "weight",
@@ -553,7 +553,6 @@ class JobFilterSet(BaseFilterSet, CustomFieldModelFilterSetMixin):
553
553
  q = SearchFilter(
554
554
  filter_predicates={
555
555
  "name": "icontains",
556
- "slug": "icontains",
557
556
  "grouping": "icontains",
558
557
  "description": "icontains",
559
558
  },
@@ -563,17 +562,15 @@ class JobFilterSet(BaseFilterSet, CustomFieldModelFilterSetMixin):
563
562
  model = Job
564
563
  fields = [
565
564
  "id",
566
- "source",
567
565
  "module_name",
568
566
  "job_class_name",
569
- "slug",
570
567
  "name",
571
568
  "grouping",
572
569
  "installed",
573
570
  "enabled",
574
571
  "has_sensitive_variables",
575
572
  "approval_required",
576
- "commit_default",
573
+ "dryrun_default",
577
574
  "hidden",
578
575
  "read_only",
579
576
  "is_job_hook_receiver",
@@ -584,9 +581,8 @@ class JobFilterSet(BaseFilterSet, CustomFieldModelFilterSetMixin):
584
581
  "name_override",
585
582
  "approval_required_override",
586
583
  "description_override",
587
- "commit_default_override",
584
+ "dryrun_default_override",
588
585
  "hidden_override",
589
- "read_only_override",
590
586
  "soft_time_limit_override",
591
587
  "time_limit_override",
592
588
  "has_sensitive_variables_override",
@@ -600,8 +596,9 @@ class JobHookFilterSet(BaseFilterSet):
600
596
  choices=ChangeLoggedModelsQuery().get_choices,
601
597
  )
602
598
  job = NaturalKeyOrPKMultipleChoiceFilter(
599
+ to_field_name="name",
603
600
  queryset=Job.objects.all(),
604
- label="Job (slug or ID)",
601
+ label="Job (name or ID)",
605
602
  )
606
603
 
607
604
  class Meta:
@@ -626,21 +623,21 @@ class JobResultFilterSet(BaseFilterSet, CustomFieldModelFilterSetMixin):
626
623
  },
627
624
  )
628
625
  job_model = NaturalKeyOrPKMultipleChoiceFilter(
626
+ to_field_name="name",
629
627
  queryset=Job.objects.all(),
630
- label="Job (ID or slug)",
628
+ label="Job (name or ID)",
631
629
  )
632
630
  job_model_id = django_filters.ModelMultipleChoiceFilter(
633
631
  queryset=Job.objects.all(),
634
632
  label="Job (ID) - Deprecated (use job_model filter)",
635
633
  )
636
- obj_type = ContentTypeFilter()
637
634
  date_created = django_filters.DateTimeFilter()
638
635
  date_done = django_filters.DateTimeFilter()
639
636
  status = django_filters.MultipleChoiceFilter(choices=JobResultStatusChoices, null_value=None)
640
637
 
641
638
  class Meta:
642
639
  model = JobResult
643
- fields = ["id", "date_created", "date_done", "name", "status", "user", "obj_type"]
640
+ fields = ["id", "date_created", "date_done", "name", "status", "user"]
644
641
 
645
642
 
646
643
  class JobLogEntryFilterSet(BaseFilterSet):
@@ -666,9 +663,9 @@ class ScheduledJobFilterSet(BaseFilterSet):
666
663
  },
667
664
  )
668
665
  job_model = NaturalKeyOrPKMultipleChoiceFilter(
669
- field_name="job_model__slug",
666
+ to_field_name="name",
670
667
  queryset=Job.objects.all(),
671
- label="Job (ID or slug)",
668
+ label="Job (name or ID)",
672
669
  )
673
670
  job_model_id = django_filters.ModelMultipleChoiceFilter(
674
671
  queryset=Job.objects.all(),
@@ -697,7 +694,11 @@ class JobButtonFilterSet(BaseFilterSet):
697
694
  },
698
695
  )
699
696
  content_types = ContentTypeFilter()
700
- job = NaturalKeyOrPKMultipleChoiceFilter(queryset=Job.objects.all(), label="Job (slug or ID)")
697
+ job = NaturalKeyOrPKMultipleChoiceFilter(
698
+ to_field_name="name",
699
+ queryset=Job.objects.all(),
700
+ label="Job (name or ID)",
701
+ )
701
702
 
702
703
  class Meta:
703
704
  model = JobButton
@@ -792,7 +793,7 @@ class ObjectChangeFilterSet(BaseFilterSet):
792
793
  class RelationshipFilterSet(BaseFilterSet):
793
794
  q = SearchFilter(
794
795
  filter_predicates={
795
- "name": "icontains",
796
+ "label": "icontains",
796
797
  "description": "icontains",
797
798
  }
798
799
  )
@@ -804,15 +805,15 @@ class RelationshipFilterSet(BaseFilterSet):
804
805
 
805
806
  class Meta:
806
807
  model = Relationship
807
- fields = ["id", "name", "slug", "type", "source_type", "destination_type"]
808
+ fields = ["id", "label", "key", "type", "source_type", "destination_type"]
808
809
 
809
810
 
810
811
  class RelationshipAssociationFilterSet(BaseFilterSet):
811
812
  relationship = django_filters.ModelMultipleChoiceFilter(
812
- field_name="relationship__slug",
813
+ field_name="relationship__key",
813
814
  queryset=Relationship.objects.all(),
814
- to_field_name="slug",
815
- label="Relationship (slug)",
815
+ to_field_name="key",
816
+ label="Relationship (key)",
816
817
  )
817
818
  source_type = ContentTypeMultipleChoiceFilter(choices=FeatureQuery("relationships").get_choices, conjoined=False)
818
819
  destination_type = ContentTypeMultipleChoiceFilter(
@@ -254,10 +254,10 @@ class RelationshipModelFilterSetMixin(django_filters.FilterSet):
254
254
  peer_side = RelationshipSideChoices.OPPOSITE[side]
255
255
 
256
256
  # If this model is on the "source" side of the relationship, then the field will be named
257
- # "cr_<relationship-slug>__destination" since it's used to pick the destination object(s).
258
- # If we're on the "destination" side, the field will be "cr_<relationship-slug>__source".
259
- # For a symmetric relationship, both sides are "peer", so the field will be "cr_<relationship-slug>__peer"
260
- field_name = f"cr_{relationship.slug}__{peer_side}"
257
+ # "cr_<relationship_key>__destination" since it's used to pick the destination object(s).
258
+ # If we're on the "destination" side, the field will be "cr_<relationship_key>__source".
259
+ # For a symmetric relationship, both sides are "peer", so the field will be "cr_<relationship_key>__peer"
260
+ field_name = f"cr_{relationship.key}__{peer_side}"
261
261
 
262
262
  if field_name in self.relationships:
263
263
  # This is a symmetric relationship that we already processed from the opposing "initial_side".
@@ -6,7 +6,6 @@ from django.core.exceptions import ValidationError
6
6
  from django.db.models.fields import TextField
7
7
  from django.forms import ModelMultipleChoiceField, inlineformset_factory
8
8
  from django.urls.base import reverse
9
- from django.utils.safestring import mark_safe
10
9
 
11
10
  from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
12
11
  from nautobot.core.forms import (
@@ -19,10 +18,7 @@ from nautobot.core.forms import (
19
18
  ColorSelect,
20
19
  CommentField,
21
20
  CSVContentTypeField,
22
- CSVModelChoiceField,
23
21
  CSVModelForm,
24
- CSVMultipleChoiceField,
25
- CSVMultipleContentTypeField,
26
22
  DateTimePicker,
27
23
  DynamicModelChoiceField,
28
24
  DynamicModelMultipleChoiceField,
@@ -113,7 +109,6 @@ __all__ = (
113
109
  "ExportTemplateForm",
114
110
  "ExportTemplateFilterForm",
115
111
  "GitRepositoryForm",
116
- "GitRepositoryCSVForm",
117
112
  "GitRepositoryBulkEditForm",
118
113
  "GitRepositoryFilterForm",
119
114
  "GraphQLQueryForm",
@@ -139,21 +134,17 @@ __all__ = (
139
134
  "RelationshipFilterForm",
140
135
  "RelationshipAssociationFilterForm",
141
136
  "RoleBulkEditForm",
142
- "RoleCSVForm",
143
137
  "RoleForm",
144
138
  "ScheduledJobFilterForm",
145
139
  "SecretForm",
146
- "SecretCSVForm",
147
140
  "SecretFilterForm",
148
141
  "SecretsGroupForm",
149
142
  "SecretsGroupFilterForm",
150
143
  "SecretsGroupAssociationFormSet",
151
144
  "StatusForm",
152
- "StatusCSVForm",
153
145
  "StatusFilterForm",
154
146
  "StatusBulkEditForm",
155
147
  "TagForm",
156
- "TagCSVForm",
157
148
  "TagFilterForm",
158
149
  "TagBulkEditForm",
159
150
  "WebhookForm",
@@ -172,7 +163,9 @@ class ComputedFieldForm(BootstrapMixin, forms.ModelForm):
172
163
  required=True,
173
164
  label="Content Type",
174
165
  )
175
- slug = SlugField(
166
+ key = SlugField(
167
+ label="Key",
168
+ max_length=50,
176
169
  slug_source="label",
177
170
  help_text="Internal name of this field. Please use underscores rather than dashes.",
178
171
  )
@@ -189,7 +182,7 @@ class ComputedFieldForm(BootstrapMixin, forms.ModelForm):
189
182
  fields = (
190
183
  "content_type",
191
184
  "label",
192
- "slug",
185
+ "key",
193
186
  "description",
194
187
  "template",
195
188
  "fallback_value",
@@ -197,6 +190,12 @@ class ComputedFieldForm(BootstrapMixin, forms.ModelForm):
197
190
  "advanced_ui",
198
191
  )
199
192
 
193
+ def __init__(self, *args, **kwargs):
194
+ super().__init__(*args, **kwargs)
195
+
196
+ if self.instance and self.instance.present_in_database:
197
+ self.fields["key"].widget.attrs["readonly"] = True
198
+
200
199
 
201
200
  class ComputedFieldFilterForm(BootstrapMixin, forms.Form):
202
201
  model = ComputedField
@@ -401,9 +400,21 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
401
400
  "validation_regex",
402
401
  )
403
402
 
403
+ def __init__(self, *args, **kwargs):
404
+ super().__init__(*args, **kwargs)
405
+
406
+ if self.instance and self.instance.present_in_database:
407
+ self.fields["key"].widget.attrs["readonly"] = True
408
+
404
409
 
405
410
  class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelFormMixin):
406
- """Base class for CSV export of models that support custom fields."""
411
+ """
412
+ Base class for CSV/JSON/YAML import of models that support custom fields.
413
+
414
+ TODO: The class name is a misnomer; as of 2.0 this class is **not** used for any CSV imports,
415
+ as that's now handled by the REST API. However it is still used when importing component-templates as
416
+ part of a JSON/YAML DeviceType import.
417
+ """
407
418
 
408
419
  def _append_customfield_fields(self):
409
420
  # Append form fields
@@ -611,36 +622,24 @@ class GitRepositoryForm(BootstrapMixin, RelationshipModelFormMixin):
611
622
  "tags",
612
623
  ]
613
624
 
614
- def clean(self):
615
- super().clean()
616
-
617
- # set dryrun after a successful clean
618
- if "_dryrun_create" in self.data or "_dryrun_update" in self.data:
619
- self.instance.set_dryrun()
625
+ def __init__(self, *args, **kwargs):
626
+ super().__init__(*args, **kwargs)
620
627
 
628
+ if self.instance and self.instance.present_in_database:
629
+ self.fields["slug"].widget.attrs["readonly"] = True
621
630
 
622
- class GitRepositoryCSVForm(CSVModelForm):
623
- secrets_group = CSVModelChoiceField(
624
- queryset=SecretsGroup.objects.all(),
625
- to_field_name="name",
626
- required=False,
627
- help_text="Secrets group for repository access (if any)",
628
- )
631
+ def save(self, commit=True):
632
+ instance = super().save(commit=commit)
629
633
 
630
- class Meta:
631
- model = GitRepository
632
- fields = GitRepository.csv_headers
634
+ # TODO(jathan): Move sync() call out of the form and into the view. However, in v2 UI this
635
+ # probably just goes away since UI views will be making API calls. For now, the user is
636
+ # magically stored on the instance by the view code.
637
+ if commit:
638
+ # Set dryrun if that button was clicked in the UI, otherwise perform a normal sync.
639
+ dry_run = "_dryrun_create" in self.data or "_dryrun_update" in self.data
640
+ instance.sync(user=instance.user, dry_run=dry_run)
633
641
 
634
- def __init__(self, *args, **kwargs):
635
- super().__init__(*args, **kwargs)
636
- self.fields["provided_contents"] = CSVMultipleChoiceField(
637
- choices=get_git_datasource_content_choices(),
638
- required=False,
639
- help_text=mark_safe(
640
- "The data types this repository provides. Multiple values must be comma-separated and wrapped in "
641
- 'double quotes (e.g. <code>"extras.job,extras.configcontext"</code>).'
642
- ),
643
- )
642
+ return instance
644
643
 
645
644
 
646
645
  class GitRepositoryBulkEditForm(NautobotBulkEditForm):
@@ -723,11 +722,11 @@ class JobForm(BootstrapMixin, forms.Form):
723
722
  controlled by the job definition. See `nautobot.extras.jobs.BaseJob.as_form`
724
723
  """
725
724
 
726
- _commit = forms.BooleanField(
725
+ _profile = forms.BooleanField(
727
726
  required=False,
728
- initial=True,
729
- label="Commit changes",
730
- help_text="Commit changes to the database (uncheck for a dry-run)",
727
+ initial=False,
728
+ label="Profile job execution",
729
+ help_text="Profiles the job execution using cProfile and outputs a report to /tmp/",
731
730
  )
732
731
  _task_queue = forms.ChoiceField(
733
732
  required=False,
@@ -738,27 +737,16 @@ class JobForm(BootstrapMixin, forms.Form):
738
737
  def __init__(self, *args, **kwargs):
739
738
  super().__init__(*args, **kwargs)
740
739
 
741
- # Move _task_queue and _commit to the end of the form
742
- task_queue = self.fields.pop("_task_queue")
743
- self.fields["_task_queue"] = task_queue
744
- commit = self.fields.pop("_commit")
745
- self.fields["_commit"] = commit
746
-
747
- @property
748
- def requires_input(self):
749
- """
750
- A boolean indicating whether the form requires user input (ignore the _commit field).
751
- """
752
- return bool(len(self.fields) > 1)
740
+ # Move special fields to the end of the form
741
+ for field in ["_task_queue", "_profile"]:
742
+ value = self.fields.pop(field)
743
+ self.fields[field] = value
753
744
 
754
745
 
755
746
  class JobEditForm(NautobotModelForm):
756
- slug = SlugField()
757
-
758
747
  class Meta:
759
748
  model = Job
760
749
  fields = [
761
- "slug",
762
750
  "enabled",
763
751
  "name_override",
764
752
  "name",
@@ -766,12 +754,10 @@ class JobEditForm(NautobotModelForm):
766
754
  "grouping",
767
755
  "description_override",
768
756
  "description",
769
- "commit_default_override",
770
- "commit_default",
757
+ "dryrun_default_override",
758
+ "dryrun_default",
771
759
  "hidden_override",
772
760
  "hidden",
773
- "read_only_override",
774
- "read_only",
775
761
  "approval_required_override",
776
762
  "approval_required",
777
763
  "soft_time_limit_override",
@@ -810,7 +796,7 @@ class JobFilterForm(BootstrapMixin, forms.Form):
810
796
  has_sensitive_variables = forms.NullBooleanField(
811
797
  required=False, widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES)
812
798
  )
813
- commit_default = forms.NullBooleanField(required=False, widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES))
799
+ dryrun_default = forms.NullBooleanField(required=False, widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES))
814
800
  hidden = forms.NullBooleanField(
815
801
  initial=False,
816
802
  required=False,
@@ -1138,7 +1124,12 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
1138
1124
 
1139
1125
 
1140
1126
  class RelationshipForm(BootstrapMixin, forms.ModelForm):
1141
- slug = SlugField(help_text="Internal name of this relationship. Please use underscores rather than dashes.")
1127
+ key = SlugField(
1128
+ help_text="Internal name of this relationship. Please use underscores rather than dashes.",
1129
+ label="Key",
1130
+ max_length=50,
1131
+ slug_source="label",
1132
+ )
1142
1133
  source_type = forms.ModelChoiceField(
1143
1134
  queryset=ContentType.objects.filter(FeatureQuery("relationships").get_query()).order_by("app_label", "model"),
1144
1135
  help_text="The source object type to which this relationship applies.",
@@ -1161,8 +1152,8 @@ class RelationshipForm(BootstrapMixin, forms.ModelForm):
1161
1152
  class Meta:
1162
1153
  model = Relationship
1163
1154
  fields = [
1164
- "name",
1165
- "slug",
1155
+ "label",
1156
+ "key",
1166
1157
  "description",
1167
1158
  "type",
1168
1159
  "required_on",
@@ -1177,6 +1168,12 @@ class RelationshipForm(BootstrapMixin, forms.ModelForm):
1177
1168
  "destination_filter",
1178
1169
  ]
1179
1170
 
1171
+ def __init__(self, *args, **kwargs):
1172
+ super().__init__(*args, **kwargs)
1173
+
1174
+ if self.instance and self.instance.present_in_database:
1175
+ self.fields["key"].widget.attrs["readonly"] = True
1176
+
1180
1177
  def save(self, commit=True):
1181
1178
  # TODO add support for owner when a CR is created in the UI
1182
1179
  obj = super().save(commit)
@@ -1203,7 +1200,7 @@ class RelationshipAssociationFilterForm(BootstrapMixin, forms.Form):
1203
1200
 
1204
1201
  relationship = DynamicModelMultipleChoiceField(
1205
1202
  queryset=Relationship.objects.all(),
1206
- to_field_name="slug",
1203
+ to_field_name="key",
1207
1204
  required=False,
1208
1205
  )
1209
1206
 
@@ -1249,28 +1246,6 @@ class RoleBulkEditForm(NautobotBulkEditForm):
1249
1246
  nullable_fields = ["weight"]
1250
1247
 
1251
1248
 
1252
- class RoleCSVForm(CustomFieldModelCSVForm):
1253
- """Generic CSV bulk import form for `Role` objects."""
1254
-
1255
- content_types = CSVMultipleContentTypeField(
1256
- queryset=RoleModelsQuery().as_queryset(),
1257
- choices_as_strings=True,
1258
- help_text=mark_safe(
1259
- "The object types to which this role applies. Multiple values "
1260
- "must be comma-separated and wrapped in double quotes. (e.g. "
1261
- '<code>"dcim.device,dcim.rack"</code>)'
1262
- ),
1263
- label="Content type(s)",
1264
- )
1265
-
1266
- class Meta:
1267
- model = Role
1268
- fields = Role.csv_headers
1269
- help_texts = {
1270
- "color": mark_safe("RGB color in hexadecimal (e.g. <code>00ff00</code>)"),
1271
- }
1272
-
1273
-
1274
1249
  #
1275
1250
  # Secrets
1276
1251
  #
@@ -1298,12 +1273,6 @@ class SecretForm(NautobotModelForm):
1298
1273
  ]
1299
1274
 
1300
1275
 
1301
- class SecretCSVForm(CustomFieldModelCSVForm):
1302
- class Meta:
1303
- model = Secret
1304
- fields = Secret.csv_headers
1305
-
1306
-
1307
1276
  def provider_choices_with_blank():
1308
1277
  return add_blank_choice(sorted([(slug, provider.name) for slug, provider in registry["secrets_providers"].items()]))
1309
1278
 
@@ -1363,28 +1332,6 @@ class StatusForm(NautobotModelForm):
1363
1332
  fields = ["name", "description", "content_types", "color"]
1364
1333
 
1365
1334
 
1366
- class StatusCSVForm(CustomFieldModelCSVForm):
1367
- """Generic CSV bulk import form for `Status` objects."""
1368
-
1369
- content_types = CSVMultipleContentTypeField(
1370
- feature="statuses",
1371
- choices_as_strings=True,
1372
- help_text=mark_safe(
1373
- "The object types to which this status applies. Multiple values "
1374
- "must be comma-separated and wrapped in double quotes. (e.g. "
1375
- '<code>"dcim.device,dcim.rack"</code>)'
1376
- ),
1377
- label="Content type(s)",
1378
- )
1379
-
1380
- class Meta:
1381
- model = Status
1382
- fields = Status.csv_headers
1383
- help_texts = {
1384
- "color": mark_safe("RGB color in hexadecimal (e.g. <code>00ff00</code>)"),
1385
- }
1386
-
1387
-
1388
1335
  class StatusFilterForm(NautobotFilterForm):
1389
1336
  """Filtering/search form for `Status` objects."""
1390
1337
 
@@ -1437,17 +1384,6 @@ class TagForm(NautobotModelForm):
1437
1384
  return data
1438
1385
 
1439
1386
 
1440
- class TagCSVForm(CustomFieldModelCSVForm):
1441
- slug = SlugField()
1442
-
1443
- class Meta:
1444
- model = Tag
1445
- fields = Tag.csv_headers
1446
- help_texts = {
1447
- "color": mark_safe("RGB color in hexadecimal (e.g. <code>00ff00</code>)"),
1448
- }
1449
-
1450
-
1451
1387
  class TagFilterForm(NautobotFilterForm):
1452
1388
  model = Tag
1453
1389
  q = forms.CharField(required=False, label="Search")
@@ -8,8 +8,6 @@ from django.db.models import Q
8
8
  from nautobot.core.forms import (
9
9
  BulkEditForm,
10
10
  CommentField,
11
- CSVModelChoiceField,
12
- CSVModelForm,
13
11
  DynamicModelChoiceField,
14
12
  DynamicModelMultipleChoiceField,
15
13
  )
@@ -44,7 +42,6 @@ __all__ = (
44
42
  "RelationshipModelFormMixin",
45
43
  "StatusModelBulkEditFormMixin",
46
44
  "StatusModelFilterFormMixin",
47
- "StatusModelCSVFormMixin",
48
45
  "TagsBulkEditFormMixin",
49
46
  # 2.0 TODO: remove the below deprecated aliases
50
47
  "AddRemoveTagsForm",
@@ -53,9 +50,7 @@ __all__ = (
53
50
  "CustomFieldModelForm",
54
51
  "RelationshipModelForm",
55
52
  "RoleModelBulkEditFormMixin",
56
- "RoleModelCSVFormMixin",
57
53
  "RoleModelFilterFormMixin",
58
- "RoleRequiredRoleModelCSVFormMixin",
59
54
  "StatusBulkEditFormMixin",
60
55
  "StatusFilterFormMixin",
61
56
  )
@@ -214,10 +209,10 @@ class RelationshipModelBulkEditFormMixin(BulkEditForm):
214
209
  peer_side = RelationshipSideChoices.OPPOSITE[side]
215
210
 
216
211
  # If this model is on the "source" side of the relationship, then the field will be named
217
- # "cr_<relationship-slug>__destination" since it's used to pick the destination object(s).
218
- # If we're on the "destination" side, the field will be "cr_<relationship-slug>__source".
219
- # For a symmetric relationship, both sides are "peer", so the field will be "cr_<relationship-slug>__peer"
220
- field_name = f"cr_{relationship.slug}__{peer_side}"
212
+ # "cr_<relationship_key>__destination" since it's used to pick the destination object(s).
213
+ # If we're on the "destination" side, the field will be "cr_<relationship_key>__source".
214
+ # For a symmetric relationship, both sides are "peer", so the field will be "cr_<relationship_key>__peer"
215
+ field_name = f"cr_{relationship.key}__{peer_side}"
221
216
 
222
217
  if field_name in self.relationships:
223
218
  # This is a symmetric relationship that we already processed from the opposing "initial_side".
@@ -257,7 +252,7 @@ class RelationshipModelBulkEditFormMixin(BulkEditForm):
257
252
  for side, relationships_data in instance_relationships.items():
258
253
  peer_side = RelationshipSideChoices.OPPOSITE[side]
259
254
  for relationship, relationshipassociation_queryset in relationships_data.items():
260
- field_name = f"cr_{relationship.slug}__{peer_side}"
255
+ field_name = f"cr_{relationship.key}__{peer_side}"
261
256
  logger.debug(
262
257
  "Processing relationship %s %s (field %s) for instance %s",
263
258
  relationship,
@@ -347,22 +342,25 @@ class RelationshipModelBulkEditFormMixin(BulkEditForm):
347
342
  def clean(self):
348
343
  # Get any initial required relationship objects errors (i.e. non-existent required objects)
349
344
  required_objects_errors = self.model.required_related_objects_errors(output_for="ui")
350
- already_invalidated_slugs = []
345
+ already_invalidated_keys = []
351
346
  for field, errors in required_objects_errors.items():
352
347
  self.add_error(None, errors)
353
- relationship_slug = field.split("__")[0][3:]
354
- already_invalidated_slugs.append(relationship_slug)
348
+ # rindex() find the last occurrence of "__" which is
349
+ # guaranteed to be cr_{key}__source, cr_{key}__destination, or cr_{key}__peer
350
+ # regardless of how {key} is formatted
351
+ relationship_key = field[: field.rindex("__")][3:]
352
+ already_invalidated_keys.append(relationship_key)
355
353
 
356
354
  required_relationships = []
357
355
  # The following query excludes already invalidated relationships (this happened above
358
356
  # by checking for the existence of required objects
359
357
  # with the call to self.Meta().model.required_related_objects_errors(output_for="ui"))
360
358
  for relationship in Relationship.objects.get_required_for_model(self.model).exclude(
361
- slug__in=already_invalidated_slugs
359
+ key__in=already_invalidated_keys
362
360
  ):
363
361
  required_relationships.append(
364
362
  {
365
- "slug": relationship.slug,
363
+ "key": relationship.key,
366
364
  "required_side": RelationshipSideChoices.OPPOSITE[relationship.required_on],
367
365
  "relationship": relationship,
368
366
  }
@@ -371,7 +369,7 @@ class RelationshipModelBulkEditFormMixin(BulkEditForm):
371
369
  # Get difference of add/remove objects for each required relationship:
372
370
  required_relationships_to_check = []
373
371
  for required_relationship in required_relationships:
374
- required_field = f"cr_{required_relationship['slug']}__{required_relationship['required_side']}"
372
+ required_field = f"cr_{required_relationship['key']}__{required_relationship['required_side']}"
375
373
 
376
374
  add_list = []
377
375
  if f"add_{required_field}" in self.cleaned_data:
@@ -456,10 +454,10 @@ class RelationshipModelFormMixin(forms.ModelForm):
456
454
  for relationship, queryset in relationships.items():
457
455
  peer_side = RelationshipSideChoices.OPPOSITE[side]
458
456
  # If this model is on the "source" side of the relationship, then the field will be named
459
- # cr_<relationship-slug>__destination since it's used to pick the destination object(s).
460
- # If we're on the "destination" side, the field will be cr_<relationship-slug>__source.
461
- # For a symmetric relationship, both sides are "peer", so the field will be cr_<relationship-slug>__peer
462
- field_name = f"cr_{relationship.slug}__{peer_side}"
457
+ # cr_<relationship_key>__destination since it's used to pick the destination object(s).
458
+ # If we're on the "destination" side, the field will be cr_<relationship_key>__source.
459
+ # For a symmetric relationship, both sides are "peer", so the field will be cr_<relationship_key>__peer
460
+ field_name = f"cr_{relationship.key}__{peer_side}"
463
461
  self.fields[field_name] = relationship.to_form_field(side=side)
464
462
 
465
463
  # HTML5 validation for required relationship field:
@@ -501,7 +499,7 @@ class RelationshipModelFormMixin(forms.ModelForm):
501
499
  for relationship in relationships:
502
500
  # The form field name reflects what it provides, i.e. the peer object(s) to link via this relationship.
503
501
  peer_side = RelationshipSideChoices.OPPOSITE[side]
504
- field_name = f"cr_{relationship.slug}__{peer_side}"
502
+ field_name = f"cr_{relationship.key}__{peer_side}"
505
503
 
506
504
  # Is the form trying to set this field (create/update a RelationshipAssociation(s))?
507
505
  # If not (that is, clearing the field / deleting RelationshipAssociation(s)), we don't need to check.
@@ -672,10 +670,10 @@ class RelationshipModelFilterFormMixin(forms.Form):
672
670
  peer_side = RelationshipSideChoices.OPPOSITE[side]
673
671
 
674
672
  # If this model is on the "source" side of the relationship, then the field will be named
675
- # "cr_<relationship-slug>__destination" since it's used to pick the destination object(s).
676
- # If we're on the "destination" side, the field will be "cr_<relationship-slug>__source".
677
- # For a symmetric relationship, both sides are "peer", so the field will be "cr_<relationship-slug>__peer"
678
- field_name = f"cr_{relationship.slug}__{peer_side}"
673
+ # "cr_<relationship_key>__destination" since it's used to pick the destination object(s).
674
+ # If we're on the "destination" side, the field will be "cr_<relationship_key>__source".
675
+ # For a symmetric relationship, both sides are "peer", so the field will be "cr_<relationship_key>__peer"
676
+ field_name = f"cr_{relationship.key}__{peer_side}"
679
677
 
680
678
  if field_name in self.relationships:
681
679
  # This is a symmetric relationship that we already processed from the opposing "initial_side".
@@ -720,22 +718,6 @@ class RoleModelFilterFormMixin(forms.Form):
720
718
  self.order_fields(self.field_order) # Reorder fields again
721
719
 
722
720
 
723
- class RoleModelCSVFormMixin(CSVModelForm):
724
- """Mixin to add a non-required `role` choice field to CSV import forms."""
725
-
726
- role = CSVModelChoiceField(
727
- queryset=Role.objects.all(), to_field_name="name", required=False, help_text="Assigned role"
728
- )
729
-
730
-
731
- class RoleRequiredRoleModelCSVFormMixin(CSVModelForm):
732
- """Mixin to add a required `role` choice field to CSV import forms."""
733
-
734
- role = CSVModelChoiceField(
735
- queryset=Role.objects.all(), to_field_name="name", required=True, help_text="Assigned role"
736
- )
737
-
738
-
739
721
  class StatusModelBulkEditFormMixin(forms.Form):
740
722
  """Mixin to add non-required `status` choice field to forms."""
741
723
 
@@ -765,16 +747,6 @@ class StatusModelFilterFormMixin(forms.Form):
765
747
  self.order_fields(self.field_order) # Reorder fields again
766
748
 
767
749
 
768
- class StatusModelCSVFormMixin(CSVModelForm):
769
- """Mixin to add a required `status` choice field to CSV import forms."""
770
-
771
- status = CSVModelChoiceField(
772
- queryset=Status.objects.all(),
773
- to_field_name="name",
774
- help_text="Operational status",
775
- )
776
-
777
-
778
750
  class TagsBulkEditFormMixin(forms.Form):
779
751
  def __init__(self, *args, **kwargs):
780
752
  super().__init__(*args, **kwargs)