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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (780) hide show
  1. nautobot/apps/api.py +6 -8
  2. nautobot/apps/forms.py +0 -2
  3. nautobot/apps/ui.py +0 -8
  4. nautobot/circuits/api/serializers.py +9 -117
  5. nautobot/circuits/api/urls.py +1 -1
  6. nautobot/circuits/api/views.py +0 -1
  7. nautobot/circuits/forms.py +0 -65
  8. nautobot/circuits/migrations/0014_related_name_changes.py +1 -1
  9. nautobot/circuits/migrations/0016_tagsfield.py +34 -0
  10. nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
  11. nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
  12. nautobot/circuits/models.py +3 -87
  13. nautobot/circuits/navigation.py +14 -69
  14. nautobot/circuits/signals.py +0 -2
  15. nautobot/circuits/tables.py +39 -1
  16. nautobot/circuits/tests/integration/test_relationships.py +9 -9
  17. nautobot/circuits/tests/test_api.py +4 -8
  18. nautobot/circuits/tests/test_filters.py +10 -4
  19. nautobot/circuits/tests/test_models.py +5 -1
  20. nautobot/circuits/tests/test_views.py +27 -5
  21. nautobot/circuits/views.py +18 -10
  22. nautobot/core/api/__init__.py +8 -2
  23. nautobot/core/api/fields.py +15 -6
  24. nautobot/core/api/filter_backends.py +3 -2
  25. nautobot/core/api/metadata.py +237 -30
  26. nautobot/core/api/mixins.py +94 -0
  27. nautobot/core/api/pagination.py +4 -0
  28. nautobot/core/api/parsers.py +154 -0
  29. nautobot/core/api/renderers.py +153 -2
  30. nautobot/core/api/schema.py +46 -2
  31. nautobot/core/api/serializers.py +377 -35
  32. nautobot/core/api/urls.py +11 -3
  33. nautobot/core/api/utils.py +174 -2
  34. nautobot/core/api/versioning.py +32 -10
  35. nautobot/core/api/views.py +266 -72
  36. nautobot/core/apps/__init__.py +138 -220
  37. nautobot/core/celery/__init__.py +112 -41
  38. nautobot/core/celery/backends.py +19 -12
  39. nautobot/core/celery/control.py +46 -0
  40. nautobot/core/celery/encoders.py +53 -0
  41. nautobot/core/celery/log.py +38 -0
  42. nautobot/core/celery/schedulers.py +23 -4
  43. nautobot/core/celery/task.py +1 -16
  44. nautobot/core/checks.py +0 -27
  45. nautobot/core/choices.py +0 -113
  46. nautobot/core/{cli.py → cli/__init__.py} +1 -1
  47. nautobot/core/cli/__main__.py +3 -0
  48. nautobot/core/constants.py +0 -24
  49. nautobot/core/context_processors.py +12 -0
  50. nautobot/core/filters.py +2 -2
  51. nautobot/core/forms/__init__.py +0 -4
  52. nautobot/core/forms/fields.py +38 -65
  53. nautobot/core/forms/forms.py +4 -1
  54. nautobot/core/forms/utils.py +0 -52
  55. nautobot/core/graphql/schema.py +4 -27
  56. nautobot/core/jobs/__init__.py +75 -0
  57. nautobot/core/management/commands/build_ui.py +255 -0
  58. nautobot/core/management/commands/generate_test_data.py +3 -2
  59. nautobot/core/management/commands/post_upgrade.py +24 -24
  60. nautobot/core/models/__init__.py +26 -1
  61. nautobot/core/models/fields.py +24 -5
  62. nautobot/core/models/generics.py +2 -42
  63. nautobot/core/models/managers.py +5 -0
  64. nautobot/core/models/name_color_content_types.py +0 -14
  65. nautobot/core/models/tree_queries.py +14 -4
  66. nautobot/core/models/utils.py +5 -6
  67. nautobot/core/models/validators.py +17 -8
  68. nautobot/core/releases.py +8 -10
  69. nautobot/core/settings.py +80 -42
  70. nautobot/core/tables.py +5 -5
  71. nautobot/core/tasks.py +4 -7
  72. nautobot/core/templates/base.html +1 -49
  73. nautobot/core/templates/base_django.html +49 -0
  74. nautobot/core/templates/base_react.html +55 -0
  75. nautobot/core/templates/buttons/export.html +6 -4
  76. nautobot/core/templates/generic/object_bulk_create.html +10 -21
  77. nautobot/core/templates/generic/object_list.html +3 -1
  78. nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
  79. nautobot/core/templates/inc/footer.html +1 -0
  80. nautobot/core/templates/inc/javascript.html +0 -14
  81. nautobot/core/templates/inc/nav_menu.html +28 -33
  82. nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
  83. nautobot/core/templates/inc/relationships_table_rows.html +2 -2
  84. nautobot/core/templates/nautobot_config.py.j2 +8 -20
  85. nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
  86. nautobot/core/templates/rest_framework/api.html +8 -0
  87. nautobot/core/templatetags/buttons.py +32 -28
  88. nautobot/core/testing/__init__.py +47 -44
  89. nautobot/core/testing/api.py +362 -47
  90. nautobot/core/testing/filters.py +1 -1
  91. nautobot/core/testing/migrations.py +2 -0
  92. nautobot/core/testing/mixins.py +22 -9
  93. nautobot/core/testing/schema.py +2 -1
  94. nautobot/core/testing/views.py +21 -46
  95. nautobot/core/tests/integration/test_filters.py +17 -8
  96. nautobot/core/tests/integration/test_navbar.py +11 -34
  97. nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
  98. nautobot/core/tests/nautobot_config.py +2 -3
  99. nautobot/core/tests/test_api.py +290 -21
  100. nautobot/core/tests/test_checks.py +0 -7
  101. nautobot/core/tests/test_filters.py +107 -59
  102. nautobot/core/tests/test_forms.py +26 -92
  103. nautobot/core/tests/test_graphql.py +110 -77
  104. nautobot/core/tests/test_logging.py +4 -0
  105. nautobot/core/tests/test_managers.py +3 -1
  106. nautobot/core/tests/test_models.py +2 -0
  107. nautobot/core/tests/test_paginator.py +3 -1
  108. nautobot/core/tests/test_releases.py +12 -12
  109. nautobot/core/tests/test_templatetags_helpers.py +4 -4
  110. nautobot/core/tests/test_utils.py +32 -68
  111. nautobot/core/tests/test_views.py +12 -15
  112. nautobot/core/utils/data.py +17 -0
  113. nautobot/core/utils/deprecation.py +9 -6
  114. nautobot/core/utils/filtering.py +8 -3
  115. nautobot/core/utils/git.py +12 -4
  116. nautobot/core/utils/lookup.py +3 -1
  117. nautobot/core/utils/requests.py +1 -104
  118. nautobot/core/views/__init__.py +1 -0
  119. nautobot/core/views/generic.py +75 -110
  120. nautobot/core/views/mixins.py +52 -61
  121. nautobot/core/views/renderers.py +6 -7
  122. nautobot/core/views/utils.py +80 -0
  123. nautobot/dcim/api/serializers.py +160 -667
  124. nautobot/dcim/api/urls.py +1 -1
  125. nautobot/dcim/api/views.py +7 -44
  126. nautobot/dcim/choices.py +2 -0
  127. nautobot/dcim/filters/__init__.py +21 -0
  128. nautobot/dcim/form_mixins.py +1 -27
  129. nautobot/dcim/forms.py +19 -765
  130. nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -1
  131. nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -13
  132. nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -1
  133. nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -1
  134. nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
  135. nautobot/dcim/migrations/0035_related_name_changes.py +1 -1
  136. nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -1
  137. nautobot/dcim/migrations/0040_tagsfield.py +109 -0
  138. nautobot/dcim/migrations/{0040_ipam__namespaces.py → 0041_ipam__namespaces.py} +1 -1
  139. nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
  140. nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
  141. nautobot/dcim/models/cables.py +3 -33
  142. nautobot/dcim/models/device_component_templates.py +6 -0
  143. nautobot/dcim/models/device_components.py +12 -198
  144. nautobot/dcim/models/devices.py +30 -143
  145. nautobot/dcim/models/locations.py +3 -64
  146. nautobot/dcim/models/power.py +3 -50
  147. nautobot/dcim/models/racks.py +7 -84
  148. nautobot/dcim/navigation.py +141 -467
  149. nautobot/dcim/signals.py +0 -2
  150. nautobot/dcim/tables/locations.py +2 -2
  151. nautobot/dcim/tables/power.py +1 -2
  152. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
  153. nautobot/dcim/templates/dcim/devicetype.html +2 -2
  154. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
  155. nautobot/dcim/templates/dcim/location.html +16 -1
  156. nautobot/dcim/templates/dcim/locationtype.html +15 -0
  157. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
  158. nautobot/dcim/templates/dcim/rackgroup.html +0 -12
  159. nautobot/dcim/tests/test_api.py +166 -81
  160. nautobot/dcim/tests/test_cablepaths.py +41 -35
  161. nautobot/dcim/tests/test_filters.py +67 -23
  162. nautobot/dcim/tests/test_forms.py +5 -205
  163. nautobot/dcim/tests/test_graphql.py +7 -2
  164. nautobot/dcim/tests/test_migrations.py +6 -11
  165. nautobot/dcim/tests/test_models.py +182 -110
  166. nautobot/dcim/tests/test_natural_ordering.py +11 -8
  167. nautobot/dcim/tests/test_signals.py +6 -3
  168. nautobot/dcim/tests/test_views.py +197 -175
  169. nautobot/dcim/urls.py +11 -16
  170. nautobot/dcim/views.py +7 -134
  171. nautobot/docs/additional-features/caching.md +6 -87
  172. nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
  173. nautobot/docs/additional-features/jobs.md +177 -195
  174. nautobot/docs/administration/nautobot-server.md +6 -21
  175. nautobot/docs/administration/replicating-nautobot.md +0 -10
  176. nautobot/docs/configuration/optional-settings.md +32 -41
  177. nautobot/docs/configuration/required-settings.md +11 -52
  178. nautobot/docs/development/application-registry.md +2 -13
  179. nautobot/docs/development/extending-models.md +15 -17
  180. nautobot/docs/development/generic-views.md +0 -2
  181. nautobot/docs/development/getting-started.md +55 -5
  182. nautobot/docs/development/navigation-menu.md +22 -93
  183. nautobot/docs/development/react-ui.md +105 -0
  184. nautobot/docs/development/role-internals.md +1 -3
  185. nautobot/docs/development/style-guide.md +6 -4
  186. nautobot/docs/index.md +3 -2
  187. nautobot/docs/installation/migrating-from-netbox.md +11 -42
  188. nautobot/docs/installation/nautobot.md +1 -1
  189. nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  190. nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  191. nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  192. nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  193. nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
  194. nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  195. nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  196. nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  197. nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  198. nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  199. nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  200. nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  201. nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  202. nautobot/docs/installation/upgrading-from-nautobot-v1.md +170 -747
  203. nautobot/docs/models/dcim/device.md +3 -0
  204. nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
  205. nautobot/docs/models/extras/computedfield.md +4 -4
  206. nautobot/docs/models/extras/gitrepository.md +3 -0
  207. nautobot/docs/models/extras/job.md +1 -0
  208. nautobot/docs/models/extras/jobbutton.md +18 -13
  209. nautobot/docs/models/extras/jobhook.md +7 -4
  210. nautobot/docs/models/extras/jobresult.md +6 -2
  211. nautobot/docs/models/extras/relationship.md +2 -2
  212. nautobot/docs/models/extras/status.md +6 -19
  213. nautobot/docs/models/ipam/ipaddress.md +3 -0
  214. nautobot/docs/models/virtualization/virtualmachine.md +3 -0
  215. nautobot/docs/plugins/development.md +83 -21
  216. nautobot/docs/release-notes/version-1.5.md +53 -0
  217. nautobot/docs/release-notes/version-2.0.md +180 -0
  218. nautobot/docs/requirements.txt +1 -0
  219. nautobot/docs/rest-api/overview.md +384 -215
  220. nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
  221. nautobot/extras/admin.py +3 -5
  222. nautobot/extras/api/customfields.py +15 -39
  223. nautobot/extras/api/fields.py +0 -11
  224. nautobot/extras/api/mixins.py +45 -0
  225. nautobot/extras/api/relationships.py +63 -158
  226. nautobot/extras/api/serializers.py +165 -700
  227. nautobot/extras/api/urls.py +1 -1
  228. nautobot/extras/api/views.py +294 -280
  229. nautobot/extras/apps.py +4 -7
  230. nautobot/extras/choices.py +11 -9
  231. nautobot/extras/constants.py +9 -3
  232. nautobot/extras/datasources/__init__.py +2 -0
  233. nautobot/extras/datasources/git.py +135 -186
  234. nautobot/extras/datasources/registry.py +25 -35
  235. nautobot/extras/filters/__init__.py +20 -19
  236. nautobot/extras/filters/mixins.py +4 -4
  237. nautobot/extras/forms/forms.py +63 -127
  238. nautobot/extras/forms/mixins.py +23 -51
  239. nautobot/extras/health_checks.py +0 -33
  240. nautobot/extras/jobs.py +387 -565
  241. nautobot/extras/management/commands/runjob.py +24 -62
  242. nautobot/extras/managers.py +30 -7
  243. nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
  244. nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -1
  245. nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -1
  246. nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -7
  247. nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -32
  248. nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -1
  249. nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -1
  250. nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
  251. nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -1
  252. nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -2
  253. nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -1
  254. nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -1
  255. nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
  256. nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -1
  257. nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
  258. nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -9
  259. nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -1
  260. nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
  261. nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
  262. nautobot/extras/migrations/{0076_remove_name_field_and_make_label_field_non_nullable.py → 0077_remove_name_field_and_make_label_field_non_nullable.py} +1 -1
  263. nautobot/extras/migrations/{0077_remove_slug.py → 0078_remove_slug.py} +1 -5
  264. nautobot/extras/migrations/0079_tagsfield.py +28 -0
  265. nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
  266. nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
  267. nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
  268. nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
  269. nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
  270. nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
  271. nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
  272. nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
  273. nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
  274. nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
  275. nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
  276. nautobot/extras/models/__init__.py +2 -3
  277. nautobot/extras/models/change_logging.py +0 -36
  278. nautobot/extras/models/customfields.py +39 -33
  279. nautobot/extras/models/datasources.py +48 -50
  280. nautobot/extras/models/groups.py +5 -6
  281. nautobot/extras/models/jobs.py +189 -321
  282. nautobot/extras/models/mixins.py +0 -71
  283. nautobot/extras/models/models.py +0 -19
  284. nautobot/extras/models/relationships.py +19 -13
  285. nautobot/extras/models/roles.py +0 -34
  286. nautobot/extras/models/secrets.py +2 -26
  287. nautobot/extras/models/statuses.py +6 -5
  288. nautobot/extras/models/tags.py +2 -17
  289. nautobot/extras/navigation.py +89 -307
  290. nautobot/extras/plugins/__init__.py +3 -120
  291. nautobot/extras/plugins/utils.py +0 -3
  292. nautobot/extras/plugins/validators.py +5 -4
  293. nautobot/extras/plugins/views.py +16 -3
  294. nautobot/extras/querysets.py +1 -7
  295. nautobot/extras/registry.py +3 -0
  296. nautobot/extras/signals.py +26 -60
  297. nautobot/extras/tables.py +34 -40
  298. nautobot/extras/tasks.py +0 -12
  299. nautobot/extras/templates/extras/configcontext.html +1 -1
  300. nautobot/extras/templates/extras/configcontextschema.html +16 -1
  301. nautobot/extras/templates/extras/customfield.html +0 -13
  302. nautobot/extras/templates/extras/gitrepository.html +3 -3
  303. nautobot/extras/templates/extras/inc/jobresult.html +10 -0
  304. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  305. nautobot/extras/templates/extras/job.html +35 -25
  306. nautobot/extras/templates/extras/job_approval_request.html +15 -30
  307. nautobot/extras/templates/extras/job_detail.html +13 -31
  308. nautobot/extras/templates/extras/job_edit.html +15 -17
  309. nautobot/extras/templates/extras/jobresult.html +24 -6
  310. nautobot/extras/templates/extras/scheduledjob.html +2 -2
  311. nautobot/extras/templates/extras/secret.html +28 -0
  312. nautobot/extras/templatetags/job_buttons.py +1 -0
  313. nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
  314. nautobot/extras/test_jobs/atomic_transaction.py +53 -0
  315. nautobot/extras/test_jobs/dry_run.py +29 -0
  316. nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
  317. nautobot/extras/test_jobs/duplicate_name2.py +9 -0
  318. nautobot/extras/test_jobs/fail.py +23 -0
  319. nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
  320. nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
  321. nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
  322. nautobot/extras/test_jobs/file_upload_pass.py +25 -0
  323. nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
  324. nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
  325. nautobot/extras/test_jobs/job_button_receiver.py +28 -0
  326. nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
  327. nautobot/extras/test_jobs/job_variables.py +88 -0
  328. nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
  329. nautobot/extras/test_jobs/log_redaction.py +20 -0
  330. nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
  331. nautobot/extras/test_jobs/modify_db.py +25 -0
  332. nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
  333. nautobot/extras/test_jobs/object_var_optional.py +21 -0
  334. nautobot/extras/test_jobs/object_var_required.py +21 -0
  335. nautobot/extras/test_jobs/object_vars.py +26 -0
  336. nautobot/extras/test_jobs/pass.py +25 -0
  337. nautobot/extras/test_jobs/profiling.py +32 -0
  338. nautobot/extras/test_jobs/read_only_job.py +15 -0
  339. nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
  340. nautobot/extras/{tests/example_jobs/test_soft_time_limit_greater_than_time_limit.py → test_jobs/soft_time_limit_greater_than_time_limit.py} +5 -1
  341. nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
  342. nautobot/extras/tests/integration/test_computedfields.py +1 -1
  343. nautobot/extras/tests/integration/test_configcontextschema.py +5 -3
  344. nautobot/extras/tests/integration/test_customfields.py +4 -2
  345. nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
  346. nautobot/extras/tests/integration/test_jobs.py +25 -27
  347. nautobot/extras/tests/integration/test_notes.py +8 -4
  348. nautobot/extras/tests/integration/test_relationships.py +2 -2
  349. nautobot/extras/tests/test_api.py +649 -642
  350. nautobot/extras/tests/test_changelog.py +3 -3
  351. nautobot/extras/tests/test_context_managers.py +5 -3
  352. nautobot/extras/tests/test_customfields.py +92 -50
  353. nautobot/extras/tests/test_datasources.py +189 -112
  354. nautobot/extras/tests/test_dynamicgroups.py +7 -8
  355. nautobot/extras/tests/test_filters.py +137 -89
  356. nautobot/extras/tests/test_forms.py +73 -75
  357. nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
  358. nautobot/extras/tests/test_jobs.py +262 -263
  359. nautobot/extras/tests/test_migrations.py +4 -3
  360. nautobot/extras/tests/test_models.py +116 -161
  361. nautobot/extras/tests/test_plugins.py +38 -60
  362. nautobot/extras/tests/test_relationships.py +167 -120
  363. nautobot/extras/tests/test_tags.py +6 -11
  364. nautobot/extras/tests/test_utils.py +31 -1
  365. nautobot/extras/tests/test_views.py +201 -145
  366. nautobot/extras/tests/test_webhooks.py +6 -2
  367. nautobot/extras/urls.py +42 -42
  368. nautobot/extras/utils.py +137 -163
  369. nautobot/extras/views.py +78 -152
  370. nautobot/ipam/api/fields.py +17 -0
  371. nautobot/ipam/api/serializers.py +58 -164
  372. nautobot/ipam/api/urls.py +1 -1
  373. nautobot/ipam/api/views.py +3 -2
  374. nautobot/ipam/apps.py +1 -2
  375. nautobot/ipam/filters.py +1 -10
  376. nautobot/ipam/forms.py +4 -177
  377. nautobot/ipam/lookups.py +1 -0
  378. nautobot/ipam/management/commands/__init__.py +0 -0
  379. nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
  380. nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -1
  381. nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -38
  382. nautobot/ipam/migrations/0020_related_name_changes.py +1 -1
  383. nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +2 -2
  384. nautobot/ipam/migrations/0028_tagsfield.py +44 -0
  385. nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
  386. nautobot/ipam/migrations/{0028_ipam__namespaces.py → 0030_ipam__namespaces.py} +77 -28
  387. nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
  388. nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
  389. nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
  390. nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
  391. nautobot/ipam/models.py +100 -236
  392. nautobot/ipam/navigation.py +36 -181
  393. nautobot/ipam/querysets.py +20 -25
  394. nautobot/ipam/signals.py +49 -6
  395. nautobot/ipam/tables.py +10 -3
  396. nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
  397. nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
  398. nautobot/ipam/templates/ipam/namespace_retrieve.html +17 -4
  399. nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
  400. nautobot/ipam/templates/ipam/prefix.html +1 -1
  401. nautobot/ipam/templates/ipam/vlangroup.html +0 -13
  402. nautobot/ipam/templates/ipam/vrf_edit.html +6 -0
  403. nautobot/ipam/tests/integration/test_prefixes.py +3 -26
  404. nautobot/ipam/tests/test_api.py +22 -19
  405. nautobot/ipam/tests/test_filters.py +59 -23
  406. nautobot/ipam/tests/test_migrations.py +6 -10
  407. nautobot/ipam/tests/test_models.py +323 -198
  408. nautobot/ipam/tests/test_ordering.py +2 -2
  409. nautobot/ipam/tests/test_querysets.py +44 -24
  410. nautobot/ipam/tests/test_views.py +73 -26
  411. nautobot/ipam/urls.py +16 -0
  412. nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
  413. nautobot/ipam/utils/migrations.py +713 -0
  414. nautobot/ipam/views.py +137 -20
  415. nautobot/project-static/docs/404.html +1178 -10
  416. nautobot/project-static/docs/additional-features/caching.html +1224 -159
  417. nautobot/project-static/docs/additional-features/change-logging.html +1180 -12
  418. nautobot/project-static/docs/additional-features/config-contexts.html +1180 -12
  419. nautobot/project-static/docs/additional-features/graphql.html +1179 -11
  420. nautobot/project-static/docs/additional-features/healthcheck.html +1180 -12
  421. nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1184 -12
  422. nautobot/project-static/docs/additional-features/jobs.html +1514 -328
  423. nautobot/project-static/docs/additional-features/napalm.html +1180 -12
  424. nautobot/project-static/docs/additional-features/prometheus-metrics.html +1180 -12
  425. nautobot/project-static/docs/additional-features/template-filters.html +1180 -12
  426. nautobot/project-static/docs/administration/celery-queues.html +1178 -10
  427. nautobot/project-static/docs/administration/nautobot-server.html +1451 -304
  428. nautobot/project-static/docs/administration/nautobot-shell.html +1178 -10
  429. nautobot/project-static/docs/administration/permissions.html +1178 -10
  430. nautobot/project-static/docs/administration/replicating-nautobot.html +1262 -113
  431. nautobot/project-static/docs/apps/index.html +1178 -10
  432. nautobot/project-static/docs/apps/nautobot-apps.html +1178 -10
  433. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1580 -426
  434. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1178 -10
  435. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3481 -1838
  436. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1178 -10
  437. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1178 -10
  438. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1185 -11
  439. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1719 -551
  440. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2062 -930
  441. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1946 -659
  442. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1180 -12
  443. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1189 -21
  444. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9283 -6218
  445. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2734 -2122
  446. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1178 -10
  447. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2337 -1300
  448. nautobot/project-static/docs/configuration/authentication/ldap.html +1178 -10
  449. nautobot/project-static/docs/configuration/authentication/remote.html +1178 -10
  450. nautobot/project-static/docs/configuration/authentication/sso.html +1178 -10
  451. nautobot/project-static/docs/configuration/index.html +1178 -10
  452. nautobot/project-static/docs/configuration/optional-settings.html +1311 -160
  453. nautobot/project-static/docs/configuration/required-settings.html +1312 -211
  454. nautobot/project-static/docs/core-functionality/circuits.html +1178 -10
  455. nautobot/project-static/docs/core-functionality/device-types.html +1178 -10
  456. nautobot/project-static/docs/core-functionality/devices.html +1182 -10
  457. nautobot/project-static/docs/core-functionality/ipam.html +1182 -10
  458. nautobot/project-static/docs/core-functionality/power.html +1178 -10
  459. nautobot/project-static/docs/core-functionality/secrets.html +1178 -10
  460. nautobot/project-static/docs/core-functionality/services.html +1178 -10
  461. nautobot/project-static/docs/core-functionality/sites-and-racks.html +1178 -10
  462. nautobot/project-static/docs/core-functionality/tenancy.html +1178 -10
  463. nautobot/project-static/docs/core-functionality/virtualization.html +1182 -10
  464. nautobot/project-static/docs/core-functionality/vlans.html +1179 -11
  465. nautobot/project-static/docs/development/application-registry.html +1190 -42
  466. nautobot/project-static/docs/development/best-practices.html +1178 -10
  467. nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1178 -10
  468. nautobot/project-static/docs/development/extending-models.html +1238 -83
  469. nautobot/project-static/docs/development/generic-views.html +1180 -14
  470. nautobot/project-static/docs/development/getting-started.html +1365 -90
  471. nautobot/project-static/docs/development/homepage.html +1178 -10
  472. nautobot/project-static/docs/development/index.html +1178 -10
  473. nautobot/project-static/docs/development/model-features.html +1178 -10
  474. nautobot/project-static/docs/development/natural-keys.html +1178 -10
  475. nautobot/project-static/docs/development/navigation-menu.html +1215 -125
  476. nautobot/project-static/docs/development/react-ui.html +4199 -0
  477. nautobot/project-static/docs/development/release-checklist.html +1178 -10
  478. nautobot/project-static/docs/development/role-internals.html +1179 -12
  479. nautobot/project-static/docs/development/style-guide.html +1188 -19
  480. nautobot/project-static/docs/development/templates.html +1178 -10
  481. nautobot/project-static/docs/development/testing.html +1178 -10
  482. nautobot/project-static/docs/development/user-preferences.html +1178 -10
  483. nautobot/project-static/docs/docker/index.html +1178 -10
  484. nautobot/project-static/docs/index.html +1183 -12
  485. nautobot/project-static/docs/installation/centos.html +1178 -10
  486. nautobot/project-static/docs/installation/external-authentication.html +1178 -10
  487. nautobot/project-static/docs/installation/http-server.html +1178 -10
  488. nautobot/project-static/docs/installation/index.html +1178 -10
  489. nautobot/project-static/docs/installation/migrating-from-netbox.html +1305 -189
  490. nautobot/project-static/docs/installation/migrating-from-postgresql.html +1178 -10
  491. nautobot/project-static/docs/installation/nautobot.html +1179 -11
  492. nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1178 -10
  493. nautobot/project-static/docs/installation/selinux-troubleshooting.html +1178 -10
  494. nautobot/project-static/docs/installation/services.html +1178 -10
  495. nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  496. nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  497. nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  498. nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  499. nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
  500. nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  501. nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  502. nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  503. nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  504. nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  505. nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  506. nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  507. nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  508. nautobot/project-static/docs/installation/ubuntu.html +1178 -10
  509. nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +3823 -2152
  510. nautobot/project-static/docs/installation/upgrading.html +1178 -10
  511. nautobot/project-static/docs/models/circuits/circuit.html +1293 -103
  512. nautobot/project-static/docs/models/circuits/circuittermination.html +1293 -103
  513. nautobot/project-static/docs/models/circuits/circuittype.html +1293 -103
  514. nautobot/project-static/docs/models/circuits/provider.html +1293 -103
  515. nautobot/project-static/docs/models/circuits/providernetwork.html +1293 -103
  516. nautobot/project-static/docs/models/dcim/cable.html +1324 -103
  517. nautobot/project-static/docs/models/dcim/consoleport.html +1293 -103
  518. nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1293 -103
  519. nautobot/project-static/docs/models/dcim/consoleserverport.html +1293 -103
  520. nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1293 -103
  521. nautobot/project-static/docs/models/dcim/device.html +1326 -132
  522. nautobot/project-static/docs/models/dcim/devicebay.html +1293 -103
  523. nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1293 -103
  524. nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1379 -97
  525. nautobot/project-static/docs/models/dcim/devicetype.html +1293 -103
  526. nautobot/project-static/docs/models/dcim/frontport.html +1293 -103
  527. nautobot/project-static/docs/models/dcim/frontporttemplate.html +1293 -103
  528. nautobot/project-static/docs/models/dcim/interface.html +1293 -103
  529. nautobot/project-static/docs/models/dcim/interfacetemplate.html +1293 -103
  530. nautobot/project-static/docs/models/dcim/inventoryitem.html +1293 -103
  531. nautobot/project-static/docs/models/dcim/location.html +1293 -103
  532. nautobot/project-static/docs/models/dcim/locationtype.html +1293 -103
  533. nautobot/project-static/docs/models/dcim/manufacturer.html +1292 -102
  534. nautobot/project-static/docs/models/dcim/platform.html +1272 -82
  535. nautobot/project-static/docs/models/dcim/powerfeed.html +1270 -80
  536. nautobot/project-static/docs/models/dcim/poweroutlet.html +1272 -82
  537. nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1272 -82
  538. nautobot/project-static/docs/models/dcim/powerpanel.html +1270 -80
  539. nautobot/project-static/docs/models/dcim/powerport.html +1272 -82
  540. nautobot/project-static/docs/models/dcim/powerporttemplate.html +1272 -82
  541. nautobot/project-static/docs/models/dcim/rack.html +1272 -82
  542. nautobot/project-static/docs/models/dcim/rackgroup.html +1272 -82
  543. nautobot/project-static/docs/models/dcim/rackreservation.html +1272 -82
  544. nautobot/project-static/docs/models/dcim/rearport.html +1286 -96
  545. nautobot/project-static/docs/models/dcim/rearporttemplate.html +1286 -96
  546. nautobot/project-static/docs/models/dcim/region.html +1178 -10
  547. nautobot/project-static/docs/models/dcim/site.html +1178 -10
  548. nautobot/project-static/docs/models/dcim/virtualchassis.html +1284 -94
  549. nautobot/project-static/docs/models/extras/computedfield.html +1184 -16
  550. nautobot/project-static/docs/models/extras/configcontext.html +1314 -86
  551. nautobot/project-static/docs/models/extras/configcontextschema.html +1276 -86
  552. nautobot/project-static/docs/models/extras/customfield.html +1180 -12
  553. nautobot/project-static/docs/models/extras/customlink.html +1180 -12
  554. nautobot/project-static/docs/models/extras/dynamicgroup.html +1180 -12
  555. nautobot/project-static/docs/models/extras/exporttemplate.html +1180 -12
  556. nautobot/project-static/docs/models/extras/gitrepository.html +1184 -12
  557. nautobot/project-static/docs/models/extras/graphqlquery.html +1321 -86
  558. nautobot/project-static/docs/models/extras/imageattachment.html +1276 -86
  559. nautobot/project-static/docs/models/extras/job.html +1277 -86
  560. nautobot/project-static/docs/models/extras/jobbutton.html +1201 -29
  561. nautobot/project-static/docs/models/extras/jobhook.html +1188 -16
  562. nautobot/project-static/docs/models/extras/joblogentry.html +1274 -84
  563. nautobot/project-static/docs/models/extras/jobresult.html +1364 -169
  564. nautobot/project-static/docs/models/extras/note.html +1180 -12
  565. nautobot/project-static/docs/models/extras/relationship.html +1182 -14
  566. nautobot/project-static/docs/models/extras/role.html +1320 -86
  567. nautobot/project-static/docs/models/extras/secret.html +1314 -86
  568. nautobot/project-static/docs/models/extras/secretsgroup.html +1276 -86
  569. nautobot/project-static/docs/models/extras/status.html +1188 -59
  570. nautobot/project-static/docs/models/extras/tag.html +1180 -12
  571. nautobot/project-static/docs/models/extras/webhook.html +1180 -12
  572. nautobot/project-static/docs/models/ipam/ipaddress.html +1327 -102
  573. nautobot/project-static/docs/models/ipam/prefix.html +1276 -86
  574. nautobot/project-static/docs/models/ipam/rir.html +1276 -86
  575. nautobot/project-static/docs/models/ipam/routetarget.html +1276 -86
  576. nautobot/project-static/docs/models/ipam/service.html +1276 -86
  577. nautobot/project-static/docs/models/ipam/vlan.html +1276 -86
  578. nautobot/project-static/docs/models/ipam/vlangroup.html +1276 -86
  579. nautobot/project-static/docs/models/ipam/vrf.html +1276 -86
  580. nautobot/project-static/docs/models/tenancy/tenant.html +1276 -86
  581. nautobot/project-static/docs/models/tenancy/tenantgroup.html +1276 -86
  582. nautobot/project-static/docs/models/users/objectpermission.html +1314 -86
  583. nautobot/project-static/docs/models/users/token.html +1276 -86
  584. nautobot/project-static/docs/models/virtualization/cluster.html +1276 -86
  585. nautobot/project-static/docs/models/virtualization/clustergroup.html +1276 -86
  586. nautobot/project-static/docs/models/virtualization/clustertype.html +1276 -86
  587. nautobot/project-static/docs/models/virtualization/virtualmachine.html +1321 -127
  588. nautobot/project-static/docs/models/virtualization/vminterface.html +1276 -86
  589. nautobot/project-static/docs/objects.inv +0 -0
  590. nautobot/project-static/docs/plugins/development.html +1726 -495
  591. nautobot/project-static/docs/plugins/index.html +1178 -10
  592. nautobot/project-static/docs/plugins/porting-from-netbox.html +1178 -10
  593. nautobot/project-static/docs/release-notes/index.html +1178 -10
  594. nautobot/project-static/docs/release-notes/version-1.0.html +1178 -10
  595. nautobot/project-static/docs/release-notes/version-1.1.html +1178 -10
  596. nautobot/project-static/docs/release-notes/version-1.2.html +1178 -10
  597. nautobot/project-static/docs/release-notes/version-1.3.html +1178 -10
  598. nautobot/project-static/docs/release-notes/version-1.4.html +1178 -10
  599. nautobot/project-static/docs/release-notes/version-1.5.html +1608 -225
  600. nautobot/project-static/docs/release-notes/version-2.0.html +1547 -47
  601. nautobot/project-static/docs/requirements.txt +1 -0
  602. nautobot/project-static/docs/rest-api/authentication.html +1179 -11
  603. nautobot/project-static/docs/rest-api/filtering.html +1178 -10
  604. nautobot/project-static/docs/rest-api/overview.html +1841 -446
  605. nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
  606. nautobot/project-static/docs/search/search_index.json +1 -1
  607. nautobot/project-static/docs/sitemap.xml +197 -187
  608. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  609. nautobot/project-static/docs/user-guides/custom-fields.html +1178 -10
  610. nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1178 -10
  611. nautobot/project-static/docs/user-guides/getting-started/index.html +1178 -10
  612. nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1178 -10
  613. nautobot/project-static/docs/user-guides/getting-started/ipam.html +1178 -10
  614. nautobot/project-static/docs/user-guides/getting-started/platforms.html +1178 -10
  615. nautobot/project-static/docs/user-guides/getting-started/regions.html +1178 -10
  616. nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1178 -10
  617. nautobot/project-static/docs/user-guides/getting-started/tenants.html +1178 -10
  618. nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1178 -10
  619. nautobot/project-static/docs/user-guides/git-data-source.html +1178 -10
  620. nautobot/project-static/docs/user-guides/graphql.html +1178 -10
  621. nautobot/project-static/docs/user-guides/relationships.html +1178 -10
  622. nautobot/project-static/docs/user-guides/s3-django-storage.html +1178 -10
  623. nautobot/project-static/js/forms.js +16 -9
  624. nautobot/project-static/js/theme.js +5 -0
  625. nautobot/tenancy/api/serializers.py +4 -32
  626. nautobot/tenancy/api/urls.py +1 -1
  627. nautobot/tenancy/forms.py +0 -28
  628. nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
  629. nautobot/tenancy/models.py +0 -25
  630. nautobot/tenancy/navigation.py +6 -39
  631. nautobot/tenancy/templates/tenancy/tenant.html +12 -12
  632. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  633. nautobot/tenancy/tests/test_api.py +1 -3
  634. nautobot/tenancy/tests/test_filters.py +10 -5
  635. nautobot/tenancy/views.py +0 -2
  636. nautobot/ui/.eslintignore +6 -0
  637. nautobot/ui/.gitignore +10 -0
  638. nautobot/ui/.prettierignore +9 -0
  639. nautobot/ui/.prettierrc +4 -0
  640. nautobot/ui/README.md +33 -0
  641. nautobot/ui/app_imports.js.j2 +7 -0
  642. nautobot/ui/craco.config.js +46 -0
  643. nautobot/ui/jsconfig-base.json +11 -0
  644. nautobot/ui/jsconfig.json +5 -0
  645. nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
  646. nautobot/ui/package-lock.json +21451 -0
  647. nautobot/ui/package.json +70 -0
  648. nautobot/ui/public/index.html +47 -0
  649. nautobot/ui/public/logo192.png +0 -0
  650. nautobot/ui/public/logo512.png +0 -0
  651. nautobot/ui/public/manifest.json +25 -0
  652. nautobot/ui/public/nautobot_logo.svg +131 -0
  653. nautobot/ui/public/robots.txt +3 -0
  654. nautobot/ui/src/App.js +71 -0
  655. nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
  656. nautobot/ui/src/components/AppTab.js +40 -0
  657. nautobot/ui/src/components/Apps.js +60 -0
  658. nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
  659. nautobot/ui/src/components/HomePanel.js +58 -0
  660. nautobot/ui/src/components/JobHistoryTable.js +78 -0
  661. nautobot/ui/src/components/Layout.js +53 -0
  662. nautobot/ui/src/components/LoadingWidget.js +25 -0
  663. nautobot/ui/src/components/Navbar.js +116 -0
  664. nautobot/ui/src/components/NotificationPopover.js +27 -0
  665. nautobot/ui/src/components/ObjectListTable.js +209 -0
  666. nautobot/ui/src/components/ReferenceDataTag.js +35 -0
  667. nautobot/ui/src/components/RouterButton.js +10 -0
  668. nautobot/ui/src/components/RouterLink.js +10 -0
  669. nautobot/ui/src/components/SidebarNav.js +147 -0
  670. nautobot/ui/src/components/Table.js +48 -0
  671. nautobot/ui/src/components/TableItem.js +71 -0
  672. nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
  673. nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
  674. nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
  675. nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
  676. nautobot/ui/src/components/__tests__/Table.test.js +36 -0
  677. nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
  678. nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
  679. nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
  680. nautobot/ui/src/components/pagination.js +93 -0
  681. nautobot/ui/src/components/paginator.js +79 -0
  682. nautobot/ui/src/components/paginator_form.js +43 -0
  683. nautobot/ui/src/components/usePagination.js +57 -0
  684. nautobot/ui/src/constants/apiPath.js +10 -0
  685. nautobot/ui/src/constants/icons.js +15 -0
  686. nautobot/ui/src/constants/size.js +15 -0
  687. nautobot/ui/src/index.js +65 -0
  688. nautobot/ui/src/reportWebVitals.js +15 -0
  689. nautobot/ui/src/router.js +77 -0
  690. nautobot/ui/src/utils/api.js +131 -0
  691. nautobot/ui/src/utils/app-import.js +15 -0
  692. nautobot/ui/src/utils/color.js +15 -0
  693. nautobot/ui/src/utils/date.js +14 -0
  694. nautobot/ui/src/utils/index.js +15 -0
  695. nautobot/ui/src/utils/navigation.js +32 -0
  696. nautobot/ui/src/utils/session.js +64 -0
  697. nautobot/ui/src/utils/store.js +242 -0
  698. nautobot/ui/src/utils/string.js +6 -0
  699. nautobot/ui/src/utils/url.js +4 -0
  700. nautobot/ui/src/views/Home.js +138 -0
  701. nautobot/ui/src/views/InstalledApps.js +80 -0
  702. nautobot/ui/src/views/Login.js +48 -0
  703. nautobot/ui/src/views/Logout.js +20 -0
  704. nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
  705. nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
  706. nautobot/ui/src/views/__tests__/Login.test.js +15 -0
  707. nautobot/ui/src/views/generic/GenericView.js +142 -0
  708. nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
  709. nautobot/ui/src/views/generic/ObjectList.js +127 -0
  710. nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
  711. nautobot/users/admin.py +1 -1
  712. nautobot/users/api/serializers.py +51 -61
  713. nautobot/users/api/urls.py +1 -1
  714. nautobot/users/api/views.py +53 -2
  715. nautobot/users/tests/test_api.py +110 -25
  716. nautobot/virtualization/api/serializers.py +18 -130
  717. nautobot/virtualization/api/urls.py +1 -1
  718. nautobot/virtualization/api/views.py +1 -22
  719. nautobot/virtualization/forms.py +13 -99
  720. nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -1
  721. nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -11
  722. nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -1
  723. nautobot/virtualization/migrations/0018_related_name_changes.py +1 -1
  724. nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
  725. nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
  726. nautobot/virtualization/migrations/{0021_ipam__namespaces.py → 0023_ipam__namespaces.py} +2 -2
  727. nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
  728. nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
  729. nautobot/virtualization/models.py +31 -123
  730. nautobot/virtualization/navigation.py +18 -99
  731. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -1
  732. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
  733. nautobot/virtualization/tests/test_api.py +25 -26
  734. nautobot/virtualization/tests/test_filters.py +41 -15
  735. nautobot/virtualization/tests/test_models.py +31 -7
  736. nautobot/virtualization/tests/test_views.py +42 -25
  737. nautobot/virtualization/views.py +7 -6
  738. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +3 -7
  739. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +744 -602
  740. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
  741. nautobot/circuits/api/nested_serializers.py +0 -69
  742. nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
  743. nautobot/dcim/api/nested_serializers.py +0 -356
  744. nautobot/dcim/templates/dcim/device_import.html +0 -5
  745. nautobot/dcim/templates/dcim/device_import_child.html +0 -5
  746. nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
  747. nautobot/extras/api/nested_serializers.py +0 -353
  748. nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -41
  749. nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -46
  750. nautobot/extras/reports.py +0 -60
  751. nautobot/extras/scripts.py +0 -72
  752. nautobot/extras/tests/example_jobs/script_variables.py +0 -67
  753. nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
  754. nautobot/extras/tests/example_jobs/test_fail.py +0 -16
  755. nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
  756. nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
  757. nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
  758. nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
  759. nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
  760. nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
  761. nautobot/extras/tests/example_jobs/test_modify_db.py +0 -18
  762. nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
  763. nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
  764. nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
  765. nautobot/extras/tests/example_jobs/test_pass.py +0 -19
  766. nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
  767. nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
  768. nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
  769. nautobot/ipam/api/nested_serializers.py +0 -159
  770. nautobot/ipam/migrations/0029_ipam__prefix__add_parent.py +0 -31
  771. nautobot/ipam/migrations/0030_ipam__prefix__data_migration.py +0 -13
  772. nautobot/ipam/migrations/0031_ipam__ipaddress__add_parent.py +0 -41
  773. nautobot/ipam/migrations/0032_ipam__ipaddress__data_migration.py +0 -11
  774. nautobot/tenancy/api/nested_serializers.py +0 -31
  775. nautobot/users/api/nested_serializers.py +0 -67
  776. nautobot/virtualization/api/nested_serializers.py +0 -65
  777. /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
  778. /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
  779. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
  780. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,10 @@
1
- from unittest import skipIf, expectedFailure, skip
1
+ import random
2
+ from unittest import skipIf
2
3
 
3
4
  import netaddr
4
5
  from django.contrib.contenttypes.models import ContentType
5
6
  from django.core.exceptions import ValidationError
6
- from django.db import connection
7
+ from django.db import connection, IntegrityError
7
8
  from django.test import TestCase
8
9
 
9
10
  from nautobot.core.testing.models import ModelTestCases
@@ -11,16 +12,21 @@ from nautobot.dcim import choices as dcim_choices
11
12
  from nautobot.dcim.models import Device, DeviceType, Interface, Location, LocationType
12
13
  from nautobot.extras.models import Role, Status
13
14
  from nautobot.ipam.choices import IPAddressStatusChoices, PrefixTypeChoices
14
- from nautobot.ipam.models import IPAddress, Prefix, VLAN, VLANGroup, VRF, Namespace
15
+ from nautobot.ipam.models import IPAddress, IPAddressToInterface, Namespace, Prefix, VLAN, VLANGroup
15
16
  from nautobot.virtualization.models import Cluster, ClusterType, VirtualMachine, VMInterface
16
17
 
17
18
 
18
- @skip
19
+ # TODO(jathan): Add VRF model tests.
20
+
21
+
19
22
  class IPAddressToInterfaceTest(TestCase):
20
23
  """Tests for `nautobot.ipam.models.IPAddressToInterface`."""
21
24
 
22
25
  @classmethod
23
26
  def setUpTestData(cls):
27
+ cls.namespace = Namespace.objects.first()
28
+ cls.status = Status.objects.get(name="Active")
29
+ cls.prefix = Prefix.objects.create(prefix="192.0.2.0/24", status=cls.status, namespace=cls.namespace)
24
30
  cls.test_device = Device.objects.create(
25
31
  name="device1",
26
32
  role=Role.objects.get_for_model(Device).first(),
@@ -60,18 +66,109 @@ class IPAddressToInterfaceTest(TestCase):
60
66
  status=vmint_status,
61
67
  )
62
68
 
69
+ def test_removing_ip_addresses_containing_host_device_primary_ip_nullifies_host_device_primary_ip(self):
70
+ """
71
+ Test that removing IPAddress from an Interface that is the host Device's primary ip nullifies the primary_ip field.
72
+ """
73
+ dev_ip_addr = IPAddress.objects.last()
74
+ self.test_int1.add_ip_addresses(dev_ip_addr)
75
+ self.test_int2.add_ip_addresses(dev_ip_addr)
76
+ ip_to_interface_1 = IPAddressToInterface.objects.get(interface=self.test_int1, ip_address=dev_ip_addr)
77
+ ip_to_interface_2 = IPAddressToInterface.objects.get(interface=self.test_int2, ip_address=dev_ip_addr)
78
+ self.assertIsNotNone(ip_to_interface_1)
79
+ self.assertIsNotNone(ip_to_interface_2)
80
+ if dev_ip_addr.ip_version == 4:
81
+ self.test_device.primary_ip4 = dev_ip_addr
82
+ else:
83
+ self.test_device.primary_ip6 = dev_ip_addr
84
+ self.test_device.save()
85
+ # You can delete IPAddress from the first Interface without nullifying primary_ip field
86
+ # Since the second Interface still contains that IPAddress
87
+ self.test_int1.remove_ip_addresses(dev_ip_addr)
88
+ self.test_device.refresh_from_db()
89
+ if dev_ip_addr.ip_version == 4:
90
+ self.assertEqual(self.test_device.primary_ip4, dev_ip_addr)
91
+ else:
92
+ self.assertEqual(self.test_device.primary_ip6, dev_ip_addr)
93
+ # This operation should nullify the device's primary_ip field since test_int2 is the only Interface
94
+ # that contains the primary ip
95
+ self.test_int2.remove_ip_addresses(dev_ip_addr)
96
+ self.test_device.refresh_from_db()
97
+ if dev_ip_addr.ip_version == 4:
98
+ self.assertEqual(self.test_device.primary_ip4, None)
99
+ else:
100
+ self.assertEqual(self.test_device.primary_ip6, None)
101
+
102
+ def test_removing_ip_addresses_containing_host_vm_primary_ip_nullifies_host_vm_primary_ip(self):
103
+ """
104
+ Test that removing IPAddress from an Interface that is the host Virtual Machine's primary ip nullifies the primary_ip field.
105
+ """
106
+ vm_ip_addr = IPAddress.objects.last()
107
+ self.test_vmint1.add_ip_addresses(vm_ip_addr)
108
+ self.test_vmint2.add_ip_addresses(vm_ip_addr)
109
+ ip_to_vminterface_1 = IPAddressToInterface.objects.get(vm_interface=self.test_vmint1, ip_address=vm_ip_addr)
110
+ ip_to_vminterface_2 = IPAddressToInterface.objects.get(vm_interface=self.test_vmint2, ip_address=vm_ip_addr)
111
+ self.assertIsNotNone(ip_to_vminterface_1)
112
+ self.assertIsNotNone(ip_to_vminterface_2)
113
+ if vm_ip_addr.ip_version == 4:
114
+ self.test_vm.primary_ip4 = vm_ip_addr
115
+ else:
116
+ self.test_vm.primary_ip6 = vm_ip_addr
117
+ self.test_vm.save()
118
+ # You can delete IPAddress from the first Interface without nullifying primary_ip field
119
+ # Since the second Interface still contains that IPAddress
120
+ self.test_vmint1.remove_ip_addresses(vm_ip_addr)
121
+ self.test_vm.refresh_from_db()
122
+ if vm_ip_addr.ip_version == 4:
123
+ self.assertEqual(self.test_vm.primary_ip4, vm_ip_addr)
124
+ else:
125
+ self.assertEqual(self.test_vm.primary_ip6, vm_ip_addr)
126
+ # This operation should nullify the device's primary_ip field since test_int2 is the only Interface
127
+ # that contains the primary ip
128
+ self.test_vmint2.remove_ip_addresses(vm_ip_addr)
129
+ self.test_vm.refresh_from_db()
130
+ if vm_ip_addr.ip_version == 4:
131
+ self.assertEqual(self.test_vm.primary_ip4, None)
132
+ else:
133
+ self.assertEqual(self.test_vm.primary_ip6, None)
134
+
135
+ def test_ip_address_to_interface_uniqueness_constraint(self):
136
+ ip_addr = IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
137
+ IPAddressToInterface.objects.create(interface=self.test_int1, ip_address=ip_addr)
138
+ with self.assertRaises(ValidationError):
139
+ IPAddressToInterface.objects.create(
140
+ vm_interface=self.test_vmint1, interface=self.test_int1, ip_address=ip_addr
141
+ )
142
+ IPAddressToInterface.objects.create(vm_interface=self.test_vmint1, ip_address=ip_addr)
143
+ with self.assertRaises(ValidationError):
144
+ IPAddressToInterface.objects.create(vm_interface=self.test_vmint1, ip_address=ip_addr)
145
+
146
+ def test_pre_save_signal_invoked_on_ip_address_to_interface_manual_creation(self):
147
+ ip_addr = IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
148
+ with self.assertRaises(ValidationError) as cm:
149
+ IPAddressToInterface.objects.create(
150
+ vm_interface=self.test_vmint1, interface=self.test_int1, ip_address=ip_addr
151
+ )
152
+ self.assertIn(
153
+ "Cannot use a single instance to associate to both an Interface and a VMInterface.", str(cm.exception)
154
+ )
155
+ with self.assertRaises(ValidationError) as cm:
156
+ IPAddressToInterface.objects.create(vm_interface=None, interface=None, ip_address=ip_addr)
157
+ self.assertIn("Must associate to either an Interface or a VMInterface.", str(cm.exception))
158
+
63
159
 
64
160
  class TestVarbinaryIPField(TestCase):
65
161
  """Tests for `nautobot.ipam.fields.VarbinaryIPField`."""
66
162
 
67
- def setUp(self):
68
- super().setUp()
69
-
163
+ @classmethod
164
+ def setUpTestData(cls):
70
165
  # Field is a VarbinaryIPField we'll use to test.
71
- self.prefix = Prefix.objects.create(prefix="10.0.0.0/24")
72
- self.field = self.prefix._meta.get_field("network")
73
- self.network = self.prefix.network
74
- self.network_packed = bytes(self.prefix.prefix.network)
166
+ cls.namespace = Namespace.objects.first()
167
+ cls.status = Status.objects.get(name="Active")
168
+ cls.prefix = Prefix.objects.create(prefix="10.0.0.0/24", status=cls.status, namespace=cls.namespace)
169
+ cls.field = cls.prefix._meta.get_field("network")
170
+ cls.network = cls.prefix.network
171
+ cls.network_packed = bytes(cls.prefix.prefix.network)
75
172
 
76
173
  def test_db_type(self):
77
174
  """Test `VarbinaryIPField.db_type`."""
@@ -161,7 +258,6 @@ class TestVarbinaryIPField(TestCase):
161
258
  self.assertEqual(prepped, manual)
162
259
 
163
260
 
164
- @skip
165
261
  class TestPrefix(ModelTestCases.BaseModelTestCase):
166
262
  model = Prefix
167
263
 
@@ -169,21 +265,23 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
169
265
  super().setUp()
170
266
  # With advent of `Prefix.parent`, Prefixes can't just be bulk deleted without clearing their
171
267
  # `parent` first in an `update()` query which doesn't call `save()` or `fire `(pre|post)_save` signals.
268
+ IPAddress.objects.update(parent=None)
269
+ IPAddress.objects.all().delete()
172
270
  Prefix.objects.update(parent=None)
173
271
  Prefix.objects.all().delete()
174
- IPAddress.objects.all().delete()
272
+ self.namespace = Namespace.objects.first()
175
273
  self.statuses = Status.objects.get_for_model(Prefix)
176
274
  self.status = self.statuses.first()
177
- self.root = Prefix.objects.create(prefix="101.102.0.0/24", status=self.status)
178
- self.parent = Prefix.objects.create(prefix="101.102.0.0/25", status=self.status)
179
- self.child1 = Prefix.objects.create(prefix="101.102.0.0/26", status=self.status)
180
- self.child2 = Prefix.objects.create(prefix="101.102.0.64/26", status=self.status)
275
+ self.status.content_types.add(ContentType.objects.get_for_model(IPAddress))
276
+ self.root = Prefix.objects.create(prefix="101.102.0.0/24", status=self.status, namespace=self.namespace)
277
+ self.parent = Prefix.objects.create(prefix="101.102.0.0/25", status=self.status, namespace=self.namespace)
278
+ self.child1 = Prefix.objects.create(prefix="101.102.0.0/26", status=self.status, namespace=self.namespace)
279
+ self.child2 = Prefix.objects.create(prefix="101.102.0.64/26", status=self.status, namespace=self.namespace)
181
280
 
182
281
  def test_prefix_validation(self):
183
282
  location_type = LocationType.objects.get(name="Room")
184
283
  location = Location.objects.filter(location_type=location_type).first()
185
- prefix = Prefix(prefix=netaddr.IPNetwork("192.0.2.0/24"), location=location)
186
- prefix.status = self.statuses[0]
284
+ prefix = Prefix(prefix="192.0.2.0/24", location=location, status=self.statuses[0])
187
285
  with self.assertRaises(ValidationError) as cm:
188
286
  prefix.validated_save()
189
287
  self.assertIn(f'Prefixes may not associate to locations of type "{location_type.name}"', str(cm.exception))
@@ -238,11 +336,10 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
238
336
  # siblings()
239
337
  self.assertEqual(list(self.child1.siblings()), [self.child2])
240
338
  self.assertEqual(list(self.child1.siblings(include_self=True)), [self.child1, self.child2])
241
- parent2 = Prefix.objects.create(prefix="101.102.0.128/25", status=self.status)
339
+ parent2 = Prefix.objects.create(prefix="101.102.0.128/25", status=self.status, namespace=self.namespace)
242
340
  self.assertEqual(list(self.parent.siblings()), [parent2])
243
341
  self.assertEqual(list(self.parent.siblings(include_self=True)), [self.parent, parent2])
244
342
 
245
- # TODO(jathan): When Namespaces are implemented, these tests must be extended to assert it.
246
343
  def test_reparenting(self):
247
344
  """Test that reparenting algorithm works in its most basic form."""
248
345
  # tree hierarchy
@@ -261,7 +358,7 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
261
358
  self.assertEqual(self.child2.parent, self.root)
262
359
  self.assertEqual(list(self.child1.ancestors()), [self.root])
263
360
 
264
- # Add /25 back in as a partn and assert that child1/child2 now have it as their parent, and
361
+ # Add /25 back in as a parent and assert that child1/child2 now have it as their parent, and
265
362
  # /24 is its parent.
266
363
  self.parent.save() # This creates another Prefix using the same instance.
267
364
  self.child1.refresh_from_db()
@@ -270,14 +367,51 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
270
367
  self.assertEqual(self.child2.parent, self.parent)
271
368
  self.assertEqual(list(self.child1.ancestors()), [self.root, self.parent])
272
369
 
370
+ # Now let's create some duplicates in another Namespace and perform the same tests.
371
+
372
+ namespace = random.choice(Namespace.objects.exclude(id=self.namespace.id))
373
+ root = Prefix.objects.create(prefix="101.102.0.0/24", status=self.status, namespace=namespace)
374
+ parent = Prefix.objects.create(prefix="101.102.0.0/25", status=self.status, namespace=namespace)
375
+ child1 = Prefix.objects.create(prefix="101.102.0.0/26", status=self.status, namespace=namespace)
376
+ child2 = Prefix.objects.create(prefix="101.102.0.64/26", status=self.status, namespace=namespace)
377
+
378
+ # tree hierarchy
379
+ self.assertIsNone(root.parent)
380
+ self.assertEqual(parent.parent, root)
381
+ self.assertEqual(child1.parent, parent)
382
+
383
+ # Delete the parent (/25); child1/child2 now have root (/24) as their parent.
384
+ num_deleted, _ = parent.delete()
385
+ self.assertEqual(num_deleted, 1)
386
+
387
+ self.assertEqual(list(root.children.all()), [child1, child2])
388
+ child1.refresh_from_db()
389
+ child2.refresh_from_db()
390
+ self.assertEqual(child1.parent, root)
391
+ self.assertEqual(child2.parent, root)
392
+ self.assertEqual(list(child1.ancestors()), [root])
393
+
394
+ # Add /25 back in as a parent and assert that child1/child2 now have it as their parent, and
395
+ # /24 is its parent.
396
+ parent.save() # This creates another Prefix using the same instance.
397
+ child1.refresh_from_db()
398
+ child2.refresh_from_db()
399
+ self.assertEqual(child1.parent, parent)
400
+ self.assertEqual(child2.parent, parent)
401
+ self.assertEqual(list(child1.ancestors()), [root, parent])
402
+
273
403
  def test_descendants(self):
274
- vrfs = VRF.objects.all()[:3]
275
404
  prefixes = (
276
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/16"), type=PrefixTypeChoices.TYPE_CONTAINER),
277
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/24"), vrf=None),
278
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.1.0/24"), vrf=vrfs[0]),
279
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.2.0/24"), vrf=vrfs[1]),
280
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.3.0/24"), vrf=vrfs[2]),
405
+ Prefix.objects.create(
406
+ prefix="10.0.0.0/16",
407
+ type=PrefixTypeChoices.TYPE_CONTAINER,
408
+ status=self.status,
409
+ namespace=self.namespace,
410
+ ),
411
+ Prefix.objects.create(prefix="10.0.0.0/24", status=self.status, namespace=self.namespace),
412
+ Prefix.objects.create(prefix="10.0.1.0/24", status=self.status, namespace=self.namespace),
413
+ Prefix.objects.create(prefix="10.0.2.0/24", status=self.status, namespace=self.namespace),
414
+ Prefix.objects.create(prefix="10.0.3.0/24", status=self.status, namespace=self.namespace),
281
415
  )
282
416
  prefix_pks = {p.pk for p in prefixes[1:]}
283
417
  child_prefix_pks = {p.pk for p in prefixes[0].descendants()}
@@ -285,67 +419,46 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
285
419
  # Global container should return all children
286
420
  self.assertSetEqual(child_prefix_pks, prefix_pks)
287
421
 
288
- # TODO(jathan): VRF is no longer considered for uniqueness/parenting algorithm, so this
289
- # check is no longer valid so we'll filter it by VRF for now to keep the test working.
290
- prefixes[0].vrf = vrfs[0]
291
- prefixes[0].save()
292
- child_prefix_pks = {p.pk for p in prefixes[0].descendants().filter(vrf=vrfs[0])}
293
-
294
- # VRF container is limited to its own VRF
295
- self.assertSetEqual(child_prefix_pks, {prefixes[2].pk})
296
-
297
- def test_get_child_ips(self):
298
- vrfs = VRF.objects.all()[:3]
299
- namespace = Namespace.objects.first()
422
+ def test_child_ip_addresses(self):
300
423
  parent_prefix = Prefix.objects.create(
301
- prefix=netaddr.IPNetwork("10.0.0.0/16"), namespace=namespace, type=PrefixTypeChoices.TYPE_CONTAINER
424
+ prefix="10.0.0.0/16", status=self.status, namespace=self.namespace, type=PrefixTypeChoices.TYPE_CONTAINER
302
425
  )
303
426
  ips = (
304
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.0.1/24"), namespace=namespace, vrf=None),
305
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.1.1/24"), namespace=namespace, vrf=vrfs[0]),
306
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.2.1/24"), namespace=namespace, vrf=vrfs[1]),
307
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.3.1/24"), namespace=namespace, vrf=vrfs[2]),
427
+ IPAddress.objects.create(address="10.0.0.1/24", status=self.status, namespace=self.namespace),
428
+ IPAddress.objects.create(address="10.0.1.1/24", status=self.status, namespace=self.namespace),
429
+ IPAddress.objects.create(address="10.0.2.1/24", status=self.status, namespace=self.namespace),
430
+ IPAddress.objects.create(address="10.0.3.1/24", status=self.status, namespace=self.namespace),
308
431
  )
309
- child_ip_pks = {p.pk for p in parent_prefix.get_child_ips()}
432
+ child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()}
310
433
 
311
434
  # Global container should return all children
312
435
  self.assertSetEqual(child_ip_pks, {ips[0].pk, ips[1].pk, ips[2].pk, ips[3].pk})
313
436
 
314
- parent_prefix.vrf = vrfs[0]
315
- parent_prefix.save()
316
- child_ip_pks = {p.pk for p in parent_prefix.get_child_ips()}
317
-
318
- # VRF container is limited to its own VRF
319
- self.assertSetEqual(child_ip_pks, {ips[1].pk})
320
-
321
437
  # Make sure /31 is handled correctly
322
438
  parent_prefix_31 = Prefix.objects.create(
323
- prefix=netaddr.IPNetwork("10.0.4.0/31"), namespace=namespace, type=PrefixTypeChoices.TYPE_CONTAINER
439
+ prefix="10.0.4.0/31", status=self.status, namespace=self.namespace, type=PrefixTypeChoices.TYPE_CONTAINER
324
440
  )
325
441
  ips_31 = (
326
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.4.0/31"), namespace=namespace, vrf=None),
327
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.4.1/31"), namespace=namespace, vrf=None),
442
+ IPAddress.objects.create(address="10.0.4.0/31", status=self.status, namespace=self.namespace),
443
+ IPAddress.objects.create(address="10.0.4.1/31", status=self.status, namespace=self.namespace),
328
444
  )
329
-
330
- child_ip_pks = {p.pk for p in parent_prefix_31.get_child_ips()}
445
+ child_ip_pks = {p.pk for p in parent_prefix_31.ip_addresses.all()}
331
446
  self.assertSetEqual(child_ip_pks, {ips_31[0].pk, ips_31[1].pk})
332
447
 
333
448
  def test_get_available_prefixes(self):
334
- namespace = Namespace.objects.first()
335
- prefixes = Prefix.objects.bulk_create(
336
- (
337
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/16"), namespace=namespace), # Parent prefix
338
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/20"), namespace=namespace),
339
- Prefix(prefix=netaddr.IPNetwork("10.0.32.0/20"), namespace=namespace),
340
- Prefix(prefix=netaddr.IPNetwork("10.0.128.0/18"), namespace=namespace),
341
- )
342
- )
449
+ prefixes = [
450
+ Prefix(prefix="10.0.0.0/16", status=self.status, namespace=self.namespace), # Parent prefix
451
+ Prefix(prefix="10.0.0.0/20", status=self.status, namespace=self.namespace),
452
+ Prefix(prefix="10.0.32.0/20", status=self.status, namespace=self.namespace),
453
+ Prefix(prefix="10.0.128.0/18", status=self.status, namespace=self.namespace),
454
+ ]
455
+ [p.save() for p in prefixes] # pylint: disable=expression-not-assigned
343
456
  missing_prefixes = netaddr.IPSet(
344
457
  [
345
- netaddr.IPNetwork("10.0.16.0/20"),
346
- netaddr.IPNetwork("10.0.48.0/20"),
347
- netaddr.IPNetwork("10.0.64.0/18"),
348
- netaddr.IPNetwork("10.0.192.0/18"),
458
+ "10.0.16.0/20",
459
+ "10.0.48.0/20",
460
+ "10.0.64.0/18",
461
+ "10.0.192.0/18",
349
462
  ]
350
463
  )
351
464
  available_prefixes = prefixes[0].get_available_prefixes()
@@ -353,19 +466,17 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
353
466
  self.assertEqual(available_prefixes, missing_prefixes)
354
467
 
355
468
  def test_get_available_ips(self):
356
- namespace = Namespace.objects.first()
357
- parent_prefix = Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/28"))
358
- IPAddress.objects.bulk_create(
359
- (
360
- IPAddress(address=netaddr.IPNetwork("10.0.0.1/26"), namespace=namespace),
361
- IPAddress(address=netaddr.IPNetwork("10.0.0.3/26"), namespace=namespace),
362
- IPAddress(address=netaddr.IPNetwork("10.0.0.5/26"), namespace=namespace),
363
- IPAddress(address=netaddr.IPNetwork("10.0.0.7/26"), namespace=namespace),
364
- IPAddress(address=netaddr.IPNetwork("10.0.0.9/26"), namespace=namespace),
365
- IPAddress(address=netaddr.IPNetwork("10.0.0.11/26"), namespace=namespace),
366
- IPAddress(address=netaddr.IPNetwork("10.0.0.13/26"), namespace=namespace),
367
- )
368
- )
469
+ parent_prefix = Prefix.objects.create(prefix="10.0.0.0/28", status=self.status, namespace=self.namespace)
470
+ ip_list = [
471
+ IPAddress(address="10.0.0.1/26", status=self.status, namespace=self.namespace),
472
+ IPAddress(address="10.0.0.3/26", status=self.status, namespace=self.namespace),
473
+ IPAddress(address="10.0.0.5/26", status=self.status, namespace=self.namespace),
474
+ IPAddress(address="10.0.0.7/26", status=self.status, namespace=self.namespace),
475
+ IPAddress(address="10.0.0.9/26", status=self.status, namespace=self.namespace),
476
+ IPAddress(address="10.0.0.11/26", status=self.status, namespace=self.namespace),
477
+ IPAddress(address="10.0.0.13/26", status=self.status, namespace=self.namespace),
478
+ ]
479
+ [i.save() for i in ip_list] # pylint: disable=expression-not-assigned
369
480
  missing_ips = netaddr.IPSet(
370
481
  [
371
482
  "10.0.0.2/32",
@@ -378,124 +489,136 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
378
489
  ]
379
490
  )
380
491
  available_ips = parent_prefix.get_available_ips()
381
-
382
492
  self.assertEqual(available_ips, missing_ips)
383
493
 
384
494
  def test_get_first_available_prefix(self):
385
- namespace = Namespace.objects.first()
386
- prefixes = Prefix.objects.bulk_create(
387
- (
388
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/16"), namespace=namespace), # Parent prefix
389
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/24"), namespace=namespace),
390
- Prefix(prefix=netaddr.IPNetwork("10.0.1.0/24"), namespace=namespace),
391
- Prefix(prefix=netaddr.IPNetwork("10.0.2.0/24"), namespace=namespace),
392
- )
393
- )
495
+ prefixes = [
496
+ Prefix(prefix="10.0.0.0/16", status=self.status, namespace=self.namespace), # Parent prefix
497
+ Prefix(prefix="10.0.0.0/24", status=self.status, namespace=self.namespace),
498
+ Prefix(prefix="10.0.1.0/24", status=self.status, namespace=self.namespace),
499
+ Prefix(prefix="10.0.2.0/24", status=self.status, namespace=self.namespace),
500
+ ]
501
+ [p.save() for p in prefixes] # pylint: disable=expression-not-assigned
394
502
  self.assertEqual(prefixes[0].get_first_available_prefix(), netaddr.IPNetwork("10.0.3.0/24"))
395
503
 
396
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.3.0/24"))
504
+ Prefix.objects.create(prefix="10.0.3.0/24", status=self.status, namespace=self.namespace)
397
505
  self.assertEqual(prefixes[0].get_first_available_prefix(), netaddr.IPNetwork("10.0.4.0/22"))
398
506
 
399
507
  def test_get_first_available_ip(self):
400
- namespace = Namespace.objects.first()
401
- parent_prefix = Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/24"), namespace=namespace)
402
- IPAddress.objects.bulk_create(
403
- (
404
- IPAddress(address=netaddr.IPNetwork("10.0.0.1/24"), namespace=namespace),
405
- IPAddress(address=netaddr.IPNetwork("10.0.0.2/24"), namespace=namespace),
406
- IPAddress(address=netaddr.IPNetwork("10.0.0.3/24"), namespace=namespace),
407
- )
408
- )
508
+ parent_prefix = Prefix.objects.create(prefix="10.0.0.0/24", status=self.status, namespace=self.namespace)
509
+ ip_list = [
510
+ IPAddress(address="10.0.0.1/24", status=self.status, namespace=self.namespace),
511
+ IPAddress(address="10.0.0.2/24", status=self.status, namespace=self.namespace),
512
+ IPAddress(address="10.0.0.3/24", status=self.status, namespace=self.namespace),
513
+ ]
514
+ [i.save() for i in ip_list] # pylint: disable=expression-not-assigned
409
515
  self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.0.4/24")
410
516
 
411
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.0.4/24"), namespace=namespace)
517
+ IPAddress.objects.create(address="10.0.0.4/24", status=self.status, namespace=self.namespace)
412
518
  self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.0.5/24")
413
519
 
414
520
  def test_get_utilization(self):
415
521
  # Container Prefix
416
- prefix = Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/24"), type=PrefixTypeChoices.TYPE_CONTAINER)
417
- Prefix.objects.bulk_create(
418
- (
419
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/26")),
420
- Prefix(prefix=netaddr.IPNetwork("10.0.0.128/26")),
421
- )
422
- )
522
+ prefix = Prefix.objects.create(
523
+ prefix="10.0.0.0/24", type=PrefixTypeChoices.TYPE_CONTAINER, status=self.status, namespace=self.namespace
524
+ )
525
+ prefixes = [
526
+ Prefix(prefix="10.0.0.0/26", status=self.status, namespace=self.namespace),
527
+ Prefix(prefix="10.0.0.128/26", status=self.status, namespace=self.namespace),
528
+ ]
529
+ [p.save() for p in prefixes] # pylint: disable=expression-not-assigned
423
530
  self.assertEqual(prefix.get_utilization(), (128, 256))
424
531
 
425
532
  # IPv4 Non-container Prefix /24
426
533
  prefix.type = PrefixTypeChoices.TYPE_NETWORK
427
- prefix.save()
428
- IPAddress.objects.bulk_create(
429
- # Create 32 IPAddresses within the Prefix
430
- [IPAddress(address=netaddr.IPNetwork(f"10.0.0.{i}/24")) for i in range(1, 33)]
431
- )
534
+ # Create 32 IPAddresses within the Prefix
535
+ ip_list = [
536
+ IPAddress(address=f"10.0.0.{i}/24", status=self.status, namespace=self.namespace) for i in range(1, 33)
537
+ ]
432
538
  # Create IPAddress objects for network and broadcast addresses
433
- IPAddress.objects.bulk_create(
434
- (IPAddress(address=netaddr.IPNetwork("10.0.0.0/32")), IPAddress(address=netaddr.IPNetwork("10.0.0.255/32")))
435
- )
436
- self.assertEqual(prefix.get_utilization(), (32, 254))
539
+ ip_list += [
540
+ IPAddress(address="10.0.0.0/32", status=self.status, namespace=self.namespace),
541
+ IPAddress(address="10.0.0.255/32", status=self.status, namespace=self.namespace),
542
+ ]
543
+ [i.save() for i in ip_list] # pylint: disable=expression-not-assigned
544
+
545
+ # The parent prefix has no children because of the two child /26 prefixes.
546
+ self.assertEqual(prefix.get_utilization(), (0, 254))
547
+
548
+ # The first child will have 32 IPs
549
+ slash26 = prefix.descendants().first()
550
+ self.assertEqual(slash26.get_utilization(), (32, 62))
437
551
 
438
552
  # Change prefix to a pool, network and broadcast address will count toward numerator and denominator in utilization
439
553
  prefix.type = PrefixTypeChoices.TYPE_POOL
440
- prefix.save()
441
- self.assertEqual(prefix.get_utilization(), (34, 256))
554
+ slash26.type = PrefixTypeChoices.TYPE_POOL
555
+ # Parent prefix only has one child IP (10.0.0.255)
556
+ self.assertEqual(prefix.get_utilization(), (1, 256))
557
+ # The slash26 will have 33.
558
+ self.assertEqual(slash26.get_utilization(), (33, 64))
442
559
 
443
560
  # IPv4 Non-container Prefix /31, network and broadcast addresses count toward utilization
444
- prefix = Prefix.objects.create(prefix="10.0.1.0/31")
445
- IPAddress.objects.bulk_create(
446
- (IPAddress(address=netaddr.IPNetwork("10.0.1.0/32")), IPAddress(address=netaddr.IPNetwork("10.0.1.1/32")))
447
- )
561
+ prefix = Prefix.objects.create(prefix="10.0.1.0/31", status=self.status, namespace=self.namespace)
562
+ ip_list = [
563
+ IPAddress(address="10.0.1.0/32", status=self.status, namespace=self.namespace),
564
+ IPAddress(address="10.0.1.1/32", status=self.status, namespace=self.namespace),
565
+ ]
566
+ [i.save() for i in ip_list] # pylint: disable=expression-not-assigned
448
567
  self.assertEqual(prefix.get_utilization(), (2, 2))
449
568
 
450
569
  # IPv6 Non-container Prefix, network and broadcast addresses count toward utilization
451
- prefix = Prefix.objects.create(prefix="aaaa::/124")
452
- IPAddress.objects.bulk_create(
453
- (IPAddress(address=netaddr.IPNetwork("aaaa::0/128")), IPAddress(address=netaddr.IPNetwork("aaaa::f/128")))
454
- )
570
+ prefix = Prefix.objects.create(prefix="aaaa::/124", status=self.status, namespace=self.namespace)
571
+ ip_list = [
572
+ IPAddress(address="aaaa::0/128", status=self.status, namespace=self.namespace),
573
+ IPAddress(address="aaaa::f/128", status=self.status, namespace=self.namespace),
574
+ ]
575
+ [i.save() for i in ip_list] # pylint: disable=expression-not-assigned
455
576
  self.assertEqual(prefix.get_utilization(), (2, 16))
456
577
 
457
578
  # Large Prefix
458
- large_prefix = Prefix.objects.create(prefix="22.0.0.0/8", type=PrefixTypeChoices.TYPE_CONTAINER)
579
+ large_prefix = Prefix.objects.create(
580
+ prefix="22.0.0.0/8", type=PrefixTypeChoices.TYPE_CONTAINER, status=self.status, namespace=self.namespace
581
+ )
459
582
 
460
583
  # 25% utilization
461
- Prefix.objects.bulk_create(
462
- (
463
- Prefix(prefix=netaddr.IPNetwork("22.0.0.0/12")),
464
- Prefix(prefix=netaddr.IPNetwork("22.16.0.0/12")),
465
- Prefix(prefix=netaddr.IPNetwork("22.32.0.0/12")),
466
- Prefix(prefix=netaddr.IPNetwork("22.48.0.0/12")),
467
- )
468
- )
584
+ prefixes = [
585
+ Prefix(prefix="22.0.0.0/12", status=self.status, namespace=self.namespace),
586
+ Prefix(prefix="22.16.0.0/12", status=self.status, namespace=self.namespace),
587
+ Prefix(prefix="22.32.0.0/12", status=self.status, namespace=self.namespace),
588
+ Prefix(prefix="22.48.0.0/12", status=self.status, namespace=self.namespace),
589
+ ]
590
+ [p.save() for p in prefixes] # pylint: disable=expression-not-assigned
469
591
  self.assertEqual(large_prefix.get_utilization(), (4194304, 16777216))
470
592
 
471
593
  # 50% utilization
472
- Prefix.objects.bulk_create((Prefix(prefix=netaddr.IPNetwork("22.64.0.0/10")),))
594
+ Prefix.objects.create(prefix="22.64.0.0/10", status=self.status, namespace=self.namespace)
473
595
  self.assertEqual(large_prefix.get_utilization(), (8388608, 16777216))
474
596
 
475
597
  # 100% utilization
476
- Prefix.objects.bulk_create((Prefix(prefix=netaddr.IPNetwork("22.128.0.0/9")),))
598
+ Prefix.objects.create(prefix="22.128.0.0/9", status=self.status, namespace=self.namespace)
477
599
  self.assertEqual(large_prefix.get_utilization(), (16777216, 16777216))
478
600
 
479
601
  # IPv6 Large Prefix
480
- large_prefix_v6 = Prefix.objects.create(prefix="ab00::/8", type=PrefixTypeChoices.TYPE_CONTAINER)
602
+ large_prefix_v6 = Prefix.objects.create(
603
+ prefix="ab00::/8", type=PrefixTypeChoices.TYPE_CONTAINER, status=self.status, namespace=self.namespace
604
+ )
481
605
 
482
606
  # 25% utilization
483
- Prefix.objects.bulk_create(
484
- (
485
- Prefix(prefix=netaddr.IPNetwork("ab00::/12")),
486
- Prefix(prefix=netaddr.IPNetwork("ab10::/12")),
487
- Prefix(prefix=netaddr.IPNetwork("ab20::/12")),
488
- Prefix(prefix=netaddr.IPNetwork("ab30::/12")),
489
- )
490
- )
607
+ prefixes = [
608
+ Prefix(prefix="ab00::/12", status=self.status, namespace=self.namespace),
609
+ Prefix(prefix="ab10::/12", status=self.status, namespace=self.namespace),
610
+ Prefix(prefix="ab20::/12", status=self.status, namespace=self.namespace),
611
+ Prefix(prefix="ab30::/12", status=self.status, namespace=self.namespace),
612
+ ]
613
+ [p.save() for p in prefixes] # pylint: disable=expression-not-assigned
491
614
  self.assertEqual(large_prefix_v6.get_utilization(), (2**118, 2**120))
492
615
 
493
616
  # 50% utilization
494
- Prefix.objects.bulk_create((Prefix(prefix=netaddr.IPNetwork("ab40::/10")),))
617
+ Prefix.objects.create(prefix="ab40::/10", status=self.status, namespace=self.namespace)
495
618
  self.assertEqual(large_prefix_v6.get_utilization(), (2**119, 2**120))
496
619
 
497
620
  # 100% utilization
498
- Prefix.objects.bulk_create((Prefix(prefix=netaddr.IPNetwork("ab80::/9")),))
621
+ Prefix.objects.create(prefix="ab80::/9", status=self.status, namespace=self.namespace)
499
622
  self.assertEqual(large_prefix_v6.get_utilization(), (2**120, 2**120))
500
623
 
501
624
  #
@@ -503,74 +626,74 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
503
626
  #
504
627
 
505
628
  def test_duplicate_global_unique(self):
506
- """This should raise a ValidationError."""
507
- Prefix.objects.create(prefix=netaddr.IPNetwork("192.0.2.0/24"))
508
- duplicate_prefix = Prefix(prefix=netaddr.IPNetwork("192.0.2.0/24"))
629
+ """Test that duplicate Prefixes in the same Namespace raises an error."""
630
+ Prefix.objects.create(prefix="192.0.2.0/24", status=self.status, namespace=self.namespace)
631
+ duplicate_prefix = Prefix(prefix="192.0.2.0/24", status=self.status, namespace=self.namespace)
632
+ # self.assertRaises(IntegrityError, duplicate_prefix.full_clean)
509
633
  self.assertRaises(ValidationError, duplicate_prefix.full_clean)
510
634
 
511
635
 
512
- @skip
513
636
  class TestIPAddress(ModelTestCases.BaseModelTestCase):
514
637
  model = IPAddress
515
638
 
516
- @expectedFailure
517
- def test_get_duplicates(self):
518
- ips = (
519
- IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24")),
520
- IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24")),
521
- IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24")),
522
- )
523
- duplicate_ip_pks = [p.pk for p in ips[0].get_duplicates()]
524
-
525
- self.assertSetEqual(set(duplicate_ip_pks), {ips[1].pk, ips[2].pk})
639
+ def setUp(self):
640
+ super().setUp()
641
+ self.namespace = Namespace.objects.first()
642
+ self.status = Status.objects.get(name="Active")
643
+ self.prefix = Prefix.objects.create(prefix="192.0.2.0/24", status=self.status, namespace=self.namespace)
526
644
 
527
645
  #
528
646
  # Uniqueness enforcement tests
529
647
  #
530
648
 
531
- @expectedFailure
532
649
  def test_duplicate_global_unique(self):
533
- namespace = Namespace.objects.first()
534
- Prefix.objects.create(prefix="192.0.2.0/24", namespace=namespace)
535
- IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24"), namespace=namespace)
536
- duplicate_ip = IPAddress(address=netaddr.IPNetwork("192.0.2.1/24"), namespace=namespace)
537
- self.assertRaises(ValidationError, duplicate_ip.clean)
538
-
539
- @expectedFailure
540
- def test_duplicate_nonunique_role(self):
541
- roles = Role.objects.get_for_model(IPAddress)
542
- IPAddress.objects.create(
543
- address=netaddr.IPNetwork("192.0.2.1/24"),
544
- role=roles[0],
545
- )
546
- IPAddress.objects.create(
547
- address=netaddr.IPNetwork("192.0.2.1/24"),
548
- role=roles[1],
549
- )
650
+ """Test that duplicate IPs in the same Namespace raises an error."""
651
+ IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
652
+ with self.assertRaises(IntegrityError):
653
+ IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
550
654
 
551
- @expectedFailure
552
655
  def test_multiple_nat_outside_list(self):
553
656
  """
554
657
  Test suite to test supporing nat_outside_list.
555
658
  """
556
- nat_inside = IPAddress.objects.create(address=netaddr.IPNetwork("192.168.0.1/24"))
557
- nat_outside1 = IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24"), nat_inside=nat_inside)
558
- nat_outside2 = IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.2/24"), nat_inside=nat_inside)
559
- nat_outside3 = IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.3/24"), nat_inside=nat_inside)
659
+ Prefix.objects.create(prefix="192.168.0.0/24", status=self.status, namespace=self.namespace)
660
+ nat_inside = IPAddress.objects.create(address="192.168.0.1/24", status=self.status, namespace=self.namespace)
661
+ nat_outside1 = IPAddress.objects.create(
662
+ address="192.0.2.1/24", nat_inside=nat_inside, status=self.status, namespace=self.namespace
663
+ )
664
+ nat_outside2 = IPAddress.objects.create(
665
+ address="192.0.2.2/24", nat_inside=nat_inside, status=self.status, namespace=self.namespace
666
+ )
667
+ nat_outside3 = IPAddress.objects.create(
668
+ address="192.0.2.3/24", nat_inside=nat_inside, status=self.status, namespace=self.namespace
669
+ )
560
670
  nat_inside.refresh_from_db()
561
671
  self.assertEqual(nat_inside.nat_outside_list.count(), 3)
562
672
  self.assertEqual(nat_inside.nat_outside_list.all()[0], nat_outside1)
563
673
  self.assertEqual(nat_inside.nat_outside_list.all()[1], nat_outside2)
564
674
  self.assertEqual(nat_inside.nat_outside_list.all()[2], nat_outside3)
565
675
 
566
- @expectedFailure
567
676
  def test_create_ip_address_without_slaac_status(self):
568
677
  slaac_status_name = IPAddressStatusChoices.as_dict()[IPAddressStatusChoices.STATUS_SLAAC]
569
678
  IPAddress.objects.filter(status__name=slaac_status_name).delete()
570
679
  Status.objects.get(name=slaac_status_name).delete()
571
- IPAddress.objects.create(address="1.1.1.1/32")
680
+ Prefix.objects.create(prefix="1.1.1.0/24", status=self.status, namespace=self.namespace)
681
+ IPAddress.objects.create(address="1.1.1.1/32", status=self.status, namespace=self.namespace)
572
682
  self.assertTrue(IPAddress.objects.filter(address="1.1.1.1/32").exists())
573
683
 
684
+ def test_get_closest_parent(self):
685
+ for ip in IPAddress.objects.all():
686
+ with self.subTest(ip=ip):
687
+ ip.save()
688
+ ip.refresh_from_db()
689
+ self.assertIsNotNone(ip.parent)
690
+ self.assertEqual(
691
+ ip.parent,
692
+ Prefix.objects.filter(network__lte=ip.host, broadcast__gte=ip.host)
693
+ .order_by("-prefix_length")
694
+ .first(),
695
+ )
696
+
574
697
 
575
698
  class TestVLANGroup(ModelTestCases.BaseModelTestCase):
576
699
  model = VLANGroup
@@ -585,17 +708,18 @@ class TestVLANGroup(ModelTestCases.BaseModelTestCase):
585
708
 
586
709
  def test_get_next_available_vid(self):
587
710
  vlangroup = VLANGroup.objects.create(name="VLAN Group 1", slug="vlan-group-1")
711
+ status = Status.objects.get_for_model(VLAN).first()
588
712
  VLAN.objects.bulk_create(
589
713
  (
590
- VLAN(name="VLAN 1", vid=1, vlan_group=vlangroup),
591
- VLAN(name="VLAN 2", vid=2, vlan_group=vlangroup),
592
- VLAN(name="VLAN 3", vid=3, vlan_group=vlangroup),
593
- VLAN(name="VLAN 5", vid=5, vlan_group=vlangroup),
714
+ VLAN(name="VLAN 1", vid=1, vlan_group=vlangroup, status=status),
715
+ VLAN(name="VLAN 2", vid=2, vlan_group=vlangroup, status=status),
716
+ VLAN(name="VLAN 3", vid=3, vlan_group=vlangroup, status=status),
717
+ VLAN(name="VLAN 5", vid=5, vlan_group=vlangroup, status=status),
594
718
  )
595
719
  )
596
720
  self.assertEqual(vlangroup.get_next_available_vid(), 4)
597
721
 
598
- VLAN.objects.bulk_create((VLAN(name="VLAN 4", vid=4, vlan_group=vlangroup),))
722
+ VLAN.objects.bulk_create((VLAN(name="VLAN 4", vid=4, vlan_group=vlangroup, status=status),))
599
723
  self.assertEqual(vlangroup.get_next_available_vid(), 6)
600
724
 
601
725
 
@@ -616,7 +740,8 @@ class VLANTestCase(ModelTestCases.BaseModelTestCase):
616
740
  location_type.content_types.add(ContentType.objects.get_for_model(VLAN))
617
741
  group = VLANGroup.objects.create(name="Group 1")
618
742
  vlan.vlan_group = group
619
- location_2 = Location.objects.create(name="Location 2", location_type=location_type)
743
+ location_status = Status.objects.get_for_model(Location).first()
744
+ location_2 = Location.objects.create(name="Location 2", location_type=location_type, status=location_status)
620
745
  group.location = location_2
621
746
  group.save()
622
747
  with self.assertRaises(ValidationError) as cm: