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
@@ -0,0 +1,713 @@
1
+ import collections
2
+
3
+ from django.core.exceptions import ValidationError
4
+ from django.db import models
5
+ import netaddr
6
+
7
+
8
+ BASE_NAME = "Cleanup Namespace"
9
+ DESCRIPTION = "Created by Nautobot 2.0 IPAM data migrations."
10
+ GLOBAL_NS = "Global"
11
+
12
+
13
+ def process_namespaces(apps, schema_editor):
14
+ print("\n", end="")
15
+
16
+ # Fail if any interface or vm interface has IPs with different VRFs
17
+ check_interface_vrfs(apps)
18
+
19
+ # Prefix Broadcast is a derived field, so we should update it before we start
20
+ ensure_correct_prefix_broadcast(apps)
21
+
22
+ # Cleanup Prefixes and IPAddresses version fields
23
+ add_prefix_and_ip_address_version(apps)
24
+
25
+ # VRFs
26
+ process_vrfs(apps)
27
+
28
+ # IPAddresses
29
+ process_ip_addresses(apps)
30
+
31
+ # Prefixes
32
+ process_prefix_duplicates(apps)
33
+ reparent_prefixes(apps)
34
+
35
+ # Make another pass across all VRFs to duplicate it if it has prefixes
36
+ # in another namespace (non-unique VRFs with duplicate Prefixes)
37
+ copy_vrfs_to_cleanup_namespaces(apps)
38
+
39
+ # [VM]Interfaces
40
+ process_interfaces(apps)
41
+
42
+ # VRF-Prefix M2M
43
+ process_vrfs_prefixes_m2m(apps)
44
+
45
+
46
+ def check_interface_vrfs(apps):
47
+ """
48
+ Enumerate all Interface and VMInterface objects and raise an exception if any interface is found that is associated
49
+ to more than one distinct VRF through the ip_address many-to-many relationship.
50
+
51
+ Args:
52
+ apps: Django apps module
53
+
54
+ Returns:
55
+ None
56
+ """
57
+
58
+ Interface = apps.get_model("dcim", "Interface")
59
+ VMInterface = apps.get_model("virtualization", "VMInterface")
60
+
61
+ interfaces_with_multiple_vrfs = Interface.objects.annotate(vrf_count=models.Count("ip_addresses__vrf")).filter(
62
+ vrf_count__gt=1
63
+ )
64
+ vm_interfaces_with_multiple_vrfs = VMInterface.objects.annotate(vrf_count=models.Count("ip_addresses__vrf")).filter(
65
+ vrf_count__gt=1
66
+ )
67
+
68
+ if interfaces_with_multiple_vrfs.exists() or vm_interfaces_with_multiple_vrfs.exists():
69
+ raise ValidationError(
70
+ "You cannot migrate Interfaces or VMInterfaces that have IPs with differing VRFs.",
71
+ list(interfaces_with_multiple_vrfs),
72
+ list(vm_interfaces_with_multiple_vrfs),
73
+ )
74
+
75
+
76
+ def process_vrfs(apps):
77
+ """
78
+ Enumerate all VRF objects in the database and attempt to find suitable Namespace with no duplicate VRF names and
79
+ no duplicate Prefixes associated to the VRF. Any VRF with `enforce_unique` set and has related prefixes will be
80
+ moved to its own Namespace. All other VRFs will be checked for duplicate names and Prefixes and moved to a cleanup
81
+ Namespace if any duplicates are found.
82
+
83
+ Args:
84
+ apps: Django apps module
85
+
86
+ Returns:
87
+ None
88
+ """
89
+ VRF = apps.get_model("ipam", "VRF")
90
+ vrfs = VRF.objects.all().order_by("name", "rd")
91
+ unique_non_empty_vrfs = vrfs.filter(enforce_unique=True).exclude(ip_addresses__isnull=True, prefixes__isnull=True)
92
+ # At the point in the migration where we iterate through vrfs in global_ns_vrfs, every vrf that
93
+ # has already been processed has been moved to a new namespace. Anything left in the global
94
+ # namespace has yet to be processed which is why we're iterating through this on the second
95
+ # loop.
96
+ global_ns_vrfs = vrfs.filter(namespace__name=GLOBAL_NS)
97
+
98
+ # Case 0: VRFs with enforce_unique move to their own Namespace.
99
+ for vrf in unique_non_empty_vrfs:
100
+ print(f">>> Processing migration for VRF {vrf.name!r}, Namespace {vrf.namespace.name!r}")
101
+ vrf.namespace = create_vrf_namespace(apps, vrf)
102
+ vrf.save()
103
+ vrf.prefixes.update(namespace=vrf.namespace)
104
+ print(f" VRF {vrf.name!r} migrated to Namespace {vrf.namespace.name!r}")
105
+
106
+ # Case 00: VRFs with duplicate names or prefixes move to a Cleanup Namespace.
107
+ # Case 1 is not included here because it is a no-op.
108
+ for vrf in global_ns_vrfs.annotate(prefix_count=models.Count("prefixes")).order_by("-prefix_count"):
109
+ print(f">>> Processing migration for VRF {vrf.name!r}, Namespace {vrf.namespace.name!r}")
110
+ original_namespace = vrf.namespace
111
+ vrf.namespace = get_next_vrf_cleanup_namespace(apps, vrf)
112
+ vrf.save()
113
+ if vrf.namespace != original_namespace:
114
+ vrf.prefixes.update(namespace=vrf.namespace)
115
+ print(f" VRF {vrf.name!r} migrated from Namespace {original_namespace.name!r} to {vrf.namespace.name!r}")
116
+
117
+
118
+ def add_prefix_and_ip_address_version(apps):
119
+ """
120
+ Enumerate all Prefix and IPAddress objects in the database and populate the ip_version field.
121
+
122
+ Args:
123
+ apps: Django apps module
124
+
125
+ Returns:
126
+ None
127
+ """
128
+ Prefix = apps.get_model("ipam", "Prefix")
129
+ IPAddress = apps.get_model("ipam", "IPAddress")
130
+
131
+ print(">>> Populating Prefix.ip_version field")
132
+ for pfx in Prefix.objects.all():
133
+ cidr = validate_cidr(apps, pfx)
134
+ pfx.ip_version = cidr.version
135
+ pfx.save()
136
+
137
+ print(">>> Populating IPAddress.ip_version field")
138
+ for ip in IPAddress.objects.all():
139
+ cidr = validate_cidr(apps, ip)
140
+ ip.ip_version = cidr.version
141
+ ip.save()
142
+
143
+
144
+ def process_ip_addresses(apps):
145
+ """
146
+ Enumerate collected IPs and parent them.
147
+
148
+ - For IPs with found parents: Set that parent and save the `IPAddress`.
149
+ - For orphaned IPs (missing parents):
150
+ - Generate a network from the `IPAddress`
151
+ - Get or create the parent `Prefix`
152
+ - Set that as the parent and save the `IPAddress`
153
+
154
+ Args:
155
+ apps: Django apps module
156
+
157
+ Returns:
158
+ None
159
+ """
160
+ # Find the correct namespace for each IPAddress and move it if necessary.
161
+ IPAddress = apps.get_model("ipam", "IPAddress")
162
+ Namespace = apps.get_model("ipam", "Namespace")
163
+ Prefix = apps.get_model("ipam", "Prefix")
164
+
165
+ # Explicitly set the parent for those that were found and save them.
166
+ for ip in IPAddress.objects.filter(parent__isnull=True).order_by("-vrf", "-tenant"):
167
+ potential_parents = get_closest_parent(apps, ip, Prefix.objects.all())
168
+ for prefix in potential_parents:
169
+ if not prefix.ip_addresses.filter(host=ip.host).exists():
170
+ ip.parent = prefix
171
+ ip.save()
172
+ break
173
+
174
+ # For IPs with no discovered parent, create one and assign it to the IP.
175
+ global_ns = Namespace.objects.get(name=GLOBAL_NS)
176
+ for orphaned_ip in IPAddress.objects.filter(parent__isnull=True):
177
+ ip_repr = str(validate_cidr(apps, orphaned_ip))
178
+ print(f">>> Processing Parent migration for orphaned IPAddress {ip_repr!r}")
179
+
180
+ new_parent_cidr = generate_parent_prefix(apps, orphaned_ip)
181
+ network = new_parent_cidr.network
182
+ prefix_length = new_parent_cidr.prefixlen
183
+ potential_parents = Prefix.objects.filter(network=network, prefix_length=prefix_length).exclude(
184
+ ip_addresses__host=orphaned_ip.host
185
+ )
186
+ if potential_parents.exists():
187
+ new_parent = potential_parents.first()
188
+
189
+ else:
190
+ broadcast = new_parent_cidr[-1]
191
+ # This can result in duplicate Prefixes being created in the global_ns but that will be
192
+ # cleaned up subsequently in `process_prefix_duplicates`.
193
+ new_parent = Prefix.objects.create(
194
+ ip_version=orphaned_ip.ip_version,
195
+ network=network,
196
+ broadcast=broadcast,
197
+ tenant=orphaned_ip.tenant,
198
+ vrf=orphaned_ip.vrf,
199
+ prefix_length=prefix_length,
200
+ namespace=orphaned_ip.vrf.namespace if orphaned_ip.vrf else global_ns,
201
+ description=DESCRIPTION,
202
+ )
203
+ orphaned_ip.parent = new_parent
204
+ orphaned_ip.save()
205
+
206
+ parent_repr = str(validate_cidr(apps, new_parent))
207
+ print(
208
+ f" IPAddress {ip_repr!r} migrated to Parent Prefix {parent_repr!r} in Namespace {new_parent.namespace.name!r}"
209
+ )
210
+
211
+ # By this point we should arrive at NO orphaned IPAddress objects.
212
+ if IPAddress.objects.filter(parent__isnull=True).exists():
213
+ raise SystemExit("Unexpected orphaned IPAddress objects found.")
214
+
215
+
216
+ def process_prefix_duplicates(apps):
217
+ """
218
+ Enumerate all Prefix objects in the database and attempt to find suitable Namespace with no Prefixes with duplicate
219
+ network and prefix length. Duplicate prefixes will be moved to a cleanup Namespace if any duplicates are found.
220
+
221
+ Args:
222
+ apps: Django apps module
223
+
224
+ Returns:
225
+ None
226
+ """
227
+ Namespace = apps.get_model("ipam", "Namespace")
228
+ Prefix = apps.get_model("ipam", "Prefix")
229
+ global_namespace = Namespace.objects.get(name=GLOBAL_NS)
230
+
231
+ namespaces = list(Namespace.objects.all())
232
+ # Always start with the Global Namespace.
233
+ namespaces.remove(global_namespace)
234
+ namespaces.insert(0, global_namespace)
235
+
236
+ for ns in namespaces:
237
+ dupe_prefixes = find_duplicate_prefixes(apps, ns)
238
+
239
+ # process tenants in order of number of related prefixes (fewest first)
240
+ tenant_ids_sorted = (
241
+ Prefix.objects.filter(namespace=ns)
242
+ .values("tenant")
243
+ .annotate(tenant_count=models.Count("tenant"))
244
+ .order_by("tenant_count")
245
+ .values_list("tenant", flat=True)
246
+ )
247
+ for dupe in dupe_prefixes:
248
+ print(f">>> Processing Namespace migration for duplicate Prefix {dupe!r}")
249
+ network, prefix_length = dupe.split("/")
250
+ objects = Prefix.objects.filter(network=network, prefix_length=prefix_length, namespace=ns)
251
+ # Leave the last instance of the Prefix in the original Namespace
252
+ last_prefix = objects.filter(tenant_id=tenant_ids_sorted.last()).last()
253
+
254
+ for tenant_id in tenant_ids_sorted:
255
+ for _, prefix in enumerate(objects):
256
+ if prefix == last_prefix or prefix.tenant_id != tenant_id:
257
+ continue
258
+
259
+ namespace_base_name = BASE_NAME
260
+ if prefix.tenant is not None:
261
+ namespace_base_name += f" {prefix.tenant.name}"
262
+ prefix.namespace = get_next_prefix_cleanup_namespace(apps, prefix, base_name=namespace_base_name)
263
+ prefix.save()
264
+ print(
265
+ f" Prefix {dupe!r} migrated from Namespace {ns.name} to Namespace {prefix.namespace.name!r}"
266
+ )
267
+
268
+
269
+ def reparent_prefixes(apps):
270
+ """
271
+ Enumerate all Prefix objects in the database and attempt to find parent Prefix objects in the same Namespace.
272
+
273
+ Args:
274
+ apps: Django apps module
275
+
276
+ Returns:
277
+ None
278
+ """
279
+ Prefix = apps.get_model("ipam", "Prefix")
280
+
281
+ print("\n>>> Processing Prefix parents, please standby...")
282
+ for pfx in Prefix.objects.all().order_by("-prefix_length", "tenant"):
283
+ try:
284
+ parent = get_closest_parent(apps, pfx, pfx.namespace.prefixes.all())
285
+ if pfx.namespace != parent.namespace:
286
+ raise ValidationError("Prefix and parent are in different Namespaces")
287
+ print(f">>> {pfx.network}/{pfx.prefix_length} parent: {parent.network}/{parent.prefix_length}")
288
+ pfx.parent = parent
289
+ pfx.save()
290
+ except Prefix.DoesNotExist:
291
+ continue
292
+
293
+
294
+ def copy_vrfs_to_cleanup_namespaces(apps):
295
+ """
296
+ Enumerate every Prefix with a non-null vrf and if the vrf namespace doesn't match the prefix namespace, make
297
+ a copy of the vrf in the cleanup namespace.
298
+
299
+ Args:
300
+ apps: Django apps module
301
+
302
+ Returns:
303
+ None
304
+ """
305
+
306
+ IPAddress = apps.get_model("ipam", "IPAddress")
307
+ Prefix = apps.get_model("ipam", "Prefix")
308
+ VRF = apps.get_model("ipam", "VRF")
309
+ Namespace = apps.get_model("ipam", "Namespace")
310
+
311
+ for vrf in VRF.objects.all():
312
+ if not vrf.prefixes.exclude(namespace=vrf.namespace).exists():
313
+ continue
314
+
315
+ namespaces = (
316
+ vrf.prefixes.exclude(namespace=vrf.namespace).order_by().values_list("namespace", flat=True).distinct()
317
+ )
318
+ for namespace_pk in namespaces:
319
+ namespace = Namespace.objects.get(pk=namespace_pk)
320
+ print(f">>> Copying VRF {vrf.name!r} to namespace {namespace.name!r}")
321
+ vrf_copy = VRF.objects.create(
322
+ namespace=namespace,
323
+ name=vrf.name,
324
+ rd=vrf.rd,
325
+ tenant=vrf.tenant,
326
+ enforce_unique=vrf.enforce_unique,
327
+ _custom_field_data=vrf._custom_field_data,
328
+ description=DESCRIPTION,
329
+ )
330
+ vrf_copy.import_targets.set(vrf.import_targets.all())
331
+ vrf_copy.export_targets.set(vrf.export_targets.all())
332
+ Prefix.objects.filter(vrf=vrf, namespace_id=namespace_pk).update(vrf=vrf_copy)
333
+ IPAddress.objects.filter(vrf=vrf, parent__namespace_id=namespace_pk).update(vrf=vrf_copy)
334
+
335
+
336
+ def process_interfaces(apps):
337
+ """
338
+ Process [VM]Interface objects.
339
+
340
+ Args:
341
+ apps: Django apps module
342
+
343
+ Returns:
344
+ None
345
+ """
346
+ Interface = apps.get_model("dcim", "Interface")
347
+ VMInterface = apps.get_model("virtualization", "VMInterface")
348
+ VRFDeviceAssignment = apps.get_model("ipam", "VRFDeviceAssignment")
349
+
350
+ # Interfaces with vrfs
351
+ ip_interfaces = Interface.objects.filter(ip_addresses__vrf__isnull=False)
352
+ ip_vminterfaces = VMInterface.objects.filter(ip_addresses__vrf__isnull=False)
353
+
354
+ # Case 2: Interface has one or more IP address assigned to it with no more than 1 distinct associated VRF (none is excluded)
355
+ # The interface's VRF foreign key should be set to the VRF of any related IP Address with a non-null VRF.
356
+ # The interface's parent device or virtual machine should adopt an assocation to the VRF (VRFDeviceAssignment) as well.
357
+ for ifc in ip_interfaces:
358
+ print(f">>> Processing VRF migration for numbered Interface {ifc.name!r}")
359
+ # Set the Interface VRF to that of the first assigned IPAddress.
360
+ first_ip = ifc.ip_addresses.filter(vrf__isnull=False).first()
361
+
362
+ ifc_vrf = first_ip.vrf
363
+ ifc.vrf = ifc_vrf
364
+ ifc.save()
365
+
366
+ # Create the VRF-to-device assignment.
367
+ VRFDeviceAssignment.objects.get_or_create(vrf=ifc_vrf, device=ifc.device, rd=ifc_vrf.rd, name=ifc_vrf.name)
368
+
369
+ print(f" VRF {ifc_vrf.name!r} migrated from IPAddress {first_ip.host!r} to Interface {ifc.name!r}")
370
+
371
+ # VirtualMachine should adopt an association to the VRF (VRFDeviceAssignment) as well.
372
+ for ifc in ip_vminterfaces:
373
+ print(f">>> Processing VRF migration for numbered VMInterface {ifc.name!r}")
374
+ # Set the VMInterface VRF to that of the first assigned IPAddress.
375
+ first_ip = ifc.ip_addresses.filter(vrf__isnull=False).first()
376
+
377
+ ifc_vrf = first_ip.vrf
378
+ ifc.vrf = ifc_vrf
379
+ ifc.save()
380
+
381
+ # Create the VRF-to-device assignment.
382
+ VRFDeviceAssignment.objects.get_or_create(
383
+ vrf=ifc_vrf, virtual_machine=ifc.virtual_machine, rd=ifc_vrf.rd, name=ifc_vrf.name
384
+ )
385
+
386
+ print(f" VRF {ifc_vrf.name!r} migrated from IPAddress {first_ip.host!r} to VMInterface {ifc.name!r}")
387
+
388
+
389
+ def process_vrfs_prefixes_m2m(apps):
390
+ """
391
+ Convert the Prefix -> VRF FK relationship to a M2M relationship.
392
+
393
+ Args:
394
+ apps: Django apps module
395
+
396
+ Returns:
397
+ None
398
+ """
399
+
400
+ VRF = apps.get_model("ipam", "VRF")
401
+
402
+ vrfs_with_prefixes = VRF.objects.filter(prefixes__isnull=False).order_by().distinct()
403
+
404
+ for vrf in vrfs_with_prefixes:
405
+ print(f" Converting Prefix relationships to VRF {vrf.name} to M2M.")
406
+ vrf.prefixes_m2m.set(vrf.prefixes.all())
407
+
408
+
409
+ def get_prefixes(qs):
410
+ """
411
+ Given a queryset, return the prefixes as 2-tuples of (network, prefix_length).
412
+
413
+ Args:
414
+ qs (QuerySet): QuerySet of Prefix objects.
415
+
416
+ Returns:
417
+ list
418
+ """
419
+ return sorted(qs.values_list("network", "prefix_length"))
420
+
421
+
422
+ def compare_prefix_querysets(a, b):
423
+ """
424
+ Compare two QuerySets of Prefix objects and return the set intersection of both.
425
+
426
+ Args:
427
+ a (QuerySet): Left-side QuerySet
428
+ b (QuerySet): Right-side QuerySet
429
+
430
+ Returns:
431
+ set(tuple)
432
+ """
433
+ set_a = set(get_prefixes(a))
434
+ set_b = set(get_prefixes(b))
435
+ return set_a.intersection(set_b)
436
+
437
+
438
+ def create_vrf_namespace(apps, vrf):
439
+ """
440
+ Given a VRF, get or create a unique "VRF Namespace" for it.
441
+
442
+ Args:
443
+ apps: Django apps module
444
+ vrf (VRF): VRF instance
445
+
446
+ Returns:
447
+ Namespace
448
+ """
449
+ Namespace = apps.get_model("ipam", "Namespace")
450
+ base_name = f"VRF Namespace {vrf.name}"
451
+ counter = 1
452
+ created = False
453
+ name = base_name
454
+ while not created:
455
+ ns, created = Namespace.objects.get_or_create(
456
+ name=name,
457
+ defaults={"description": DESCRIPTION},
458
+ )
459
+ counter += 1
460
+ name = f"{base_name} ({counter})"
461
+
462
+ return ns
463
+
464
+
465
+ def find_duplicate_prefixes(apps, namespace):
466
+ """
467
+ Return a list of duplicate prefixes for a given Namespace.
468
+
469
+ Args:
470
+ apps: Django apps module
471
+ namespace (Namespace): Namespace instance
472
+
473
+ Returns:
474
+ list(str)
475
+ """
476
+ Prefix = apps.get_model("ipam", "Prefix")
477
+ prefixes = Prefix.objects.filter(namespace=namespace).values_list("network", "prefix_length")
478
+ counter = collections.Counter(prefixes)
479
+ dupes = [p for p, cnt in counter.most_common() if cnt > 1]
480
+ return [f"{network}/{prefix_length}" for network, prefix_length in dupes]
481
+
482
+
483
+ def generate_parent_prefix(apps, address):
484
+ """
485
+ For a given `address`, generate a containing parent network address.
486
+
487
+ Args:
488
+ apps: Django apps module
489
+ address: Prefix/IPAddress instance or string
490
+
491
+ Returns:
492
+ netaddr.IPNetwork
493
+ """
494
+ cidr = validate_cidr(apps, address)
495
+ return cidr.cidr
496
+
497
+
498
+ def get_closest_parent(apps, obj, qs):
499
+ """
500
+ This is forklifted from `Prefix.objects.get_closest_parent()` so that it can safely be used in
501
+ migrations.
502
+
503
+ Return the closest matching parent Prefix for a `cidr` even if it doesn't exist in the database.
504
+
505
+ Args:
506
+ obj: Prefix/IPAddress instance
507
+ qs (QuerySet): QuerySet of Prefix objects
508
+
509
+ Returns:
510
+ Prefix or a filtered queryset
511
+ """
512
+ # Validate that it's a real CIDR
513
+ cidr = validate_cidr(apps, obj)
514
+ broadcast = str(cidr.broadcast or cidr.ip)
515
+
516
+ Prefix = apps.get_model("ipam", "Prefix")
517
+ IPAddress = apps.get_model("ipam", "IPAddress")
518
+
519
+ # Prepare the queryset filter
520
+ lookup_kwargs = {
521
+ "ip_version": cidr.version,
522
+ "network__lte": cidr.network,
523
+ "broadcast__gte": broadcast,
524
+ }
525
+
526
+ if isinstance(obj, Prefix):
527
+ lookup_kwargs["prefix_length__lt"] = cidr.prefixlen
528
+ qs = qs.exclude(id=obj.id)
529
+ else:
530
+ lookup_kwargs["prefix_length__lte"] = cidr.prefixlen
531
+
532
+ # Search for possible ancestors by network/prefix, returning them in
533
+ # reverse prefix length order, so that we can choose the first one.
534
+ possible_ancestors = (
535
+ qs.filter(**lookup_kwargs)
536
+ .annotate(
537
+ custom_sort_order=models.Case(
538
+ models.When(tenant=obj.tenant, vrf=obj.vrf, then=models.Value(1)),
539
+ models.When(tenant__isnull=True, vrf=obj.vrf, then=models.Value(2)),
540
+ models.When(tenant=obj.tenant, vrf__isnull=True, then=models.Value(3)),
541
+ models.When(vrf=obj.vrf, then=models.Value(4)),
542
+ models.When(tenant__isnull=True, vrf__isnull=True, then=models.Value(5)),
543
+ models.When(vrf__isnull=True, then=models.Value(6)),
544
+ default=models.Value(7),
545
+ )
546
+ )
547
+ .order_by("-prefix_length", "custom_sort_order")
548
+ )
549
+
550
+ if isinstance(obj, IPAddress):
551
+ # IP should not fall back to less specific prefixes
552
+ if not possible_ancestors.exists():
553
+ return qs.none()
554
+ prefix_length = possible_ancestors.first().prefix_length
555
+ return possible_ancestors.filter(prefix_length=prefix_length)
556
+
557
+ # If we've got any matches, the first one is our closest parent.
558
+ try:
559
+ return possible_ancestors[0]
560
+ except IndexError:
561
+ raise Prefix.DoesNotExist(f"Could not determine parent Prefix for {cidr}")
562
+
563
+
564
+ def get_next_vrf_cleanup_namespace(apps, vrf):
565
+ """
566
+ Try to get the next available Cleanup Namespace based on `vrf` found in the "Global" Namespace.
567
+
568
+ The Global Namespace is always scanned first to check for duplicates. If none are found then the
569
+ Global Namespace will be returned, otherwise Cleanup Namespaces will be iterated until one
570
+ without a duplicate is found. If a Namespace without duplicates cannot be found, a new one will
571
+ be created.
572
+
573
+ Args:
574
+ apps: Django apps module
575
+ vrf (VRF): VRF instance
576
+
577
+ Returns:
578
+ Namespace
579
+ """
580
+ Namespace = apps.get_model("ipam", "Namespace")
581
+ VRF = apps.get_model("ipam", "VRF")
582
+
583
+ counter = 1
584
+ vrf_prefixes = vrf.prefixes.all()
585
+
586
+ global_ns = Namespace.objects.get(name=GLOBAL_NS)
587
+ global_ns_prefixes = global_ns.prefixes.exclude(vrf=vrf)
588
+ global_dupe_prefixes = compare_prefix_querysets(vrf_prefixes, global_ns_prefixes)
589
+ global_dupe_vrfs = VRF.objects.filter(namespace=global_ns, name=vrf.name).exclude(pk=vrf.pk).exists()
590
+
591
+ if global_dupe_prefixes:
592
+ print(f" VRF {vrf.name} has duplicate prefixes with NS {global_ns.name}")
593
+
594
+ if global_dupe_vrfs:
595
+ print(f" VRF {vrf.name} has duplicate VRF name with NS {global_ns.name}")
596
+
597
+ if not any([global_dupe_prefixes, global_dupe_vrfs]):
598
+ return global_ns
599
+
600
+ # Iterate non-enforce_unique VRFS
601
+ # - Compare duplicate prefixes for each VRF
602
+ # - If a VRF has duplicates, it moves to a new namespace
603
+ while True:
604
+ base_name = f"{BASE_NAME} ({counter})"
605
+ namespace, created = Namespace.objects.get_or_create(
606
+ name=base_name,
607
+ defaults={"description": DESCRIPTION},
608
+ )
609
+ if created:
610
+ return namespace
611
+
612
+ ns_prefixes = namespace.prefixes.exclude(vrf=vrf)
613
+ dupe_prefixes = compare_prefix_querysets(vrf_prefixes, ns_prefixes)
614
+ dupe_vrfs = VRF.objects.filter(namespace=namespace, name=vrf.name).exclude(pk=vrf.pk).exists()
615
+
616
+ if dupe_prefixes:
617
+ print(f" VRF {vrf.name} has duplicate prefixes with NS {namespace.name}")
618
+
619
+ if dupe_vrfs:
620
+ print(f" VRF {vrf.name} has duplicate VRF name with NS {namespace.name}")
621
+
622
+ if any([dupe_prefixes, dupe_vrfs]):
623
+ counter += 1
624
+ continue
625
+
626
+ return namespace
627
+
628
+
629
+ def get_next_prefix_cleanup_namespace(apps, prefix, base_name=BASE_NAME):
630
+ """
631
+ Try to ge the next avialable Cleanup Namespace based on `prefix` found in the "Global" Namespace.
632
+
633
+ It is implied that the Prefix will be in the Global Namespace, so Cleanup Namespaces are
634
+ automatically iterated to find a suitable match that has no duplicates. If a Namespace without
635
+ duplicates cannot be found, a new one will be created.
636
+
637
+ Args:
638
+ apps: Django apps module
639
+ prefix (Prefix): Prefix instance
640
+ base_name (str): Base name to use for the Namespace
641
+
642
+ Returns:
643
+ Namespace
644
+ """
645
+ Namespace = apps.get_model("ipam", "Namespace")
646
+
647
+ counter = 1
648
+ while True:
649
+ name = f"{base_name} ({counter})"
650
+ namespace, created = Namespace.objects.get_or_create(
651
+ name=name,
652
+ defaults={"description": DESCRIPTION},
653
+ )
654
+ if created:
655
+ return namespace
656
+
657
+ cidr = f"{prefix.network}/{prefix.prefix_length}"
658
+ has_dupe = namespace.prefixes.filter(network=prefix.network, prefix_length=prefix.prefix_length).exists()
659
+
660
+ if has_dupe:
661
+ print(f" Prefix {cidr} is duplicated in NS {namespace.name}")
662
+ counter += 1
663
+ continue
664
+
665
+ return namespace
666
+
667
+
668
+ def validate_cidr(apps, value):
669
+ """
670
+ Validate whether `value` is a valid IPv4/IPv6 CIDR.
671
+
672
+ Args:
673
+ value (str): IP address
674
+
675
+ Returns:
676
+ netaddr.IPNetwork
677
+ """
678
+ IPAddress = apps.get_model("ipam", "IPAddress")
679
+ Prefix = apps.get_model("ipam", "Prefix")
680
+
681
+ if isinstance(value, IPAddress):
682
+ value = f"{value.host}/{value.prefix_length}"
683
+ elif isinstance(value, Prefix):
684
+ value = f"{value.network}/{value.prefix_length}"
685
+ else:
686
+ value = str(value)
687
+
688
+ try:
689
+ return netaddr.IPNetwork(value)
690
+ except netaddr.AddrFormatError as err:
691
+ raise ValidationError({"cidr": f"{value} does not appear to be an IPv4 or IPv6 network."}) from err
692
+
693
+
694
+ def ensure_correct_prefix_broadcast(apps):
695
+ """
696
+ Ensure that the prefix broadcast address is correct.
697
+
698
+ Args:
699
+ apps: Django apps module
700
+
701
+ Returns:
702
+ None
703
+ """
704
+ Prefix = apps.get_model("ipam", "Prefix")
705
+
706
+ for prefix in Prefix.objects.all():
707
+ true_broadcast = str(netaddr.IPNetwork(f"{prefix.network}/{prefix.prefix_length}")[-1])
708
+ if prefix.broadcast != true_broadcast:
709
+ print(
710
+ f"Updating {prefix.network}/{prefix.prefix_length} broadcast from {prefix.broadcast} to {true_broadcast}"
711
+ )
712
+ prefix.broadcast = true_broadcast
713
+ prefix.save()