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
@@ -24,123 +24,126 @@ from nautobot.extras.models import Relationship, RelationshipAssociation, Status
24
24
 
25
25
 
26
26
  class RelationshipBaseTest(TestCase):
27
- def setUp(self):
28
- self.location_ct = ContentType.objects.get_for_model(Location)
29
- self.rack_ct = ContentType.objects.get_for_model(Rack)
30
- self.vlan_ct = ContentType.objects.get_for_model(VLAN)
31
-
32
- self.locations = Location.objects.all()[:5]
33
-
34
- self.racks = [
35
- Rack.objects.create(name="Rack A", location=self.locations[0]),
36
- Rack.objects.create(name="Rack B", location=self.locations[1]),
37
- Rack.objects.create(name="Rack C", location=self.locations[2]),
27
+ @classmethod
28
+ def setUpTestData(cls):
29
+ cls.location_ct = ContentType.objects.get_for_model(Location)
30
+ cls.rack_ct = ContentType.objects.get_for_model(Rack)
31
+ cls.vlan_ct = ContentType.objects.get_for_model(VLAN)
32
+
33
+ cls.locations = Location.objects.all()[:5]
34
+
35
+ cls.rack_status = Status.objects.get_for_model(Rack).first()
36
+ cls.racks = [
37
+ Rack.objects.create(name="Rack A", location=cls.locations[0], status=cls.rack_status),
38
+ Rack.objects.create(name="Rack B", location=cls.locations[1], status=cls.rack_status),
39
+ Rack.objects.create(name="Rack C", location=cls.locations[2], status=cls.rack_status),
38
40
  ]
39
41
 
40
- self.vlans = [
41
- VLAN.objects.create(name="VLAN A", vid=100, location=self.locations[0]),
42
- VLAN.objects.create(name="VLAN B", vid=100, location=self.locations[1]),
43
- VLAN.objects.create(name="VLAN C", vid=100, location=self.locations[2]),
42
+ cls.vlan_status = Status.objects.get_for_model(VLAN).first()
43
+ cls.vlans = [
44
+ VLAN.objects.create(name="VLAN A", vid=100, location=cls.locations[0], status=cls.vlan_status),
45
+ VLAN.objects.create(name="VLAN B", vid=100, location=cls.locations[1], status=cls.vlan_status),
46
+ VLAN.objects.create(name="VLAN C", vid=100, location=cls.locations[2], status=cls.vlan_status),
44
47
  ]
45
48
 
46
- self.m2m_1 = Relationship(
47
- name="Vlan to Rack",
48
- slug="vlan-rack",
49
- source_type=self.rack_ct,
49
+ cls.m2m_1 = Relationship(
50
+ label="Vlan to Rack",
51
+ key="vlan_rack",
52
+ source_type=cls.rack_ct,
50
53
  source_label="My Vlans",
51
- source_filter={"location": [self.locations[0].slug, self.locations[1].slug, self.locations[2].slug]},
52
- destination_type=self.vlan_ct,
54
+ source_filter={"location": [cls.locations[0].slug, cls.locations[1].slug, cls.locations[2].slug]},
55
+ destination_type=cls.vlan_ct,
53
56
  destination_label="My Racks",
54
57
  type=RelationshipTypeChoices.TYPE_MANY_TO_MANY,
55
58
  )
56
- self.m2m_1.validated_save()
59
+ cls.m2m_1.validated_save()
57
60
 
58
- self.m2m_2 = Relationship(
59
- name="Another Vlan to Rack",
60
- slug="vlan-rack-2",
61
- source_type=self.rack_ct,
62
- destination_type=self.vlan_ct,
61
+ cls.m2m_2 = Relationship(
62
+ label="Another Vlan to Rack",
63
+ key="vlan_rack_2",
64
+ source_type=cls.rack_ct,
65
+ destination_type=cls.vlan_ct,
63
66
  type=RelationshipTypeChoices.TYPE_MANY_TO_MANY,
64
67
  )
65
- self.m2m_2.validated_save()
68
+ cls.m2m_2.validated_save()
66
69
 
67
- self.o2m_1 = Relationship(
68
- name="generic location to vlan",
69
- slug="location-vlan",
70
- source_type=self.location_ct,
71
- destination_type=self.vlan_ct,
70
+ cls.o2m_1 = Relationship(
71
+ label="generic location to vlan",
72
+ key="location_vlan",
73
+ source_type=cls.location_ct,
74
+ destination_type=cls.vlan_ct,
72
75
  type=RelationshipTypeChoices.TYPE_ONE_TO_MANY,
73
76
  )
74
- self.o2m_1.validated_save()
77
+ cls.o2m_1.validated_save()
75
78
 
76
- self.o2o_1 = Relationship(
77
- name="Primary Rack per Location",
78
- slug="primary-rack-location",
79
- source_type=self.rack_ct,
79
+ cls.o2o_1 = Relationship(
80
+ label="Primary Rack per Location",
81
+ key="primary_rack_location",
82
+ source_type=cls.rack_ct,
80
83
  source_hidden=True,
81
- destination_type=self.location_ct,
84
+ destination_type=cls.location_ct,
82
85
  destination_label="Primary Rack",
83
86
  type=RelationshipTypeChoices.TYPE_ONE_TO_ONE,
84
87
  )
85
- self.o2o_1.validated_save()
88
+ cls.o2o_1.validated_save()
86
89
 
87
90
  # Relationships between objects of the same type
88
91
 
89
- self.o2o_2 = Relationship(
90
- name="Alphabetical Locations",
91
- slug="alphabetical-locations",
92
- source_type=self.location_ct,
92
+ cls.o2o_2 = Relationship(
93
+ label="Alphabetical Locations",
94
+ key="alphabetical_locations",
95
+ source_type=cls.location_ct,
93
96
  source_label="Alphabetically Prior",
94
- destination_type=self.location_ct,
97
+ destination_type=cls.location_ct,
95
98
  destination_label="Alphabetically Subsequent",
96
99
  type=RelationshipTypeChoices.TYPE_ONE_TO_ONE,
97
100
  )
98
- self.o2o_2.validated_save()
101
+ cls.o2o_2.validated_save()
99
102
 
100
- self.o2os_1 = Relationship(
101
- name="Redundant Rack",
102
- slug="redundant-rack",
103
- source_type=self.rack_ct,
104
- destination_type=self.rack_ct,
103
+ cls.o2os_1 = Relationship(
104
+ label="Redundant Rack",
105
+ key="redundant_rack",
106
+ source_type=cls.rack_ct,
107
+ destination_type=cls.rack_ct,
105
108
  type=RelationshipTypeChoices.TYPE_ONE_TO_ONE_SYMMETRIC,
106
109
  )
107
- self.o2os_1.validated_save()
110
+ cls.o2os_1.validated_save()
108
111
 
109
- self.m2ms_1 = Relationship(
110
- name="Related Locations",
111
- slug="related-locations",
112
- source_type=self.location_ct,
113
- destination_type=self.location_ct,
112
+ cls.m2ms_1 = Relationship(
113
+ label="Related Locations",
114
+ key="related_locations",
115
+ source_type=cls.location_ct,
116
+ destination_type=cls.location_ct,
114
117
  type=RelationshipTypeChoices.TYPE_MANY_TO_MANY_SYMMETRIC,
115
118
  )
116
- self.m2ms_1.validated_save()
119
+ cls.m2ms_1.validated_save()
117
120
 
118
121
  # Relationships involving a content type that doesn't actually have a backing model.
119
122
  # This can occur in practice if, for example, a relationship is defined for a plugin-defined model,
120
123
  # then the plugin is subsequently uninstalled or deactivated.
121
- self.invalid_ct = ContentType.objects.create(app_label="nonexistent", model="nosuchmodel")
124
+ cls.invalid_ct = ContentType.objects.create(app_label="nonexistent", model="nosuchmodel")
122
125
 
123
126
  # Don't use validated_save() on these as it will fail due to the invalid content-type
124
- self.invalid_relationships = [
127
+ cls.invalid_relationships = [
125
128
  Relationship.objects.create(
126
- name="Invalid Relationship 1",
127
- slug="invalid-relationship-1",
128
- source_type=self.location_ct,
129
- destination_type=self.invalid_ct,
129
+ label="Invalid Relationship 1",
130
+ key="invalid_relationship_1",
131
+ source_type=cls.location_ct,
132
+ destination_type=cls.invalid_ct,
130
133
  type=RelationshipTypeChoices.TYPE_ONE_TO_ONE,
131
134
  ),
132
135
  Relationship.objects.create(
133
- name="Invalid Relationship 2",
134
- slug="invalid-relationship-2",
135
- source_type=self.invalid_ct,
136
- destination_type=self.location_ct,
136
+ label="Invalid Relationship 2",
137
+ key="invalid_relationship_2",
138
+ source_type=cls.invalid_ct,
139
+ destination_type=cls.location_ct,
137
140
  type=RelationshipTypeChoices.TYPE_ONE_TO_MANY,
138
141
  ),
139
142
  Relationship.objects.create(
140
- name="Invalid Relationship 3",
141
- slug="invalid-relationship-3",
142
- source_type=self.invalid_ct,
143
- destination_type=self.invalid_ct,
143
+ label="Invalid Relationship 3",
144
+ key="invalid_relationship_3",
145
+ source_type=cls.invalid_ct,
146
+ destination_type=cls.invalid_ct,
144
147
  type=RelationshipTypeChoices.TYPE_MANY_TO_MANY_SYMMETRIC,
145
148
  ),
146
149
  ]
@@ -150,8 +153,8 @@ class RelationshipBaseTest(TestCase):
150
153
  class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
151
154
  def test_clean_filter_not_dict(self):
152
155
  m2m = Relationship(
153
- name="Another Vlan to Rack",
154
- slug="vlan-rack-2",
156
+ label="Another Vlan to Rack",
157
+ key="vlan_rack_2",
155
158
  source_type=self.location_ct,
156
159
  source_filter=["a list not a dict"],
157
160
  destination_type=self.rack_ct,
@@ -165,8 +168,8 @@ class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
165
168
 
166
169
  def test_clean_filter_not_valid(self):
167
170
  m2m = Relationship(
168
- name="Another Vlan to Rack",
169
- slug="vlan-rack-2",
171
+ label="Another Vlan to Rack",
172
+ key="vlan_rack_2",
170
173
  source_type=self.location_ct,
171
174
  source_filter={"notvalid": "not a location"},
172
175
  destination_type=self.rack_ct,
@@ -179,8 +182,8 @@ class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
179
182
  self.assertEqual(handler.exception.message_dict, expected_errors)
180
183
 
181
184
  m2m = Relationship(
182
- name="Another Vlan to Rack",
183
- slug="vlan-rack-2",
185
+ label="Another Vlan to Rack",
186
+ key="vlan_rack_2",
184
187
  source_type=self.location_ct,
185
188
  source_filter={"parent": "not a list"},
186
189
  destination_type=self.rack_ct,
@@ -193,8 +196,8 @@ class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
193
196
  self.assertEqual(handler.exception.message_dict, expected_errors)
194
197
 
195
198
  m2m = Relationship(
196
- name="Another Vlan to Rack",
197
- slug="vlan-rack-2",
199
+ label="Another Vlan to Rack",
200
+ key="vlan_rack_2",
198
201
  source_type=self.location_ct,
199
202
  source_filter={"parent": ["not a valid location"]},
200
203
  destination_type=self.rack_ct,
@@ -212,8 +215,8 @@ class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
212
215
 
213
216
  def test_clean_valid(self):
214
217
  m2m = Relationship(
215
- name="Another Vlan to Rack",
216
- slug="vlan-rack-2",
218
+ label="Another Vlan to Rack",
219
+ key="vlan_rack_2",
217
220
  source_type=self.location_ct,
218
221
  source_filter={"name": [self.locations[1].slug]},
219
222
  destination_type=self.rack_ct,
@@ -226,8 +229,8 @@ class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
226
229
  def test_clean_invalid_asymmetric(self):
227
230
  """For a symmetric relationship, source and destination properties must match if specified."""
228
231
  o2os = Relationship(
229
- name="Location to Location",
230
- slug="location-to-location",
232
+ label="Location to Location",
233
+ key="location_to_location",
231
234
  source_type=self.location_ct,
232
235
  source_label="Location A",
233
236
  source_hidden=True,
@@ -253,8 +256,8 @@ class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
253
256
  expected_exception = ValidationError({"required_on": ["Symmetric relationships cannot be marked as required."]})
254
257
  with self.assertRaises(ValidationError) as err:
255
258
  Relationship(
256
- name="This shouldn't validate",
257
- slug="vlans-vlans-m2m",
259
+ label="This shouldn't validate",
260
+ key="vlans_vlans_m2m",
258
261
  type="symmetric-many-to-many",
259
262
  source_type=self.vlan_ct,
260
263
  destination_type=self.vlan_ct,
@@ -263,8 +266,8 @@ class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
263
266
  self.assertEqual(expected_exception, err.exception)
264
267
  with self.assertRaises(ValidationError) as err:
265
268
  Relationship(
266
- name="This shouldn't validate",
267
- slug="vlans-vlans-o2o",
269
+ label="This shouldn't validate",
270
+ key="vlans_vlans_o2o",
268
271
  type="symmetric-one-to-one",
269
272
  source_type=self.vlan_ct,
270
273
  destination_type=self.vlan_ct,
@@ -275,8 +278,8 @@ class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
275
278
  def test_clean_valid_symmetric_implicit(self):
276
279
  """For a symmetric relationship, omitted relevant properties are autofilled on clean."""
277
280
  o2os = Relationship(
278
- name="Location to Location",
279
- slug="location-to-location",
281
+ label="Location to Location",
282
+ key="location_to_location",
280
283
  source_type=self.location_ct,
281
284
  destination_type=self.location_ct,
282
285
  source_label="Location",
@@ -374,6 +377,49 @@ class RelationshipTest(RelationshipBaseTest): # TODO: BaseModelTestCase mixin?
374
377
  self.assertIsInstance(field, DynamicModelChoiceField)
375
378
  self.assertEqual(field.label, "rack")
376
379
 
380
+ def test_check_if_key_is_graphql_safe(self):
381
+ """
382
+ Check the GraphQL validation method on CustomField Key Attribute.
383
+ """
384
+ # Check if it catches the cr.key starting with a digit.
385
+ cr1 = Relationship(
386
+ label="Vlans to Vlans",
387
+ key="12_vlans_to_vlans",
388
+ type="symmetric-many-to-many",
389
+ source_type=self.vlan_ct,
390
+ destination_type=self.vlan_ct,
391
+ )
392
+ with self.assertRaises(ValidationError) as error:
393
+ cr1.validated_save()
394
+ self.assertIn(
395
+ "This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
396
+ str(error.exception),
397
+ )
398
+ # Check if it catches the cr.key with whitespace.
399
+ cr1.key = "test 1"
400
+ with self.assertRaises(ValidationError) as error:
401
+ cr1.validated_save()
402
+ self.assertIn(
403
+ "This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
404
+ str(error.exception),
405
+ )
406
+ # Check if it catches the cr.key with hyphens.
407
+ cr1.key = "test-1-relationship"
408
+ with self.assertRaises(ValidationError) as error:
409
+ cr1.validated_save()
410
+ self.assertIn(
411
+ "This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
412
+ str(error.exception),
413
+ )
414
+ # Check if it catches the cr.key with special characters
415
+ cr1.key = "test_1_rela)(&dship"
416
+ with self.assertRaises(ValidationError) as error:
417
+ cr1.validated_save()
418
+ self.assertIn(
419
+ "This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
420
+ str(error.exception),
421
+ )
422
+
377
423
 
378
424
  @skip
379
425
  class RelationshipAssociationTest(RelationshipBaseTest):
@@ -413,8 +459,8 @@ class RelationshipAssociationTest(RelationshipBaseTest):
413
459
  """Test creation of invalid relationship association restricted by destination/source filter."""
414
460
 
415
461
  relationship = Relationship.objects.create(
416
- name="Location to Rack Rel 1",
417
- slug="location-to-rack-rel-1",
462
+ label="Location to Rack Rel 1",
463
+ key="location_to_rack_rel_1",
418
464
  source_type=self.location_ct,
419
465
  source_filter={"name": [self.locations[0].name]},
420
466
  destination_type=self.rack_ct,
@@ -701,7 +747,7 @@ class RelationshipAssociationTest(RelationshipBaseTest):
701
747
  "has_many": False,
702
748
  "label": "Alphabetically Subsequent",
703
749
  "peer_type": self.location_ct,
704
- "url": reverse("dcim:location", kwargs={"slug": self.locations[0].slug}),
750
+ "url": reverse("dcim:location", kwargs={"pk": self.locations[0].pk}),
705
751
  "value": self.locations[0],
706
752
  },
707
753
  )
@@ -767,11 +813,12 @@ class RelationshipAssociationTest(RelationshipBaseTest):
767
813
  initial_count = RelationshipAssociation.objects.count()
768
814
  # Create new locations because protected error might be raised if we use test fixtures here.
769
815
  location_type = LocationType.objects.get(name="Campus")
816
+ location_status = Status.objects.get_for_model(Location).first()
770
817
  locations = (
771
- Location.objects.create(name="new location 1", location_type=location_type),
772
- Location.objects.create(name="new location 2", location_type=location_type),
773
- Location.objects.create(name="new location 3", location_type=location_type),
774
- Location.objects.create(name="new location 4", location_type=location_type),
818
+ Location.objects.create(name="new location 1", location_type=location_type, status=location_status),
819
+ Location.objects.create(name="new location 2", location_type=location_type, status=location_status),
820
+ Location.objects.create(name="new location 3", location_type=location_type, status=location_status),
821
+ Location.objects.create(name="new location 4", location_type=location_type, status=location_status),
775
822
  )
776
823
  associations = [
777
824
  RelationshipAssociation(relationship=self.m2m_1, source=self.racks[0], destination=self.vlans[0]),
@@ -905,8 +952,8 @@ class RelationshipTableTest(RelationshipBaseTest):
905
952
 
906
953
  # Test non-symmetric many to many with same source_type and same destination_type
907
954
  self.m2m_same_type = Relationship(
908
- name="Location to Location",
909
- slug="location-to-location",
955
+ label="Location to Location",
956
+ key="location_to_location",
910
957
  source_type=self.location_ct,
911
958
  destination_type=self.location_ct,
912
959
  type=RelationshipTypeChoices.TYPE_MANY_TO_MANY,
@@ -933,50 +980,50 @@ class RelationshipTableTest(RelationshipBaseTest):
933
980
  location_table = LocationTable(queryset)
934
981
 
935
982
  relationship_column_expected = {
936
- "location-vlan_src": [
983
+ "location_vlan_src": [
937
984
  format_html(
938
985
  '<a href="{}?relationship={}&{}_id={}">{} {}</a>',
939
986
  reverse("extras:relationshipassociation_list"),
940
- cr_1.relationship.slug,
987
+ cr_1.relationship.key,
941
988
  "source",
942
989
  self.locations[0].id,
943
990
  2,
944
991
  "VLANs",
945
992
  )
946
993
  ],
947
- "primary-rack-location_dst": [
994
+ "primary_rack_location_dst": [
948
995
  f'<a href="{self.racks[0].get_absolute_url()}">{self.racks[0].__str__()}</a>'
949
996
  ],
950
- "alphabetical-locations_src": [
997
+ "alphabetical_locations_src": [
951
998
  f'<a href="{self.locations[1].get_absolute_url()}">{self.locations[1].__str__()}</a>'
952
999
  ],
953
- "related-locations_peer": [
1000
+ "related_locations_peer": [
954
1001
  format_html(
955
1002
  '<a href="{}?relationship={}&{}_id={}">{} {}</a>',
956
1003
  reverse("extras:relationshipassociation_list"),
957
- cr_5.relationship.slug,
1004
+ cr_5.relationship.key,
958
1005
  "peer",
959
1006
  self.locations[0].id,
960
1007
  2,
961
1008
  "locations",
962
1009
  )
963
1010
  ],
964
- "location-to-location_src": [
1011
+ "location_to_location_src": [
965
1012
  format_html(
966
1013
  '<a href="{}?relationship={}&{}_id={}">{} {}</a>',
967
1014
  reverse("extras:relationshipassociation_list"),
968
- cr_7.relationship.slug,
1015
+ cr_7.relationship.key,
969
1016
  "source",
970
1017
  self.locations[0].id,
971
1018
  1,
972
1019
  "location",
973
1020
  )
974
1021
  ],
975
- "location-to-location_dst": [
1022
+ "location_to_location_dst": [
976
1023
  format_html(
977
1024
  '<a href="{}?relationship={}&{}_id={}">{} {}</a>',
978
1025
  reverse("extras:relationshipassociation_list"),
979
- cr_8.relationship.slug,
1026
+ cr_8.relationship.key,
980
1027
  "destination",
981
1028
  self.locations[0].id,
982
1029
  1,
@@ -1062,8 +1109,8 @@ class RequiredRelationshipTestMixin(TestCase):
1062
1109
  circuittype_ct = ContentType.objects.get_for_model(CircuitType)
1063
1110
  vlan_ct = ContentType.objects.get_for_model(VLAN)
1064
1111
  relationship_m2m = Relationship(
1065
- name="VLANs require at least one Device",
1066
- slug="vlans-devices-m2m",
1112
+ label="VLANs require at least one Device",
1113
+ key="vlans_devices_m2m",
1067
1114
  type="many-to-many",
1068
1115
  source_type=device_ct,
1069
1116
  destination_type=vlan_ct,
@@ -1071,8 +1118,8 @@ class RequiredRelationshipTestMixin(TestCase):
1071
1118
  )
1072
1119
  relationship_m2m.validated_save()
1073
1120
  relationship_o2m = Relationship(
1074
- name="Platforms require at least one device",
1075
- slug="platform-devices-o2m",
1121
+ label="Platforms require at least one device",
1122
+ key="platform_devices_o2m",
1076
1123
  type="one-to-many",
1077
1124
  source_type=platform_ct,
1078
1125
  destination_type=device_ct,
@@ -1080,8 +1127,8 @@ class RequiredRelationshipTestMixin(TestCase):
1080
1127
  )
1081
1128
  relationship_o2m.validated_save()
1082
1129
  relationship_o2o = Relationship(
1083
- name="Circuit type requires one platform",
1084
- slug="circuittype-platform-o2o",
1130
+ label="Circuit type requires one platform",
1131
+ key="circuittype_platform_o2o",
1085
1132
  type="one-to-one",
1086
1133
  source_type=circuittype_ct,
1087
1134
  destination_type=platform_ct,
@@ -1106,7 +1153,7 @@ class RequiredRelationshipTestMixin(TestCase):
1106
1153
  "api": {
1107
1154
  "objects_nonexistent": "VLANs require at least one device, but no devices exist yet. "
1108
1155
  "Create a device by posting to /api/dcim/devices/",
1109
- "objects_not_specified": 'You need to specify ["relationships"]["vlans-devices-m2m"]'
1156
+ "objects_not_specified": 'You need to specify ["relationships"]["vlans_devices_m2m"]'
1110
1157
  '["source"]["objects"].',
1111
1158
  },
1112
1159
  "ui": {
@@ -1128,7 +1175,7 @@ class RequiredRelationshipTestMixin(TestCase):
1128
1175
  "api": {
1129
1176
  "objects_nonexistent": "Platforms require at least one device, but no devices exist yet. "
1130
1177
  "Create a device by posting to /api/dcim/devices/",
1131
- "objects_not_specified": 'You need to specify ["relationships"]["platform-devices-o2m"]'
1178
+ "objects_not_specified": 'You need to specify ["relationships"]["platform_devices_o2m"]'
1132
1179
  '["destination"]["objects"].',
1133
1180
  },
1134
1181
  "ui": {
@@ -1151,7 +1198,7 @@ class RequiredRelationshipTestMixin(TestCase):
1151
1198
  "api": {
1152
1199
  "objects_nonexistent": "Circuit types require a platform, but no platforms exist yet. "
1153
1200
  "Create a platform by posting to /api/dcim/platforms/",
1154
- "objects_not_specified": 'You need to specify ["relationships"]["circuittype-platform-o2o"]'
1201
+ "objects_not_specified": 'You need to specify ["relationships"]["circuittype_platform_o2o"]'
1155
1202
  '["destination"]["objects"].',
1156
1203
  },
1157
1204
  "ui": {
@@ -1173,7 +1220,7 @@ class RequiredRelationshipTestMixin(TestCase):
1173
1220
  from_model = getattr(params["relationship"], f"{required_on}_type").model_class()
1174
1221
  to_model = getattr(params["relationship"], f"{target_side}_type").model_class()
1175
1222
 
1176
- test_msg = f"Testing {from_model._meta.verbose_name} relationship '{params['relationship'].slug}'"
1223
+ test_msg = f"Testing {from_model._meta.verbose_name} relationship '{params['relationship'].key}'"
1177
1224
  with self.subTest(msg=test_msg):
1178
1225
  # Clear any existing required target model objects that may have been created in previous subTests
1179
1226
  to_model.objects.all().delete()
@@ -1181,7 +1228,7 @@ class RequiredRelationshipTestMixin(TestCase):
1181
1228
  # Get count of existing objects:
1182
1229
  existing_count = from_model.objects.count()
1183
1230
 
1184
- related_field_name = params["relationship"].slug
1231
+ related_field_name = params["relationship"].key
1185
1232
  if interact_with == "ui":
1186
1233
  related_field_name = f"cr_{related_field_name}__{target_side}"
1187
1234
 
@@ -18,14 +18,6 @@ class TaggedItemORMTest(TestCase):
18
18
 
19
19
  cls.location = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
20
20
 
21
- def test_tags_set_taggit_1(self):
22
- """Test that obj.tags.set() works when invoked like django-taggit 1.x."""
23
- self.location.tags.set("Tag 1", "Tag 2")
24
- self.assertListEqual(sorted([t.name for t in self.location.tags.all()]), ["Tag 1", "Tag 2"])
25
-
26
- self.location.tags.set(Tag.objects.get(name="Tag 1"))
27
- self.assertListEqual(sorted([t.name for t in self.location.tags.all()]), ["Tag 1"])
28
-
29
21
  def test_tags_set_taggit_2(self):
30
22
  """Test that obj.tags.set() works when invoked like django-taggit 2.x and later."""
31
23
  self.location.tags.set(["Tag 1", "Tag 2"])
@@ -59,7 +51,8 @@ class TaggedItemTest(APITestCase):
59
51
 
60
52
  response = self.client.post(url, data, format="json", **self.header)
61
53
  self.assertHttpStatus(response, status.HTTP_201_CREATED)
62
- self.assertListEqual(sorted([t["id"] for t in response.data["tags"]]), sorted(data["tags"]))
54
+ # response with default depth is a list of tag URLs
55
+ self.assertEqual(sorted(response.data["tags"]), sorted([self.absolute_api_url(t) for t in self.tags]))
63
56
  location = Location.objects.get(pk=response.data["id"])
64
57
  self.assertListEqual(sorted([t.name for t in location.tags.all()]), sorted([t.name for t in self.tags]))
65
58
 
@@ -78,9 +71,10 @@ class TaggedItemTest(APITestCase):
78
71
 
79
72
  response = self.client.patch(url, data, format="json", **self.header)
80
73
  self.assertHttpStatus(response, status.HTTP_200_OK)
74
+ # response with default depth is a list of tag URLs
81
75
  self.assertListEqual(
82
- sorted([t["name"] for t in response.data["tags"]]),
83
- sorted([t["name"] for t in data["tags"]]),
76
+ sorted(response.data["tags"]),
77
+ sorted([self.absolute_api_url(t) for t in [self.tags[0], self.tags[1], self.tags[3]]]),
84
78
  )
85
79
  location = Location.objects.get(pk=response.data["id"])
86
80
  self.assertListEqual(
@@ -110,6 +104,7 @@ class TaggedItemTest(APITestCase):
110
104
  "slug": "test-location",
111
105
  "status": Status.objects.get_for_model(Location).first().pk,
112
106
  "tags": [tag.id],
107
+ "location_type": self.location_type.pk,
113
108
  }
114
109
  url = reverse("dcim-api:location-list")
115
110
  self.add_permissions("dcim.add_location")
@@ -1,7 +1,8 @@
1
1
  from unittest import mock
2
2
 
3
3
  from nautobot.core.testing import TestCase
4
- from nautobot.extras.utils import get_celery_queues, get_worker_count
4
+ from nautobot.extras.registry import registry
5
+ from nautobot.extras.utils import get_celery_queues, get_worker_count, populate_model_features_registry
5
6
 
6
7
 
7
8
  class UtilsTestCase(TestCase):
@@ -47,3 +48,32 @@ class UtilsTestCase(TestCase):
47
48
  self.assertEqual(get_worker_count(queue="bulk"), 4)
48
49
  with self.subTest("Empty queue"):
49
50
  self.assertEqual(get_worker_count(queue="nobody"), 0)
51
+
52
+ def test_populate_model_features_registry(self):
53
+ original_custom_fields_registry = registry["model_features"]["custom_fields"].copy()
54
+
55
+ self.assertIn(
56
+ "circuit", registry["model_features"]["custom_fields"]["circuits"], "Registry should already be populated"
57
+ )
58
+
59
+ populate_model_features_registry()
60
+ self.assertDictEqual(
61
+ registry["model_features"]["custom_fields"],
62
+ original_custom_fields_registry,
63
+ "Registry should not be modified if refresh flag not set",
64
+ )
65
+
66
+ registry["model_features"]["custom_fields"].pop("circuits")
67
+ self.assertNotEqual(
68
+ registry["model_features"]["custom_fields"],
69
+ original_custom_fields_registry,
70
+ "Registry should be successfully modified",
71
+ )
72
+
73
+ # Make sure we return to the original state
74
+ populate_model_features_registry(refresh=True)
75
+ self.assertDictEqual(
76
+ registry["model_features"]["custom_fields"],
77
+ original_custom_fields_registry,
78
+ "Registry should be restored to original state",
79
+ )