nautobot 2.0.0a2__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 (1029) hide show
  1. nautobot/__init__.py +1 -5
  2. nautobot/apps/api.py +6 -8
  3. nautobot/apps/forms.py +0 -2
  4. nautobot/apps/ui.py +0 -8
  5. nautobot/circuits/api/serializers.py +9 -119
  6. nautobot/circuits/api/urls.py +1 -1
  7. nautobot/circuits/api/views.py +0 -1
  8. nautobot/circuits/choices.py +0 -2
  9. nautobot/circuits/filters.py +7 -6
  10. nautobot/circuits/forms.py +3 -73
  11. nautobot/circuits/migrations/0001_initial_part_1.py +0 -1
  12. nautobot/circuits/migrations/0002_initial_part_2.py +0 -1
  13. nautobot/circuits/migrations/0003_auto_slug.py +0 -1
  14. nautobot/circuits/migrations/0004_increase_provider_account_length.py +0 -1
  15. nautobot/circuits/migrations/0005_providernetwork.py +0 -1
  16. nautobot/circuits/migrations/0006_cache_circuit_terminations.py +0 -1
  17. nautobot/circuits/migrations/0007_circuitterminations_primary_model.py +0 -1
  18. nautobot/circuits/migrations/0008_add_natural_indexing.py +0 -1
  19. nautobot/circuits/migrations/0009_circuittermination_location.py +0 -1
  20. nautobot/circuits/migrations/0010_rename_foreign_keys_and_related_names.py +0 -1
  21. nautobot/circuits/migrations/0011_remove_site_foreign_key_from_circuit_termination_class.py +0 -1
  22. nautobot/circuits/migrations/0012_created_datetime.py +0 -1
  23. nautobot/circuits/migrations/0013_alter_circuittermination__path.py +0 -1
  24. nautobot/circuits/migrations/0014_related_name_changes.py +1 -2
  25. nautobot/circuits/migrations/0015_remove_circuittype_provider_slug.py +20 -0
  26. nautobot/circuits/migrations/0016_tagsfield.py +34 -0
  27. nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
  28. nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
  29. nautobot/circuits/models.py +3 -93
  30. nautobot/circuits/navigation.py +14 -69
  31. nautobot/circuits/signals.py +0 -2
  32. nautobot/circuits/tables.py +42 -5
  33. nautobot/circuits/templates/circuits/circuit_retrieve.html +1 -1
  34. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -1
  35. nautobot/circuits/templates/circuits/circuittype_retrieve.html +1 -1
  36. nautobot/circuits/templates/circuits/provider_create.html +0 -1
  37. nautobot/circuits/templates/circuits/provider_retrieve.html +1 -1
  38. nautobot/circuits/tests/integration/test_relationships.py +13 -16
  39. nautobot/circuits/tests/test_api.py +13 -43
  40. nautobot/circuits/tests/test_filters.py +20 -15
  41. nautobot/circuits/tests/test_models.py +7 -3
  42. nautobot/circuits/tests/test_views.py +57 -67
  43. nautobot/circuits/views.py +18 -9
  44. nautobot/core/api/__init__.py +8 -2
  45. nautobot/core/api/authentication.py +0 -3
  46. nautobot/core/api/fields.py +15 -6
  47. nautobot/core/api/filter_backends.py +3 -2
  48. nautobot/core/api/metadata.py +237 -30
  49. nautobot/core/api/mixins.py +94 -0
  50. nautobot/core/api/pagination.py +3 -3
  51. nautobot/core/api/parsers.py +154 -0
  52. nautobot/core/api/renderers.py +153 -2
  53. nautobot/core/api/schema.py +47 -3
  54. nautobot/core/api/serializers.py +377 -37
  55. nautobot/core/api/urls.py +11 -3
  56. nautobot/core/api/utils.py +174 -2
  57. nautobot/core/api/versioning.py +32 -10
  58. nautobot/core/api/views.py +266 -75
  59. nautobot/core/apps/__init__.py +138 -221
  60. nautobot/core/celery/__init__.py +112 -41
  61. nautobot/core/celery/backends.py +19 -13
  62. nautobot/core/celery/control.py +46 -0
  63. nautobot/core/celery/encoders.py +53 -0
  64. nautobot/core/celery/log.py +38 -0
  65. nautobot/core/celery/schedulers.py +23 -4
  66. nautobot/core/celery/task.py +1 -16
  67. nautobot/core/checks.py +0 -27
  68. nautobot/core/choices.py +21 -113
  69. nautobot/core/{cli.py → cli/__init__.py} +1 -2
  70. nautobot/core/cli/__main__.py +3 -0
  71. nautobot/core/constants.py +25 -43
  72. nautobot/core/context_processors.py +12 -0
  73. nautobot/core/filters.py +2 -2
  74. nautobot/core/forms/__init__.py +0 -4
  75. nautobot/core/forms/fields.py +39 -68
  76. nautobot/core/forms/forms.py +27 -27
  77. nautobot/core/forms/utils.py +7 -59
  78. nautobot/core/forms/widgets.py +0 -1
  79. nautobot/core/graphql/__init__.py +2 -2
  80. nautobot/core/graphql/schema.py +4 -27
  81. nautobot/core/jobs/__init__.py +75 -0
  82. nautobot/core/management/commands/build_ui.py +255 -0
  83. nautobot/core/management/commands/celery.py +0 -1
  84. nautobot/core/management/commands/generate_test_data.py +18 -13
  85. nautobot/core/management/commands/post_upgrade.py +24 -24
  86. nautobot/core/management/commands/validate_models.py +0 -1
  87. nautobot/core/middleware.py +0 -1
  88. nautobot/core/models/__init__.py +26 -1
  89. nautobot/core/models/fields.py +24 -5
  90. nautobot/core/models/generics.py +2 -46
  91. nautobot/core/models/managers.py +5 -0
  92. nautobot/core/models/name_color_content_types.py +1 -19
  93. nautobot/core/models/tree_queries.py +14 -4
  94. nautobot/core/models/utils.py +9 -10
  95. nautobot/core/models/validators.py +17 -8
  96. nautobot/core/releases.py +8 -10
  97. nautobot/core/settings.py +81 -53
  98. nautobot/core/tables.py +5 -5
  99. nautobot/core/tasks.py +4 -7
  100. nautobot/core/templates/base.html +1 -49
  101. nautobot/core/templates/base_django.html +49 -0
  102. nautobot/core/templates/base_react.html +55 -0
  103. nautobot/core/templates/buttons/export.html +6 -4
  104. nautobot/core/templates/generic/object_bulk_create.html +10 -21
  105. nautobot/core/templates/generic/object_list.html +4 -1
  106. nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
  107. nautobot/core/templates/inc/footer.html +1 -0
  108. nautobot/core/templates/inc/javascript.html +0 -14
  109. nautobot/core/templates/inc/nav_menu.html +28 -33
  110. nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
  111. nautobot/core/templates/inc/relationships_table_rows.html +2 -2
  112. nautobot/core/templates/nautobot_config.py.j2 +8 -25
  113. nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
  114. nautobot/core/templates/rest_framework/api.html +8 -0
  115. nautobot/core/templatetags/buttons.py +32 -29
  116. nautobot/core/templatetags/helpers.py +1 -1
  117. nautobot/core/testing/__init__.py +47 -44
  118. nautobot/core/testing/api.py +365 -47
  119. nautobot/core/testing/filters.py +12 -7
  120. nautobot/core/testing/integration.py +1 -1
  121. nautobot/core/testing/migrations.py +2 -0
  122. nautobot/core/testing/mixins.py +22 -12
  123. nautobot/core/testing/schema.py +2 -1
  124. nautobot/core/testing/views.py +28 -51
  125. nautobot/core/tests/integration/test_filters.py +17 -8
  126. nautobot/core/tests/integration/test_navbar.py +11 -34
  127. nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
  128. nautobot/core/tests/nautobot_config.py +2 -3
  129. nautobot/core/tests/runner.py +0 -1
  130. nautobot/core/tests/test_api.py +290 -24
  131. nautobot/core/tests/test_authentication.py +57 -14
  132. nautobot/core/tests/test_checks.py +0 -7
  133. nautobot/core/tests/test_choices.py +0 -1
  134. nautobot/core/tests/test_filters.py +117 -110
  135. nautobot/core/tests/test_forms.py +47 -110
  136. nautobot/core/tests/test_graphql.py +158 -135
  137. nautobot/core/tests/test_logging.py +4 -1
  138. nautobot/core/tests/test_managers.py +3 -5
  139. nautobot/core/tests/test_models.py +2 -0
  140. nautobot/core/tests/test_ordering.py +0 -2
  141. nautobot/core/tests/test_paginator.py +3 -1
  142. nautobot/core/tests/test_releases.py +12 -12
  143. nautobot/core/tests/test_templatetags_helpers.py +7 -4
  144. nautobot/core/tests/test_utils.py +112 -78
  145. nautobot/core/tests/test_views.py +12 -17
  146. nautobot/core/tests/test_views_utils.py +6 -9
  147. nautobot/core/utils/data.py +17 -0
  148. nautobot/core/utils/deprecation.py +13 -20
  149. nautobot/core/utils/filtering.py +53 -9
  150. nautobot/core/utils/git.py +12 -4
  151. nautobot/core/utils/lookup.py +3 -1
  152. nautobot/core/utils/requests.py +23 -116
  153. nautobot/core/views/__init__.py +1 -2
  154. nautobot/core/views/generic.py +131 -119
  155. nautobot/core/views/mixins.py +53 -62
  156. nautobot/core/views/paginator.py +0 -1
  157. nautobot/core/views/renderers.py +14 -12
  158. nautobot/core/views/utils.py +87 -4
  159. nautobot/dcim/api/serializers.py +160 -672
  160. nautobot/dcim/api/urls.py +1 -1
  161. nautobot/dcim/api/views.py +7 -46
  162. nautobot/dcim/choices.py +2 -25
  163. nautobot/dcim/elevations.py +0 -1
  164. nautobot/dcim/factory.py +15 -4
  165. nautobot/dcim/filters/__init__.py +42 -13
  166. nautobot/dcim/form_mixins.py +1 -27
  167. nautobot/dcim/forms.py +58 -797
  168. nautobot/dcim/management/commands/trace_paths.py +0 -1
  169. nautobot/dcim/migrations/0001_initial_part_1.py +0 -1
  170. nautobot/dcim/migrations/0002_initial_part_2.py +0 -1
  171. nautobot/dcim/migrations/0003_initial_part_3.py +0 -1
  172. nautobot/dcim/migrations/0004_initial_part_4.py +0 -1
  173. nautobot/dcim/migrations/0005_device_local_context_schema.py +0 -1
  174. nautobot/dcim/migrations/0006_auto_slug.py +0 -1
  175. nautobot/dcim/migrations/0007_device_secrets_group.py +0 -1
  176. nautobot/dcim/migrations/0008_increase_all_serial_lengths.py +0 -1
  177. nautobot/dcim/migrations/0009_add_natural_indexing.py +0 -1
  178. nautobot/dcim/migrations/0010_interface_status.py +0 -1
  179. nautobot/dcim/migrations/0011_interface_status_data_migration.py +0 -1
  180. nautobot/dcim/migrations/0012_interface_parent_bridge.py +0 -1
  181. nautobot/dcim/migrations/0013_location_location_type.py +0 -1
  182. nautobot/dcim/migrations/0014_location_status_data_migration.py +0 -1
  183. nautobot/dcim/migrations/0015_device_components__changeloggedmodel.py +0 -1
  184. nautobot/dcim/migrations/0016_device_components__timestamp_data_migration.py +0 -1
  185. nautobot/dcim/migrations/0017_locationtype_nestable.py +0 -1
  186. nautobot/dcim/migrations/0018_device_redundancy_group.py +0 -1
  187. nautobot/dcim/migrations/0019_device_redundancy_group_data_migration.py +0 -1
  188. nautobot/dcim/migrations/0020_move_site_fields_to_location_model.py +0 -1
  189. nautobot/dcim/migrations/0021_mptt_to_tree_queries.py +0 -1
  190. nautobot/dcim/migrations/0022_interface_mac_address_data_migration.py +0 -1
  191. nautobot/dcim/migrations/0023_alter_interface_mac_address.py +0 -1
  192. nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -2
  193. nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -14
  194. nautobot/dcim/migrations/0026_rename_device_and_rack_role.py +0 -1
  195. nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -2
  196. nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -2
  197. nautobot/dcim/migrations/0029_add_tree_managers_and_foreign_keys_pre_data_migration.py +0 -1
  198. nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
  199. nautobot/dcim/migrations/0031_rename_path_end_point_related_name.py +0 -1
  200. nautobot/dcim/migrations/0032_remove_site_foreign_key_from_dcim_models.py +0 -1
  201. nautobot/dcim/migrations/0033_created_datetime.py +0 -1
  202. nautobot/dcim/migrations/0034_fixup_fks_and_related_names.py +0 -1
  203. nautobot/dcim/migrations/0035_related_name_changes.py +1 -2
  204. nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -2
  205. nautobot/dcim/migrations/0037_interface_ip_addresses_m2m.py +0 -1
  206. nautobot/dcim/migrations/0038_alter_location_managers.py +0 -1
  207. nautobot/dcim/migrations/0039_remove_slug.py +24 -0
  208. nautobot/dcim/migrations/0040_tagsfield.py +109 -0
  209. nautobot/dcim/migrations/0041_ipam__namespaces.py +25 -0
  210. nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
  211. nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
  212. nautobot/dcim/models/cables.py +4 -35
  213. nautobot/dcim/models/device_component_templates.py +7 -2
  214. nautobot/dcim/models/device_components.py +26 -203
  215. nautobot/dcim/models/devices.py +30 -152
  216. nautobot/dcim/models/locations.py +3 -64
  217. nautobot/dcim/models/power.py +3 -51
  218. nautobot/dcim/models/racks.py +7 -86
  219. nautobot/dcim/navigation.py +141 -467
  220. nautobot/dcim/signals.py +0 -2
  221. nautobot/dcim/tables/devices.py +8 -5
  222. nautobot/dcim/tables/devicetypes.py +1 -1
  223. nautobot/dcim/tables/locations.py +2 -2
  224. nautobot/dcim/tables/power.py +2 -2
  225. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
  226. nautobot/dcim/templates/dcim/device.html +15 -4
  227. nautobot/dcim/templates/dcim/device_edit.html +6 -0
  228. nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +0 -1
  229. nautobot/dcim/templates/dcim/devicetype.html +2 -2
  230. nautobot/dcim/templates/dcim/interface.html +4 -0
  231. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
  232. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  233. nautobot/dcim/templates/dcim/location.html +16 -1
  234. nautobot/dcim/templates/dcim/locationtype.html +15 -0
  235. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
  236. nautobot/dcim/templates/dcim/rackgroup.html +0 -12
  237. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  238. nautobot/dcim/tests/test_api.py +202 -130
  239. nautobot/dcim/tests/test_cablepaths.py +47 -42
  240. nautobot/dcim/tests/test_filters.py +156 -134
  241. nautobot/dcim/tests/test_forms.py +12 -213
  242. nautobot/dcim/tests/test_graphql.py +8 -3
  243. nautobot/dcim/tests/test_migrations.py +6 -11
  244. nautobot/dcim/tests/test_models.py +208 -158
  245. nautobot/dcim/tests/test_natural_ordering.py +12 -14
  246. nautobot/dcim/tests/test_signals.py +7 -4
  247. nautobot/dcim/tests/test_views.py +270 -264
  248. nautobot/dcim/urls.py +21 -26
  249. nautobot/dcim/views.py +14 -156
  250. nautobot/docs/additional-features/caching.md +6 -87
  251. nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
  252. nautobot/docs/additional-features/jobs.md +179 -197
  253. nautobot/docs/administration/nautobot-server.md +9 -24
  254. nautobot/docs/administration/nautobot-shell.md +6 -6
  255. nautobot/docs/administration/replicating-nautobot.md +0 -10
  256. nautobot/docs/configuration/index.md +9 -9
  257. nautobot/docs/configuration/optional-settings.md +32 -61
  258. nautobot/docs/configuration/required-settings.md +11 -52
  259. nautobot/docs/development/application-registry.md +2 -13
  260. nautobot/docs/development/best-practices.md +2 -1
  261. nautobot/docs/development/docker-compose-advanced-use-cases.md +1 -1
  262. nautobot/docs/development/extending-models.md +15 -17
  263. nautobot/docs/development/generic-views.md +0 -2
  264. nautobot/docs/development/getting-started.md +56 -6
  265. nautobot/docs/development/navigation-menu.md +22 -93
  266. nautobot/docs/development/react-ui.md +105 -0
  267. nautobot/docs/development/release-checklist.md +3 -3
  268. nautobot/docs/development/role-internals.md +1 -3
  269. nautobot/docs/development/style-guide.md +6 -4
  270. nautobot/docs/development/templates.md +2 -1
  271. nautobot/docs/docker/index.md +16 -14
  272. nautobot/docs/index.md +7 -3
  273. nautobot/docs/installation/index.md +4 -1
  274. nautobot/docs/installation/migrating-from-netbox.md +12 -43
  275. nautobot/docs/installation/migrating-from-postgresql.md +1 -1
  276. nautobot/docs/installation/nautobot.md +1 -1
  277. nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  278. nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  279. nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  280. nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  281. nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
  282. nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  283. nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  284. nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  285. nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  286. nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  287. nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  288. nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  289. nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  290. nautobot/docs/installation/upgrading-from-nautobot-v1.md +190 -636
  291. nautobot/docs/installation/upgrading.md +5 -2
  292. nautobot/docs/models/dcim/device.md +3 -0
  293. nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
  294. nautobot/docs/models/extras/computedfield.md +4 -4
  295. nautobot/docs/models/extras/dynamicgroup.md +9 -9
  296. nautobot/docs/models/extras/gitrepository.md +3 -0
  297. nautobot/docs/models/extras/job.md +1 -0
  298. nautobot/docs/models/extras/jobbutton.md +18 -13
  299. nautobot/docs/models/extras/jobhook.md +7 -4
  300. nautobot/docs/models/extras/jobresult.md +6 -2
  301. nautobot/docs/models/extras/relationship.md +2 -2
  302. nautobot/docs/models/extras/status.md +6 -19
  303. nautobot/docs/models/ipam/ipaddress.md +3 -0
  304. nautobot/docs/models/ipam/vrf.md +0 -3
  305. nautobot/docs/models/virtualization/virtualmachine.md +3 -0
  306. nautobot/docs/plugins/development.md +92 -24
  307. nautobot/docs/release-notes/version-1.5.md +96 -0
  308. nautobot/docs/release-notes/version-2.0.md +216 -0
  309. nautobot/docs/requirements.txt +5 -4
  310. nautobot/docs/rest-api/overview.md +384 -215
  311. nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
  312. nautobot/extras/admin.py +3 -5
  313. nautobot/extras/api/customfields.py +15 -39
  314. nautobot/extras/api/fields.py +0 -11
  315. nautobot/extras/api/mixins.py +45 -0
  316. nautobot/extras/api/relationships.py +63 -159
  317. nautobot/extras/api/serializers.py +165 -706
  318. nautobot/extras/api/urls.py +1 -1
  319. nautobot/extras/api/views.py +295 -282
  320. nautobot/extras/apps.py +4 -7
  321. nautobot/extras/choices.py +11 -22
  322. nautobot/extras/constants.py +9 -3
  323. nautobot/extras/datasources/__init__.py +2 -0
  324. nautobot/extras/datasources/git.py +135 -186
  325. nautobot/extras/datasources/registry.py +25 -35
  326. nautobot/extras/factory.py +1 -3
  327. nautobot/extras/filters/__init__.py +49 -47
  328. nautobot/extras/filters/mixins.py +10 -8
  329. nautobot/extras/forms/forms.py +72 -148
  330. nautobot/extras/forms/mixins.py +34 -57
  331. nautobot/extras/health_checks.py +0 -33
  332. nautobot/extras/jobs.py +387 -566
  333. nautobot/extras/management/__init__.py +55 -48
  334. nautobot/extras/management/commands/renaturalize.py +0 -1
  335. nautobot/extras/management/commands/runjob.py +24 -62
  336. nautobot/extras/management/commands/webhook_receiver.py +0 -1
  337. nautobot/extras/managers.py +30 -7
  338. nautobot/extras/migrations/0001_initial_part_1.py +0 -1
  339. nautobot/extras/migrations/0002_initial_part_2.py +0 -1
  340. nautobot/extras/migrations/0003_initial_part_3.py +0 -1
  341. nautobot/extras/migrations/0004_populate_default_status_records.py +0 -1
  342. nautobot/extras/migrations/0005_configcontext_device_types.py +0 -1
  343. nautobot/extras/migrations/0006_graphqlquery.py +0 -1
  344. nautobot/extras/migrations/0007_configcontextschema.py +0 -1
  345. nautobot/extras/migrations/0008_jobresult__custom_field_data.py +0 -1
  346. nautobot/extras/migrations/0009_computedfield.py +0 -1
  347. nautobot/extras/migrations/0010_change_cf_validation_max_min_field_to_bigint.py +0 -1
  348. nautobot/extras/migrations/0011_fileattachment_fileproxy.py +0 -1
  349. nautobot/extras/migrations/0012_healthchecktestmodel.py +0 -1
  350. nautobot/extras/migrations/0013_default_fallback_value_computedfield.py +0 -1
  351. nautobot/extras/migrations/0014_auto_slug.py +0 -1
  352. nautobot/extras/migrations/0015_scheduled_job.py +0 -1
  353. nautobot/extras/migrations/0016_secret.py +0 -1
  354. nautobot/extras/migrations/0017_joblogentry.py +0 -1
  355. nautobot/extras/migrations/0018_joblog_data_migration.py +0 -2
  356. nautobot/extras/migrations/0019_joblogentry__meta_options__related_name.py +0 -1
  357. nautobot/extras/migrations/0020_customfield_changelog.py +0 -1
  358. nautobot/extras/migrations/0021_customfield_changelog_data.py +0 -1
  359. nautobot/extras/migrations/0022_objectchange_object_datav2.py +0 -1
  360. nautobot/extras/migrations/0023_job_model.py +0 -1
  361. nautobot/extras/migrations/0024_job_data_migration.py +0 -1
  362. nautobot/extras/migrations/0025_add_advanced_ui_boolean_to_customfield_conputedfield_and_relationship.py +0 -1
  363. nautobot/extras/migrations/0026_job_add_gitrepository_fk.py +0 -1
  364. nautobot/extras/migrations/0027_job_gitrepository_data_migration.py +0 -1
  365. nautobot/extras/migrations/0028_job_reduce_source.py +0 -1
  366. nautobot/extras/migrations/0029_dynamicgroup.py +0 -1
  367. nautobot/extras/migrations/0030_webhook_alter_unique_together.py +0 -1
  368. nautobot/extras/migrations/0031_tag_content_types.py +0 -1
  369. nautobot/extras/migrations/0032_tag_content_types_data_migration.py +0 -1
  370. nautobot/extras/migrations/0033_add__optimized_indexing.py +0 -1
  371. nautobot/extras/migrations/0034_alter_fileattachment_mimetype.py +0 -1
  372. nautobot/extras/migrations/0035_scheduledjob_crontab.py +0 -1
  373. nautobot/extras/migrations/0036_job_add_has_sensitive_variables.py +0 -1
  374. nautobot/extras/migrations/0037_configcontextschema__remove_name_unique__create_constraint_unique_name_owner.py +0 -1
  375. nautobot/extras/migrations/0038_configcontext_locations.py +0 -1
  376. nautobot/extras/migrations/0039_objectchange__add_change_context.py +0 -1
  377. nautobot/extras/migrations/0040_dynamicgroup__dynamicgroupmembership.py +0 -1
  378. nautobot/extras/migrations/0041_jobresult_job_kwargs.py +0 -1
  379. nautobot/extras/migrations/0042_job__add_is_job_hook_receiver.py +0 -1
  380. nautobot/extras/migrations/0043_note.py +0 -1
  381. nautobot/extras/migrations/0044_add_job_hook.py +0 -1
  382. nautobot/extras/migrations/0045_add_custom_field_slug.py +0 -1
  383. nautobot/extras/migrations/0046_populate_custom_field_slug_label.py +0 -1
  384. nautobot/extras/migrations/0047_enforce_custom_field_slug.py +0 -1
  385. nautobot/extras/migrations/0048_alter_objectchange_change_context_detail.py +0 -1
  386. nautobot/extras/migrations/0049_alter_tag_slug.py +0 -1
  387. nautobot/extras/migrations/0050_customfield_grouping.py +0 -1
  388. nautobot/extras/migrations/0051_add_job_task_queues.py +0 -1
  389. nautobot/extras/migrations/0052_configcontext_device_redundancy_groups.py +0 -1
  390. nautobot/extras/migrations/0053_relationship_required_on.py +0 -1
  391. nautobot/extras/migrations/0054_scheduledjob_kwargs_request_user_change.py +0 -1
  392. nautobot/extras/migrations/0055_configcontext_dynamic_groups.py +0 -1
  393. nautobot/extras/migrations/0056_objectchange_add_reverse_time_idx.py +0 -1
  394. nautobot/extras/migrations/0057_jobbutton.py +0 -1
  395. nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
  396. nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -2
  397. nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -2
  398. nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -8
  399. nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -33
  400. nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -2
  401. nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -2
  402. nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
  403. nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -2
  404. nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -3
  405. nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -2
  406. nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -2
  407. nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
  408. nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -2
  409. nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
  410. nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -10
  411. nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -2
  412. nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
  413. nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
  414. 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
  415. nautobot/extras/migrations/0078_remove_slug.py +45 -0
  416. nautobot/extras/migrations/0079_tagsfield.py +28 -0
  417. nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
  418. nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
  419. nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
  420. nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
  421. nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
  422. nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
  423. nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
  424. nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
  425. nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
  426. nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
  427. nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
  428. nautobot/extras/models/__init__.py +2 -3
  429. nautobot/extras/models/change_logging.py +0 -36
  430. nautobot/extras/models/customfields.py +39 -33
  431. nautobot/extras/models/datasources.py +48 -50
  432. nautobot/extras/models/groups.py +5 -12
  433. nautobot/extras/models/jobs.py +190 -323
  434. nautobot/extras/models/mixins.py +0 -71
  435. nautobot/extras/models/models.py +1 -22
  436. nautobot/extras/models/relationships.py +20 -21
  437. nautobot/extras/models/roles.py +0 -23
  438. nautobot/extras/models/secrets.py +2 -31
  439. nautobot/extras/models/statuses.py +6 -5
  440. nautobot/extras/models/tags.py +2 -17
  441. nautobot/extras/navigation.py +89 -307
  442. nautobot/extras/plugins/__init__.py +3 -121
  443. nautobot/extras/plugins/utils.py +0 -3
  444. nautobot/extras/plugins/validators.py +5 -4
  445. nautobot/extras/plugins/views.py +16 -4
  446. nautobot/extras/querysets.py +1 -7
  447. nautobot/extras/registry.py +3 -0
  448. nautobot/extras/signals.py +26 -60
  449. nautobot/extras/tables.py +42 -49
  450. nautobot/extras/tasks.py +0 -12
  451. nautobot/extras/templates/extras/configcontext.html +1 -1
  452. nautobot/extras/templates/extras/configcontextschema.html +16 -1
  453. nautobot/extras/templates/extras/customfield.html +0 -13
  454. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -1
  455. nautobot/extras/templates/extras/gitrepository.html +3 -3
  456. nautobot/extras/templates/extras/inc/jobresult.html +10 -0
  457. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  458. nautobot/extras/templates/extras/job.html +35 -25
  459. nautobot/extras/templates/extras/job_approval_request.html +15 -30
  460. nautobot/extras/templates/extras/job_detail.html +13 -31
  461. nautobot/extras/templates/extras/job_edit.html +14 -17
  462. nautobot/extras/templates/extras/jobresult.html +24 -6
  463. nautobot/extras/templates/extras/objectchange_list.html +1 -1
  464. nautobot/extras/templates/extras/scheduledjob.html +2 -2
  465. nautobot/extras/templates/extras/secret.html +28 -0
  466. nautobot/extras/templates/extras/secret_edit.html +0 -1
  467. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -1
  468. nautobot/extras/templatetags/custom_links.py +0 -2
  469. nautobot/extras/templatetags/job_buttons.py +1 -0
  470. nautobot/extras/templatetags/plugins.py +0 -1
  471. nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
  472. nautobot/extras/test_jobs/atomic_transaction.py +53 -0
  473. nautobot/extras/test_jobs/dry_run.py +29 -0
  474. nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
  475. nautobot/extras/test_jobs/duplicate_name2.py +9 -0
  476. nautobot/extras/test_jobs/fail.py +23 -0
  477. nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
  478. nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
  479. nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
  480. nautobot/extras/test_jobs/file_upload_pass.py +25 -0
  481. nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
  482. nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
  483. nautobot/extras/test_jobs/job_button_receiver.py +28 -0
  484. nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
  485. nautobot/extras/test_jobs/job_variables.py +88 -0
  486. nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
  487. nautobot/extras/test_jobs/log_redaction.py +20 -0
  488. nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
  489. nautobot/extras/test_jobs/modify_db.py +25 -0
  490. nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
  491. nautobot/extras/test_jobs/object_var_optional.py +21 -0
  492. nautobot/extras/test_jobs/object_var_required.py +21 -0
  493. nautobot/extras/test_jobs/object_vars.py +26 -0
  494. nautobot/extras/test_jobs/pass.py +25 -0
  495. nautobot/extras/test_jobs/profiling.py +32 -0
  496. nautobot/extras/test_jobs/read_only_job.py +15 -0
  497. nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
  498. 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
  499. nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
  500. nautobot/extras/tests/integration/__init__.py +3 -3
  501. nautobot/extras/tests/integration/test_computedfields.py +1 -1
  502. nautobot/extras/tests/integration/test_configcontextschema.py +7 -5
  503. nautobot/extras/tests/integration/test_customfields.py +4 -2
  504. nautobot/extras/tests/integration/test_dynamicgroups.py +2 -2
  505. nautobot/extras/tests/integration/test_jobs.py +25 -27
  506. nautobot/extras/tests/integration/test_notes.py +8 -4
  507. nautobot/extras/tests/integration/test_plugins.py +4 -4
  508. nautobot/extras/tests/integration/test_relationships.py +2 -2
  509. nautobot/extras/tests/test_api.py +371 -381
  510. nautobot/extras/tests/test_changelog.py +17 -16
  511. nautobot/extras/tests/test_context_managers.py +5 -6
  512. nautobot/extras/tests/test_customfields.py +112 -73
  513. nautobot/extras/tests/test_datasources.py +191 -117
  514. nautobot/extras/tests/test_dynamicgroups.py +45 -68
  515. nautobot/extras/tests/test_filters.py +170 -130
  516. nautobot/extras/tests/test_forms.py +107 -109
  517. nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
  518. nautobot/extras/tests/test_jobs.py +271 -273
  519. nautobot/extras/tests/test_management.py +3 -6
  520. nautobot/extras/tests/test_migrations.py +5 -3
  521. nautobot/extras/tests/test_models.py +121 -173
  522. nautobot/extras/tests/test_notes.py +0 -1
  523. nautobot/extras/tests/test_plugins.py +55 -89
  524. nautobot/extras/tests/test_relationships.py +174 -130
  525. nautobot/extras/tests/test_tags.py +6 -12
  526. nautobot/extras/tests/test_utils.py +31 -1
  527. nautobot/extras/tests/test_views.py +223 -184
  528. nautobot/extras/tests/test_webhooks.py +16 -15
  529. nautobot/extras/urls.py +69 -69
  530. nautobot/extras/utils.py +137 -163
  531. nautobot/extras/views.py +81 -153
  532. nautobot/ipam/api/fields.py +17 -0
  533. nautobot/ipam/api/serializers.py +77 -164
  534. nautobot/ipam/api/urls.py +4 -1
  535. nautobot/ipam/api/views.py +28 -19
  536. nautobot/ipam/apps.py +1 -0
  537. nautobot/ipam/choices.py +5 -12
  538. nautobot/ipam/constants.py +1 -0
  539. nautobot/ipam/factory.py +41 -30
  540. nautobot/ipam/filters.py +58 -25
  541. nautobot/ipam/forms.py +82 -211
  542. nautobot/ipam/graphql/types.py +0 -9
  543. nautobot/ipam/lookups.py +13 -8
  544. nautobot/ipam/management/commands/__init__.py +0 -0
  545. nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
  546. nautobot/ipam/migrations/0001_initial_part_1.py +0 -1
  547. nautobot/ipam/migrations/0002_initial_part_2.py +0 -1
  548. nautobot/ipam/migrations/0003_remove_max_length.py +0 -1
  549. nautobot/ipam/migrations/0004_fixup_p2p_broadcast.py +0 -1
  550. nautobot/ipam/migrations/0005_auto_slug.py +0 -1
  551. nautobot/ipam/migrations/0006_ipaddress_nat_outside_list.py +0 -1
  552. nautobot/ipam/migrations/0007_add_natural_indexing.py +0 -1
  553. nautobot/ipam/migrations/0008_prefix_vlan_vlangroup_location.py +0 -1
  554. nautobot/ipam/migrations/0009_alter_vlan_name.py +0 -1
  555. nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -2
  556. nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -39
  557. nautobot/ipam/migrations/0012_rename_ipam_roles.py +0 -1
  558. nautobot/ipam/migrations/0013_delete_role.py +0 -1
  559. nautobot/ipam/migrations/0014_rename_foreign_keys_and_related_names.py +0 -1
  560. nautobot/ipam/migrations/0015_prefix_add_type.py +0 -1
  561. nautobot/ipam/migrations/0016_prefix_type_data_migration.py +0 -3
  562. nautobot/ipam/migrations/0017_prefix_remove_is_pool.py +0 -1
  563. nautobot/ipam/migrations/0018_remove_site_foreign_key_from_ipam_models.py +0 -1
  564. nautobot/ipam/migrations/0019_created_datetime.py +0 -1
  565. nautobot/ipam/migrations/0020_related_name_changes.py +1 -2
  566. nautobot/ipam/migrations/0021_prefix_add_rir_and_date_allocated.py +0 -1
  567. nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +3 -5
  568. nautobot/ipam/migrations/0023_delete_aggregate.py +0 -1
  569. nautobot/ipam/migrations/0024_interface_to_ipaddress_m2m.py +0 -1
  570. nautobot/ipam/migrations/0025_interface_ipaddress_m2m_data_migration.py +0 -1
  571. nautobot/ipam/migrations/0026_ipaddress_remove_assigned_object.py +0 -1
  572. nautobot/ipam/migrations/0027_remove_rir_slug.py +16 -0
  573. nautobot/ipam/migrations/0028_tagsfield.py +44 -0
  574. nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
  575. nautobot/ipam/migrations/0030_ipam__namespaces.py +231 -0
  576. nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
  577. nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
  578. nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
  579. nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
  580. nautobot/ipam/models.py +579 -368
  581. nautobot/ipam/navigation.py +36 -159
  582. nautobot/ipam/querysets.py +117 -90
  583. nautobot/ipam/signals.py +89 -0
  584. nautobot/ipam/tables.py +86 -28
  585. nautobot/ipam/templates/ipam/ipaddress.html +14 -30
  586. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -0
  587. nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
  588. nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
  589. nautobot/ipam/templates/ipam/namespace_retrieve.html +42 -0
  590. nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
  591. nautobot/ipam/templates/ipam/prefix.html +27 -33
  592. nautobot/ipam/templates/ipam/prefix_edit.html +7 -1
  593. nautobot/ipam/templates/ipam/vlangroup.html +0 -13
  594. nautobot/ipam/templates/ipam/vrf.html +6 -4
  595. nautobot/ipam/templates/ipam/vrf_edit.html +20 -2
  596. nautobot/ipam/tests/integration/test_prefixes.py +4 -27
  597. nautobot/ipam/tests/test_api.py +60 -61
  598. nautobot/ipam/tests/test_filters.py +187 -126
  599. nautobot/ipam/tests/test_forms.py +12 -6
  600. nautobot/ipam/tests/test_graphql.py +8 -6
  601. nautobot/ipam/tests/test_migrations.py +8 -13
  602. nautobot/ipam/tests/test_models.py +426 -274
  603. nautobot/ipam/tests/test_ordering.py +6 -3
  604. nautobot/ipam/tests/test_querysets.py +340 -96
  605. nautobot/ipam/tests/test_views.py +100 -55
  606. nautobot/ipam/urls.py +28 -5
  607. nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
  608. nautobot/ipam/utils/migrations.py +713 -0
  609. nautobot/ipam/views.py +237 -122
  610. nautobot/project-static/docs/404.html +1399 -166
  611. nautobot/project-static/docs/additional-features/caching.html +1416 -320
  612. nautobot/project-static/docs/additional-features/change-logging.html +1389 -190
  613. nautobot/project-static/docs/additional-features/config-contexts.html +1389 -190
  614. nautobot/project-static/docs/additional-features/graphql.html +1389 -190
  615. nautobot/project-static/docs/additional-features/healthcheck.html +1389 -190
  616. nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1393 -190
  617. nautobot/project-static/docs/additional-features/jobs.html +1677 -460
  618. nautobot/project-static/docs/additional-features/napalm.html +1389 -190
  619. nautobot/project-static/docs/additional-features/prometheus-metrics.html +1389 -190
  620. nautobot/project-static/docs/additional-features/template-filters.html +1389 -190
  621. nautobot/project-static/docs/administration/celery-queues.html +1389 -190
  622. nautobot/project-static/docs/administration/nautobot-server.html +1553 -375
  623. nautobot/project-static/docs/administration/nautobot-shell.html +1395 -196
  624. nautobot/project-static/docs/administration/permissions.html +1389 -190
  625. nautobot/project-static/docs/administration/replicating-nautobot.html +1387 -207
  626. nautobot/project-static/docs/apps/index.html +1389 -190
  627. nautobot/project-static/docs/apps/nautobot-apps.html +1387 -175
  628. nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js +29 -0
  629. nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js.map +8 -0
  630. nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js → search.208ed371.min.js} +9 -15
  631. nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js.map → search.208ed371.min.js.map} +4 -4
  632. nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css +1 -0
  633. nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css.map +1 -0
  634. nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css +1 -0
  635. nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css.map +1 -0
  636. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1775 -590
  637. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1389 -190
  638. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3588 -1922
  639. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1461 -262
  640. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1401 -170
  641. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1396 -191
  642. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2095 -894
  643. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2357 -1194
  644. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2258 -940
  645. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1389 -190
  646. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1400 -201
  647. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +11068 -7861
  648. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2867 -2224
  649. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1389 -190
  650. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2641 -1573
  651. nautobot/project-static/docs/configuration/authentication/ldap.html +1389 -190
  652. nautobot/project-static/docs/configuration/authentication/remote.html +1389 -190
  653. nautobot/project-static/docs/configuration/authentication/sso.html +1389 -190
  654. nautobot/project-static/docs/configuration/index.html +1398 -199
  655. nautobot/project-static/docs/configuration/optional-settings.html +1418 -274
  656. nautobot/project-static/docs/configuration/required-settings.html +1419 -287
  657. nautobot/project-static/docs/core-functionality/circuits.html +1446 -247
  658. nautobot/project-static/docs/core-functionality/device-types.html +1448 -249
  659. nautobot/project-static/docs/core-functionality/devices.html +1452 -249
  660. nautobot/project-static/docs/core-functionality/ipam.html +1452 -253
  661. nautobot/project-static/docs/core-functionality/power.html +1448 -249
  662. nautobot/project-static/docs/core-functionality/secrets.html +1448 -249
  663. nautobot/project-static/docs/core-functionality/services.html +1448 -249
  664. nautobot/project-static/docs/core-functionality/sites-and-racks.html +1448 -249
  665. nautobot/project-static/docs/core-functionality/tenancy.html +1448 -249
  666. nautobot/project-static/docs/core-functionality/virtualization.html +1452 -249
  667. nautobot/project-static/docs/core-functionality/vlans.html +1448 -249
  668. nautobot/project-static/docs/development/application-registry.html +1393 -214
  669. nautobot/project-static/docs/development/best-practices.html +1392 -192
  670. nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1390 -191
  671. nautobot/project-static/docs/development/extending-models.html +1443 -257
  672. nautobot/project-static/docs/development/generic-views.html +1403 -174
  673. nautobot/project-static/docs/development/getting-started.html +1568 -262
  674. nautobot/project-static/docs/development/homepage.html +1389 -190
  675. nautobot/project-static/docs/development/index.html +1389 -190
  676. nautobot/project-static/docs/development/model-features.html +1389 -190
  677. nautobot/project-static/docs/development/natural-keys.html +1389 -190
  678. nautobot/project-static/docs/development/navigation-menu.html +1451 -330
  679. nautobot/project-static/docs/development/react-ui.html +4199 -0
  680. nautobot/project-static/docs/development/release-checklist.html +1392 -193
  681. nautobot/project-static/docs/development/role-internals.html +1402 -172
  682. nautobot/project-static/docs/development/style-guide.html +1399 -199
  683. nautobot/project-static/docs/development/templates.html +1391 -191
  684. nautobot/project-static/docs/development/testing.html +1389 -190
  685. nautobot/project-static/docs/development/user-preferences.html +1389 -190
  686. nautobot/project-static/docs/docker/index.html +1408 -206
  687. nautobot/project-static/docs/index.html +1397 -180
  688. nautobot/project-static/docs/installation/centos.html +1401 -170
  689. nautobot/project-static/docs/installation/external-authentication.html +1389 -190
  690. nautobot/project-static/docs/installation/http-server.html +1389 -190
  691. nautobot/project-static/docs/installation/index.html +1394 -191
  692. nautobot/project-static/docs/installation/migrating-from-netbox.html +1452 -305
  693. nautobot/project-static/docs/installation/migrating-from-postgresql.html +1390 -191
  694. nautobot/project-static/docs/installation/nautobot.html +1390 -191
  695. nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1389 -190
  696. nautobot/project-static/docs/installation/selinux-troubleshooting.html +1401 -170
  697. nautobot/project-static/docs/installation/services.html +1389 -190
  698. nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  699. nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  700. nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  701. nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  702. nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
  703. nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  704. nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  705. nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  706. nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  707. nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  708. nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  709. nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  710. nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  711. nautobot/project-static/docs/installation/ubuntu.html +1401 -170
  712. nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +4254 -1923
  713. nautobot/project-static/docs/installation/upgrading.html +1395 -192
  714. nautobot/project-static/docs/models/circuits/circuit.html +1427 -174
  715. nautobot/project-static/docs/models/circuits/circuittermination.html +1427 -174
  716. nautobot/project-static/docs/models/circuits/circuittype.html +1427 -174
  717. nautobot/project-static/docs/models/circuits/provider.html +1427 -174
  718. nautobot/project-static/docs/models/circuits/providernetwork.html +1427 -174
  719. nautobot/project-static/docs/models/dcim/cable.html +1458 -174
  720. nautobot/project-static/docs/models/dcim/consoleport.html +1427 -174
  721. nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1427 -174
  722. nautobot/project-static/docs/models/dcim/consoleserverport.html +1427 -174
  723. nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1427 -174
  724. nautobot/project-static/docs/models/dcim/device.html +1431 -174
  725. nautobot/project-static/docs/models/dcim/devicebay.html +1427 -174
  726. nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1427 -174
  727. nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1522 -177
  728. nautobot/project-static/docs/models/dcim/devicetype.html +1427 -174
  729. nautobot/project-static/docs/models/dcim/frontport.html +1427 -174
  730. nautobot/project-static/docs/models/dcim/frontporttemplate.html +1427 -174
  731. nautobot/project-static/docs/models/dcim/interface.html +1427 -174
  732. nautobot/project-static/docs/models/dcim/interfacetemplate.html +1427 -174
  733. nautobot/project-static/docs/models/dcim/inventoryitem.html +1427 -174
  734. nautobot/project-static/docs/models/dcim/location.html +1427 -174
  735. nautobot/project-static/docs/models/dcim/locationtype.html +1427 -174
  736. nautobot/project-static/docs/models/dcim/manufacturer.html +1427 -174
  737. nautobot/project-static/docs/models/dcim/platform.html +1427 -174
  738. nautobot/project-static/docs/models/dcim/powerfeed.html +1425 -172
  739. nautobot/project-static/docs/models/dcim/poweroutlet.html +1427 -174
  740. nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1427 -174
  741. nautobot/project-static/docs/models/dcim/powerpanel.html +1425 -172
  742. nautobot/project-static/docs/models/dcim/powerport.html +1427 -174
  743. nautobot/project-static/docs/models/dcim/powerporttemplate.html +1427 -174
  744. nautobot/project-static/docs/models/dcim/rack.html +1427 -174
  745. nautobot/project-static/docs/models/dcim/rackgroup.html +1427 -174
  746. nautobot/project-static/docs/models/dcim/rackreservation.html +1427 -174
  747. nautobot/project-static/docs/models/dcim/rearport.html +1427 -174
  748. nautobot/project-static/docs/models/dcim/rearporttemplate.html +1427 -174
  749. nautobot/project-static/docs/models/dcim/region.html +1401 -170
  750. nautobot/project-static/docs/models/dcim/site.html +1401 -170
  751. nautobot/project-static/docs/models/dcim/virtualchassis.html +1425 -172
  752. nautobot/project-static/docs/models/extras/computedfield.html +1393 -194
  753. nautobot/project-static/docs/models/extras/configcontext.html +1465 -174
  754. nautobot/project-static/docs/models/extras/configcontextschema.html +1421 -168
  755. nautobot/project-static/docs/models/extras/customfield.html +1389 -190
  756. nautobot/project-static/docs/models/extras/customlink.html +1389 -190
  757. nautobot/project-static/docs/models/extras/dynamicgroup.html +1398 -199
  758. nautobot/project-static/docs/models/extras/exporttemplate.html +1389 -190
  759. nautobot/project-static/docs/models/extras/gitrepository.html +1393 -190
  760. nautobot/project-static/docs/models/extras/graphqlquery.html +1469 -171
  761. nautobot/project-static/docs/models/extras/imageattachment.html +1434 -181
  762. nautobot/project-static/docs/models/extras/job.html +1411 -157
  763. nautobot/project-static/docs/models/extras/jobbutton.html +1410 -207
  764. nautobot/project-static/docs/models/extras/jobhook.html +1397 -194
  765. nautobot/project-static/docs/models/extras/joblogentry.html +1408 -155
  766. nautobot/project-static/docs/models/extras/jobresult.html +1417 -159
  767. nautobot/project-static/docs/models/extras/note.html +1389 -190
  768. nautobot/project-static/docs/models/extras/relationship.html +1391 -192
  769. nautobot/project-static/docs/models/extras/role.html +1495 -198
  770. nautobot/project-static/docs/models/extras/secret.html +1492 -201
  771. nautobot/project-static/docs/models/extras/secretsgroup.html +1410 -157
  772. nautobot/project-static/docs/models/extras/status.html +1381 -221
  773. nautobot/project-static/docs/models/extras/tag.html +1389 -190
  774. nautobot/project-static/docs/models/extras/webhook.html +1389 -190
  775. nautobot/project-static/docs/models/ipam/ipaddress.html +1488 -200
  776. nautobot/project-static/docs/models/ipam/prefix.html +1410 -157
  777. nautobot/project-static/docs/models/ipam/rir.html +1410 -157
  778. nautobot/project-static/docs/models/ipam/routetarget.html +1410 -157
  779. nautobot/project-static/docs/models/ipam/service.html +1410 -157
  780. nautobot/project-static/docs/models/ipam/vlan.html +1410 -157
  781. nautobot/project-static/docs/models/ipam/vlangroup.html +1410 -157
  782. nautobot/project-static/docs/models/ipam/vrf.html +1410 -161
  783. nautobot/project-static/docs/models/tenancy/tenant.html +1412 -159
  784. nautobot/project-static/docs/models/tenancy/tenantgroup.html +1412 -159
  785. nautobot/project-static/docs/models/users/objectpermission.html +1462 -171
  786. nautobot/project-static/docs/models/users/token.html +1410 -157
  787. nautobot/project-static/docs/models/virtualization/cluster.html +1410 -157
  788. nautobot/project-static/docs/models/virtualization/clustergroup.html +1410 -157
  789. nautobot/project-static/docs/models/virtualization/clustertype.html +1410 -157
  790. nautobot/project-static/docs/models/virtualization/virtualmachine.html +1414 -157
  791. nautobot/project-static/docs/models/virtualization/vminterface.html +1410 -157
  792. nautobot/project-static/docs/objects.inv +0 -0
  793. nautobot/project-static/docs/plugins/development.html +1916 -646
  794. nautobot/project-static/docs/plugins/index.html +1389 -190
  795. nautobot/project-static/docs/plugins/porting-from-netbox.html +1389 -190
  796. nautobot/project-static/docs/release-notes/index.html +1389 -190
  797. nautobot/project-static/docs/release-notes/version-1.0.html +1389 -190
  798. nautobot/project-static/docs/release-notes/version-1.1.html +1389 -190
  799. nautobot/project-static/docs/release-notes/version-1.2.html +1389 -190
  800. nautobot/project-static/docs/release-notes/version-1.3.html +1389 -190
  801. nautobot/project-static/docs/release-notes/version-1.4.html +1389 -190
  802. nautobot/project-static/docs/release-notes/version-1.5.html +2016 -397
  803. nautobot/project-static/docs/release-notes/version-2.0.html +1935 -287
  804. nautobot/project-static/docs/requirements.txt +5 -4
  805. nautobot/project-static/docs/rest-api/authentication.html +1389 -190
  806. nautobot/project-static/docs/rest-api/filtering.html +1389 -190
  807. nautobot/project-static/docs/rest-api/overview.html +2002 -576
  808. nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
  809. nautobot/project-static/docs/search/search_index.json +1 -1
  810. nautobot/project-static/docs/sitemap.xml +197 -187
  811. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  812. nautobot/project-static/docs/user-guides/custom-fields.html +1390 -191
  813. nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1392 -193
  814. nautobot/project-static/docs/user-guides/getting-started/index.html +1388 -189
  815. nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1388 -189
  816. nautobot/project-static/docs/user-guides/getting-started/ipam.html +1386 -187
  817. nautobot/project-static/docs/user-guides/getting-started/platforms.html +1448 -249
  818. nautobot/project-static/docs/user-guides/getting-started/regions.html +1411 -212
  819. nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1395 -196
  820. nautobot/project-static/docs/user-guides/getting-started/tenants.html +1448 -249
  821. nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1448 -249
  822. nautobot/project-static/docs/user-guides/git-data-source.html +1405 -206
  823. nautobot/project-static/docs/user-guides/graphql.html +1402 -203
  824. nautobot/project-static/docs/user-guides/relationships.html +1448 -249
  825. nautobot/project-static/docs/user-guides/s3-django-storage.html +1448 -249
  826. nautobot/project-static/js/forms.js +16 -9
  827. nautobot/project-static/js/theme.js +5 -0
  828. nautobot/tenancy/api/serializers.py +4 -34
  829. nautobot/tenancy/api/urls.py +1 -1
  830. nautobot/tenancy/filters/__init__.py +9 -7
  831. nautobot/tenancy/filters/mixins.py +3 -2
  832. nautobot/tenancy/forms.py +3 -36
  833. nautobot/tenancy/migrations/0001_initial.py +0 -1
  834. nautobot/tenancy/migrations/0002_auto_slug.py +0 -1
  835. nautobot/tenancy/migrations/0003_mptt_to_tree_queries.py +0 -1
  836. nautobot/tenancy/migrations/0004_change_tree_manager_on_tree_models.py +0 -1
  837. nautobot/tenancy/migrations/0005_rename_foreign_keys_and_related_names.py +0 -1
  838. nautobot/tenancy/migrations/0006_created_datetime.py +0 -1
  839. nautobot/tenancy/migrations/0007_remove_tenant_tenantgroup_slug.py +20 -0
  840. nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
  841. nautobot/tenancy/models.py +0 -30
  842. nautobot/tenancy/navigation.py +6 -39
  843. nautobot/tenancy/tables.py +4 -4
  844. nautobot/tenancy/templates/tenancy/tenant.html +12 -12
  845. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -1
  846. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  847. nautobot/tenancy/tests/test_api.py +1 -12
  848. nautobot/tenancy/tests/test_filters.py +20 -12
  849. nautobot/tenancy/tests/test_views.py +11 -29
  850. nautobot/tenancy/urls.py +10 -10
  851. nautobot/tenancy/views.py +0 -3
  852. nautobot/ui/.eslintignore +6 -0
  853. nautobot/ui/.gitignore +10 -0
  854. nautobot/ui/.prettierignore +9 -0
  855. nautobot/ui/.prettierrc +4 -0
  856. nautobot/ui/README.md +33 -0
  857. nautobot/ui/app_imports.js.j2 +7 -0
  858. nautobot/ui/craco.config.js +46 -0
  859. nautobot/ui/jsconfig-base.json +11 -0
  860. nautobot/ui/jsconfig.json +5 -0
  861. nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
  862. nautobot/ui/package-lock.json +21451 -0
  863. nautobot/ui/package.json +70 -0
  864. nautobot/ui/public/index.html +47 -0
  865. nautobot/ui/public/logo192.png +0 -0
  866. nautobot/ui/public/logo512.png +0 -0
  867. nautobot/ui/public/manifest.json +25 -0
  868. nautobot/ui/public/nautobot_logo.svg +131 -0
  869. nautobot/ui/public/robots.txt +3 -0
  870. nautobot/ui/src/App.js +71 -0
  871. nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
  872. nautobot/ui/src/components/AppTab.js +40 -0
  873. nautobot/ui/src/components/Apps.js +60 -0
  874. nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
  875. nautobot/ui/src/components/HomePanel.js +58 -0
  876. nautobot/ui/src/components/JobHistoryTable.js +78 -0
  877. nautobot/ui/src/components/Layout.js +53 -0
  878. nautobot/ui/src/components/LoadingWidget.js +25 -0
  879. nautobot/ui/src/components/Navbar.js +116 -0
  880. nautobot/ui/src/components/NotificationPopover.js +27 -0
  881. nautobot/ui/src/components/ObjectListTable.js +209 -0
  882. nautobot/ui/src/components/ReferenceDataTag.js +35 -0
  883. nautobot/ui/src/components/RouterButton.js +10 -0
  884. nautobot/ui/src/components/RouterLink.js +10 -0
  885. nautobot/ui/src/components/SidebarNav.js +147 -0
  886. nautobot/ui/src/components/Table.js +48 -0
  887. nautobot/ui/src/components/TableItem.js +71 -0
  888. nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
  889. nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
  890. nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
  891. nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
  892. nautobot/ui/src/components/__tests__/Table.test.js +36 -0
  893. nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
  894. nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
  895. nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
  896. nautobot/ui/src/components/pagination.js +93 -0
  897. nautobot/ui/src/components/paginator.js +79 -0
  898. nautobot/ui/src/components/paginator_form.js +43 -0
  899. nautobot/ui/src/components/usePagination.js +57 -0
  900. nautobot/ui/src/constants/apiPath.js +10 -0
  901. nautobot/ui/src/constants/icons.js +15 -0
  902. nautobot/ui/src/constants/size.js +15 -0
  903. nautobot/ui/src/index.js +65 -0
  904. nautobot/ui/src/reportWebVitals.js +15 -0
  905. nautobot/ui/src/router.js +77 -0
  906. nautobot/ui/src/utils/api.js +131 -0
  907. nautobot/ui/src/utils/app-import.js +15 -0
  908. nautobot/ui/src/utils/color.js +15 -0
  909. nautobot/ui/src/utils/date.js +14 -0
  910. nautobot/ui/src/utils/index.js +15 -0
  911. nautobot/ui/src/utils/navigation.js +32 -0
  912. nautobot/ui/src/utils/session.js +64 -0
  913. nautobot/ui/src/utils/store.js +242 -0
  914. nautobot/ui/src/utils/string.js +6 -0
  915. nautobot/ui/src/utils/url.js +4 -0
  916. nautobot/ui/src/views/Home.js +138 -0
  917. nautobot/ui/src/views/InstalledApps.js +80 -0
  918. nautobot/ui/src/views/Login.js +48 -0
  919. nautobot/ui/src/views/Logout.js +20 -0
  920. nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
  921. nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
  922. nautobot/ui/src/views/__tests__/Login.test.js +15 -0
  923. nautobot/ui/src/views/generic/GenericView.js +142 -0
  924. nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
  925. nautobot/ui/src/views/generic/ObjectList.js +127 -0
  926. nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
  927. nautobot/users/admin.py +1 -1
  928. nautobot/users/api/serializers.py +51 -61
  929. nautobot/users/api/urls.py +1 -1
  930. nautobot/users/api/views.py +53 -2
  931. nautobot/users/migrations/0001_initial.py +0 -1
  932. nautobot/users/migrations/0002_token_ordering_by_created.py +0 -1
  933. nautobot/users/migrations/0003_alter_user_options.py +0 -1
  934. nautobot/users/migrations/0004_alter_user_managers.py +0 -1
  935. nautobot/users/tests/test_api.py +109 -28
  936. nautobot/users/tests/test_filters.py +0 -4
  937. nautobot/users/tests/test_models.py +0 -1
  938. nautobot/users/views.py +0 -7
  939. nautobot/virtualization/api/serializers.py +18 -132
  940. nautobot/virtualization/api/urls.py +1 -1
  941. nautobot/virtualization/api/views.py +1 -22
  942. nautobot/virtualization/choices.py +0 -2
  943. nautobot/virtualization/filters.py +12 -7
  944. nautobot/virtualization/forms.py +21 -117
  945. nautobot/virtualization/migrations/0001_initial.py +0 -1
  946. nautobot/virtualization/migrations/0002_virtualmachine_local_context_schema.py +0 -1
  947. nautobot/virtualization/migrations/0003_vminterface_verbose_name.py +0 -1
  948. nautobot/virtualization/migrations/0004_auto_slug.py +0 -1
  949. nautobot/virtualization/migrations/0005_add_natural_indexing.py +0 -1
  950. nautobot/virtualization/migrations/0006_vminterface_status.py +0 -1
  951. nautobot/virtualization/migrations/0007_vminterface_status_data_migration.py +0 -1
  952. nautobot/virtualization/migrations/0008_vminterface_parent.py +0 -1
  953. nautobot/virtualization/migrations/0009_cluster_location.py +0 -1
  954. nautobot/virtualization/migrations/0010_vminterface_mac_address_data_migration.py +0 -1
  955. nautobot/virtualization/migrations/0011_alter_vminterface_mac_address.py +0 -1
  956. nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -2
  957. nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -12
  958. nautobot/virtualization/migrations/0014_rename_virtualmachine_roles.py +0 -1
  959. nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -2
  960. nautobot/virtualization/migrations/0016_remove_site_foreign_key_from_cluster_class.py +0 -1
  961. nautobot/virtualization/migrations/0017_created_datetime.py +0 -1
  962. nautobot/virtualization/migrations/0018_related_name_changes.py +1 -2
  963. nautobot/virtualization/migrations/0019_vminterface_ip_addresses_m2m.py +0 -1
  964. nautobot/virtualization/migrations/0020_remove_clustergroup_clustertype_slug.py +20 -0
  965. nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
  966. nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
  967. nautobot/virtualization/migrations/0023_ipam__namespaces.py +25 -0
  968. nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
  969. nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
  970. nautobot/virtualization/models.py +39 -131
  971. nautobot/virtualization/navigation.py +18 -99
  972. nautobot/virtualization/tables.py +4 -4
  973. nautobot/virtualization/templates/virtualization/virtualmachine.html +13 -2
  974. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
  975. nautobot/virtualization/tests/test_api.py +42 -52
  976. nautobot/virtualization/tests/test_filters.py +98 -75
  977. nautobot/virtualization/tests/test_models.py +36 -13
  978. nautobot/virtualization/tests/test_views.py +68 -73
  979. nautobot/virtualization/urls.py +10 -10
  980. nautobot/virtualization/views.py +8 -14
  981. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +15 -22
  982. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +987 -834
  983. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
  984. nautobot/circuits/api/nested_serializers.py +0 -69
  985. nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
  986. nautobot/dcim/api/nested_serializers.py +0 -356
  987. nautobot/dcim/templates/dcim/device_import.html +0 -5
  988. nautobot/dcim/templates/dcim/device_import_child.html +0 -5
  989. nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
  990. nautobot/extras/api/nested_serializers.py +0 -353
  991. nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -42
  992. nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -47
  993. nautobot/extras/reports.py +0 -60
  994. nautobot/extras/scripts.py +0 -72
  995. nautobot/extras/tests/example_jobs/script_variables.py +0 -67
  996. nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
  997. nautobot/extras/tests/example_jobs/test_fail.py +0 -16
  998. nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
  999. nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
  1000. nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
  1001. nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
  1002. nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
  1003. nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
  1004. nautobot/extras/tests/example_jobs/test_modify_db.py +0 -19
  1005. nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
  1006. nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
  1007. nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
  1008. nautobot/extras/tests/example_jobs/test_pass.py +0 -19
  1009. nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
  1010. nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
  1011. nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
  1012. nautobot/ipam/api/nested_serializers.py +0 -143
  1013. nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js +0 -29
  1014. nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js.map +0 -8
  1015. nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js +0 -18
  1016. nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js.map +0 -8
  1017. nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css +0 -1
  1018. nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css.map +0 -1
  1019. nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css +0 -1
  1020. nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css.map +0 -1
  1021. nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css +0 -1
  1022. nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css.map +0 -1
  1023. nautobot/tenancy/api/nested_serializers.py +0 -31
  1024. nautobot/users/api/nested_serializers.py +0 -67
  1025. nautobot/virtualization/api/nested_serializers.py +0 -65
  1026. /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
  1027. /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
  1028. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
  1029. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -1,25 +1,32 @@
1
+ import random
1
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.test import TestCase, override_settings
7
+ from django.db import connection, IntegrityError
8
+ from django.test import TestCase
8
9
 
9
10
  from nautobot.core.testing.models import ModelTestCases
10
11
  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
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
 
19
+ # TODO(jathan): Add VRF model tests.
20
+
21
+
18
22
  class IPAddressToInterfaceTest(TestCase):
19
23
  """Tests for `nautobot.ipam.models.IPAddressToInterface`."""
20
24
 
21
25
  @classmethod
22
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)
23
30
  cls.test_device = Device.objects.create(
24
31
  name="device1",
25
32
  role=Role.objects.get_for_model(Device).first(),
@@ -40,7 +47,7 @@ class IPAddressToInterfaceTest(TestCase):
40
47
  status=int_status,
41
48
  type=dcim_choices.InterfaceTypeChoices.TYPE_1GE_FIXED,
42
49
  )
43
- cluster_type = ClusterType.objects.create(name="cluster_type1")
50
+ cluster_type = ClusterType.objects.create(name="Cluster Type 1")
44
51
  cluster = Cluster.objects.create(name="cluster1", cluster_type=cluster_type)
45
52
  vmint_status = Status.objects.get_for_model(VMInterface).first()
46
53
  cls.test_vm = VirtualMachine.objects.create(
@@ -59,18 +66,109 @@ class IPAddressToInterfaceTest(TestCase):
59
66
  status=vmint_status,
60
67
  )
61
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
+
62
159
 
63
160
  class TestVarbinaryIPField(TestCase):
64
161
  """Tests for `nautobot.ipam.fields.VarbinaryIPField`."""
65
162
 
66
- def setUp(self):
67
- super().setUp()
68
-
163
+ @classmethod
164
+ def setUpTestData(cls):
69
165
  # Field is a VarbinaryIPField we'll use to test.
70
- self.prefix = Prefix.objects.create(prefix="10.0.0.0/24")
71
- self.field = self.prefix._meta.get_field("network")
72
- self.network = self.prefix.network
73
- 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)
74
172
 
75
173
  def test_db_type(self):
76
174
  """Test `VarbinaryIPField.db_type`."""
@@ -160,106 +258,207 @@ class TestVarbinaryIPField(TestCase):
160
258
  self.assertEqual(prepped, manual)
161
259
 
162
260
 
163
- class TestPrefix(TestCase): # TODO change to BaseModelTestCase
261
+ class TestPrefix(ModelTestCases.BaseModelTestCase):
262
+ model = Prefix
263
+
164
264
  def setUp(self):
165
265
  super().setUp()
266
+ # With advent of `Prefix.parent`, Prefixes can't just be bulk deleted without clearing their
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()
270
+ Prefix.objects.update(parent=None)
166
271
  Prefix.objects.all().delete()
272
+ self.namespace = Namespace.objects.first()
167
273
  self.statuses = Status.objects.get_for_model(Prefix)
274
+ self.status = self.statuses.first()
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)
168
280
 
169
281
  def test_prefix_validation(self):
170
282
  location_type = LocationType.objects.get(name="Room")
171
283
  location = Location.objects.filter(location_type=location_type).first()
172
- prefix = Prefix(prefix=netaddr.IPNetwork("192.0.2.0/24"), location=location)
173
- prefix.status = self.statuses.get(slug="active")
284
+ prefix = Prefix(prefix="192.0.2.0/24", location=location, status=self.statuses[0])
174
285
  with self.assertRaises(ValidationError) as cm:
175
286
  prefix.validated_save()
176
287
  self.assertIn(f'Prefixes may not associate to locations of type "{location_type.name}"', str(cm.exception))
177
288
 
178
- def test_get_duplicates(self):
179
- prefixes = (
180
- Prefix.objects.create(prefix=netaddr.IPNetwork("192.0.2.0/24")),
181
- Prefix.objects.create(prefix=netaddr.IPNetwork("192.0.2.0/24")),
182
- Prefix.objects.create(prefix=netaddr.IPNetwork("192.0.2.0/24")),
183
- )
184
- duplicate_prefix_pks = [p.pk for p in prefixes[0].get_duplicates()]
185
-
186
- self.assertSetEqual(set(duplicate_prefix_pks), {prefixes[1].pk, prefixes[2].pk})
187
-
188
- def test_get_child_prefixes(self):
189
- vrfs = VRF.objects.all()[:3]
289
+ def test_tree_methods(self):
290
+ """Test the various tree methods work as expected."""
291
+
292
+ # supernets()
293
+ self.assertEqual(list(self.root.supernets()), [])
294
+ self.assertEqual(list(self.child1.supernets()), [self.root, self.parent])
295
+ self.assertEqual(list(self.child1.supernets(include_self=True)), [self.root, self.parent, self.child1])
296
+ self.assertEqual(list(self.child1.supernets(direct=True)), [self.parent])
297
+
298
+ # subnets()
299
+ self.assertEqual(list(self.root.subnets()), [self.parent, self.child1, self.child2])
300
+ self.assertEqual(list(self.root.subnets(direct=True)), [self.parent])
301
+ self.assertEqual(list(self.root.subnets(include_self=True)), [self.root, self.parent, self.child1, self.child2])
302
+
303
+ # is_child_node()
304
+ self.assertFalse(self.root.is_child_node())
305
+ self.assertTrue(self.parent.is_child_node())
306
+ self.assertTrue(self.child1.is_child_node())
307
+
308
+ # is_leaf_node()
309
+ self.assertFalse(self.root.is_leaf_node())
310
+ self.assertFalse(self.parent.is_leaf_node())
311
+ self.assertTrue(self.child1.is_leaf_node())
312
+
313
+ # is_root_node()
314
+ self.assertTrue(self.root.is_root_node())
315
+ self.assertFalse(self.parent.is_leaf_node())
316
+ self.assertFalse(self.child1.is_root_node())
317
+
318
+ # ancestors()
319
+ self.assertEqual(list(self.child1.ancestors()), [self.root, self.parent])
320
+ self.assertEqual(list(self.child1.ancestors(ascending=True)), [self.parent, self.root])
321
+ self.assertEqual(list(self.child1.ancestors(include_self=True)), [self.root, self.parent, self.child1])
322
+
323
+ # children.all()
324
+ self.assertEqual(list(self.parent.children.all()), [self.child1, self.child2])
325
+
326
+ # descendants()
327
+ self.assertEqual(list(self.root.descendants()), [self.parent, self.child1, self.child2])
328
+ self.assertEqual(
329
+ list(self.root.descendants(include_self=True)), [self.root, self.parent, self.child1, self.child2]
330
+ )
331
+
332
+ # root()
333
+ self.assertEqual(self.child1.root(), self.root)
334
+ self.assertIsNone(self.root.root())
335
+
336
+ # siblings()
337
+ self.assertEqual(list(self.child1.siblings()), [self.child2])
338
+ self.assertEqual(list(self.child1.siblings(include_self=True)), [self.child1, self.child2])
339
+ parent2 = Prefix.objects.create(prefix="101.102.0.128/25", status=self.status, namespace=self.namespace)
340
+ self.assertEqual(list(self.parent.siblings()), [parent2])
341
+ self.assertEqual(list(self.parent.siblings(include_self=True)), [self.parent, parent2])
342
+
343
+ def test_reparenting(self):
344
+ """Test that reparenting algorithm works in its most basic form."""
345
+ # tree hierarchy
346
+ self.assertIsNone(self.root.parent)
347
+ self.assertEqual(self.parent.parent, self.root)
348
+ self.assertEqual(self.child1.parent, self.parent)
349
+
350
+ # Delete the parent (/25); child1/child2 now have root (/24) as their parent.
351
+ num_deleted, _ = self.parent.delete()
352
+ self.assertEqual(num_deleted, 1)
353
+
354
+ self.assertEqual(list(self.root.children.all()), [self.child1, self.child2])
355
+ self.child1.refresh_from_db()
356
+ self.child2.refresh_from_db()
357
+ self.assertEqual(self.child1.parent, self.root)
358
+ self.assertEqual(self.child2.parent, self.root)
359
+ self.assertEqual(list(self.child1.ancestors()), [self.root])
360
+
361
+ # Add /25 back in as a parent and assert that child1/child2 now have it as their parent, and
362
+ # /24 is its parent.
363
+ self.parent.save() # This creates another Prefix using the same instance.
364
+ self.child1.refresh_from_db()
365
+ self.child2.refresh_from_db()
366
+ self.assertEqual(self.child1.parent, self.parent)
367
+ self.assertEqual(self.child2.parent, self.parent)
368
+ self.assertEqual(list(self.child1.ancestors()), [self.root, self.parent])
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
+
403
+ def test_descendants(self):
190
404
  prefixes = (
191
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/16"), type=PrefixTypeChoices.TYPE_CONTAINER),
192
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/24"), vrf=None),
193
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.1.0/24"), vrf=vrfs[0]),
194
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.2.0/24"), vrf=vrfs[1]),
195
- Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.3.0/24"), vrf=vrfs[2]),
196
- )
197
- child_prefix_pks = {p.pk for p in prefixes[0].get_child_prefixes()}
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),
415
+ )
416
+ prefix_pks = {p.pk for p in prefixes[1:]}
417
+ child_prefix_pks = {p.pk for p in prefixes[0].descendants()}
198
418
 
199
419
  # Global container should return all children
200
- self.assertSetEqual(
201
- child_prefix_pks,
202
- {prefixes[1].pk, prefixes[2].pk, prefixes[3].pk, prefixes[4].pk},
203
- )
420
+ self.assertSetEqual(child_prefix_pks, prefix_pks)
204
421
 
205
- prefixes[0].vrf = vrfs[0]
206
- prefixes[0].save()
207
- child_prefix_pks = {p.pk for p in prefixes[0].get_child_prefixes()}
208
-
209
- # VRF container is limited to its own VRF
210
- self.assertSetEqual(child_prefix_pks, {prefixes[2].pk})
211
-
212
- def test_get_child_ips(self):
213
- vrfs = VRF.objects.all()[:3]
422
+ def test_child_ip_addresses(self):
214
423
  parent_prefix = Prefix.objects.create(
215
- prefix=netaddr.IPNetwork("10.0.0.0/16"), type=PrefixTypeChoices.TYPE_CONTAINER
424
+ prefix="10.0.0.0/16", status=self.status, namespace=self.namespace, type=PrefixTypeChoices.TYPE_CONTAINER
216
425
  )
217
426
  ips = (
218
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.0.1/24"), vrf=None),
219
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.1.1/24"), vrf=vrfs[0]),
220
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.2.1/24"), vrf=vrfs[1]),
221
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.3.1/24"), 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),
222
431
  )
223
- 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()}
224
433
 
225
434
  # Global container should return all children
226
435
  self.assertSetEqual(child_ip_pks, {ips[0].pk, ips[1].pk, ips[2].pk, ips[3].pk})
227
436
 
228
- parent_prefix.vrf = vrfs[0]
229
- parent_prefix.save()
230
- child_ip_pks = {p.pk for p in parent_prefix.get_child_ips()}
231
-
232
- # VRF container is limited to its own VRF
233
- self.assertSetEqual(child_ip_pks, {ips[1].pk})
234
-
235
437
  # Make sure /31 is handled correctly
236
438
  parent_prefix_31 = Prefix.objects.create(
237
- prefix=netaddr.IPNetwork("10.0.4.0/31"), type=PrefixTypeChoices.TYPE_CONTAINER
439
+ prefix="10.0.4.0/31", status=self.status, namespace=self.namespace, type=PrefixTypeChoices.TYPE_CONTAINER
238
440
  )
239
441
  ips_31 = (
240
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.4.0/31"), vrf=None),
241
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.4.1/31"), 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),
242
444
  )
243
-
244
- 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()}
245
446
  self.assertSetEqual(child_ip_pks, {ips_31[0].pk, ips_31[1].pk})
246
447
 
247
448
  def test_get_available_prefixes(self):
248
-
249
- prefixes = Prefix.objects.bulk_create(
250
- (
251
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/16")), # Parent prefix
252
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/20")),
253
- Prefix(prefix=netaddr.IPNetwork("10.0.32.0/20")),
254
- Prefix(prefix=netaddr.IPNetwork("10.0.128.0/18")),
255
- )
256
- )
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
257
456
  missing_prefixes = netaddr.IPSet(
258
457
  [
259
- netaddr.IPNetwork("10.0.16.0/20"),
260
- netaddr.IPNetwork("10.0.48.0/20"),
261
- netaddr.IPNetwork("10.0.64.0/18"),
262
- 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",
263
462
  ]
264
463
  )
265
464
  available_prefixes = prefixes[0].get_available_prefixes()
@@ -267,19 +466,17 @@ class TestPrefix(TestCase): # TODO change to BaseModelTestCase
267
466
  self.assertEqual(available_prefixes, missing_prefixes)
268
467
 
269
468
  def test_get_available_ips(self):
270
-
271
- parent_prefix = Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/28"))
272
- IPAddress.objects.bulk_create(
273
- (
274
- IPAddress(address=netaddr.IPNetwork("10.0.0.1/26")),
275
- IPAddress(address=netaddr.IPNetwork("10.0.0.3/26")),
276
- IPAddress(address=netaddr.IPNetwork("10.0.0.5/26")),
277
- IPAddress(address=netaddr.IPNetwork("10.0.0.7/26")),
278
- IPAddress(address=netaddr.IPNetwork("10.0.0.9/26")),
279
- IPAddress(address=netaddr.IPNetwork("10.0.0.11/26")),
280
- IPAddress(address=netaddr.IPNetwork("10.0.0.13/26")),
281
- )
282
- )
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
283
480
  missing_ips = netaddr.IPSet(
284
481
  [
285
482
  "10.0.0.2/32",
@@ -292,257 +489,211 @@ class TestPrefix(TestCase): # TODO change to BaseModelTestCase
292
489
  ]
293
490
  )
294
491
  available_ips = parent_prefix.get_available_ips()
295
-
296
492
  self.assertEqual(available_ips, missing_ips)
297
493
 
298
494
  def test_get_first_available_prefix(self):
299
-
300
- prefixes = Prefix.objects.bulk_create(
301
- (
302
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/16")), # Parent prefix
303
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/24")),
304
- Prefix(prefix=netaddr.IPNetwork("10.0.1.0/24")),
305
- Prefix(prefix=netaddr.IPNetwork("10.0.2.0/24")),
306
- )
307
- )
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
308
502
  self.assertEqual(prefixes[0].get_first_available_prefix(), netaddr.IPNetwork("10.0.3.0/24"))
309
503
 
310
- 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)
311
505
  self.assertEqual(prefixes[0].get_first_available_prefix(), netaddr.IPNetwork("10.0.4.0/22"))
312
506
 
313
507
  def test_get_first_available_ip(self):
314
-
315
- parent_prefix = Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/24"))
316
- IPAddress.objects.bulk_create(
317
- (
318
- IPAddress(address=netaddr.IPNetwork("10.0.0.1/24")),
319
- IPAddress(address=netaddr.IPNetwork("10.0.0.2/24")),
320
- IPAddress(address=netaddr.IPNetwork("10.0.0.3/24")),
321
- )
322
- )
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
323
515
  self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.0.4/24")
324
516
 
325
- IPAddress.objects.create(address=netaddr.IPNetwork("10.0.0.4/24"))
517
+ IPAddress.objects.create(address="10.0.0.4/24", status=self.status, namespace=self.namespace)
326
518
  self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.0.5/24")
327
519
 
328
520
  def test_get_utilization(self):
329
-
330
521
  # Container Prefix
331
- prefix = Prefix.objects.create(prefix=netaddr.IPNetwork("10.0.0.0/24"), type=PrefixTypeChoices.TYPE_CONTAINER)
332
- Prefix.objects.bulk_create(
333
- (
334
- Prefix(prefix=netaddr.IPNetwork("10.0.0.0/26")),
335
- Prefix(prefix=netaddr.IPNetwork("10.0.0.128/26")),
336
- )
337
- )
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
338
530
  self.assertEqual(prefix.get_utilization(), (128, 256))
339
531
 
340
532
  # IPv4 Non-container Prefix /24
341
533
  prefix.type = PrefixTypeChoices.TYPE_NETWORK
342
- prefix.save()
343
- IPAddress.objects.bulk_create(
344
- # Create 32 IPAddresses within the Prefix
345
- [IPAddress(address=netaddr.IPNetwork(f"10.0.0.{i}/24")) for i in range(1, 33)]
346
- )
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
+ ]
347
538
  # Create IPAddress objects for network and broadcast addresses
348
- IPAddress.objects.bulk_create(
349
- (IPAddress(address=netaddr.IPNetwork("10.0.0.0/32")), IPAddress(address=netaddr.IPNetwork("10.0.0.255/32")))
350
- )
351
- 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))
352
551
 
353
552
  # Change prefix to a pool, network and broadcast address will count toward numerator and denominator in utilization
354
553
  prefix.type = PrefixTypeChoices.TYPE_POOL
355
- prefix.save()
356
- 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))
357
559
 
358
560
  # IPv4 Non-container Prefix /31, network and broadcast addresses count toward utilization
359
- prefix = Prefix.objects.create(prefix="10.0.1.0/31")
360
- IPAddress.objects.bulk_create(
361
- (IPAddress(address=netaddr.IPNetwork("10.0.1.0/32")), IPAddress(address=netaddr.IPNetwork("10.0.1.1/32")))
362
- )
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
363
567
  self.assertEqual(prefix.get_utilization(), (2, 2))
364
568
 
365
569
  # IPv6 Non-container Prefix, network and broadcast addresses count toward utilization
366
- prefix = Prefix.objects.create(prefix="aaaa::/124")
367
- IPAddress.objects.bulk_create(
368
- (IPAddress(address=netaddr.IPNetwork("aaaa::0/128")), IPAddress(address=netaddr.IPNetwork("aaaa::f/128")))
369
- )
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
370
576
  self.assertEqual(prefix.get_utilization(), (2, 16))
371
577
 
372
578
  # Large Prefix
373
- 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
+ )
374
582
 
375
583
  # 25% utilization
376
- Prefix.objects.bulk_create(
377
- (
378
- Prefix(prefix=netaddr.IPNetwork("22.0.0.0/12")),
379
- Prefix(prefix=netaddr.IPNetwork("22.16.0.0/12")),
380
- Prefix(prefix=netaddr.IPNetwork("22.32.0.0/12")),
381
- Prefix(prefix=netaddr.IPNetwork("22.48.0.0/12")),
382
- )
383
- )
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
384
591
  self.assertEqual(large_prefix.get_utilization(), (4194304, 16777216))
385
592
 
386
593
  # 50% utilization
387
- 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)
388
595
  self.assertEqual(large_prefix.get_utilization(), (8388608, 16777216))
389
596
 
390
597
  # 100% utilization
391
- 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)
392
599
  self.assertEqual(large_prefix.get_utilization(), (16777216, 16777216))
393
600
 
394
601
  # IPv6 Large Prefix
395
- 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
+ )
396
605
 
397
606
  # 25% utilization
398
- Prefix.objects.bulk_create(
399
- (
400
- Prefix(prefix=netaddr.IPNetwork("ab00::/12")),
401
- Prefix(prefix=netaddr.IPNetwork("ab10::/12")),
402
- Prefix(prefix=netaddr.IPNetwork("ab20::/12")),
403
- Prefix(prefix=netaddr.IPNetwork("ab30::/12")),
404
- )
405
- )
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
406
614
  self.assertEqual(large_prefix_v6.get_utilization(), (2**118, 2**120))
407
615
 
408
616
  # 50% utilization
409
- Prefix.objects.bulk_create((Prefix(prefix=netaddr.IPNetwork("ab40::/10")),))
617
+ Prefix.objects.create(prefix="ab40::/10", status=self.status, namespace=self.namespace)
410
618
  self.assertEqual(large_prefix_v6.get_utilization(), (2**119, 2**120))
411
619
 
412
620
  # 100% utilization
413
- Prefix.objects.bulk_create((Prefix(prefix=netaddr.IPNetwork("ab80::/9")),))
621
+ Prefix.objects.create(prefix="ab80::/9", status=self.status, namespace=self.namespace)
414
622
  self.assertEqual(large_prefix_v6.get_utilization(), (2**120, 2**120))
415
623
 
416
624
  #
417
625
  # Uniqueness enforcement tests
418
626
  #
419
627
 
420
- @override_settings(ENFORCE_GLOBAL_UNIQUE=False)
421
- def test_duplicate_global(self):
422
- Prefix.objects.create(prefix=netaddr.IPNetwork("192.0.2.0/24"))
423
- duplicate_prefix = Prefix(prefix=netaddr.IPNetwork("192.0.2.0/24"))
424
- self.assertIsNone(duplicate_prefix.clean())
425
-
426
- @override_settings(ENFORCE_GLOBAL_UNIQUE=True)
427
628
  def test_duplicate_global_unique(self):
428
- Prefix.objects.create(prefix=netaddr.IPNetwork("192.0.2.0/24"))
429
- duplicate_prefix = Prefix(prefix=netaddr.IPNetwork("192.0.2.0/24"))
430
- self.assertRaises(ValidationError, duplicate_prefix.clean)
431
-
432
- def test_duplicate_vrf(self):
433
- vrf = VRF.objects.filter(enforce_unique=False).first()
434
- Prefix.objects.create(vrf=vrf, prefix=netaddr.IPNetwork("192.0.2.0/24"))
435
- duplicate_prefix = Prefix(vrf=vrf, prefix=netaddr.IPNetwork("192.0.2.0/24"))
436
- self.assertIsNone(duplicate_prefix.clean())
437
-
438
- def test_duplicate_vrf_unique(self):
439
- vrf = VRF.objects.filter(enforce_unique=True).first()
440
- Prefix.objects.create(vrf=vrf, prefix=netaddr.IPNetwork("192.0.2.0/24"))
441
- duplicate_prefix = Prefix(vrf=vrf, prefix=netaddr.IPNetwork("192.0.2.0/24"))
442
- self.assertRaises(ValidationError, duplicate_prefix.clean)
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)
633
+ self.assertRaises(ValidationError, duplicate_prefix.full_clean)
443
634
 
444
635
 
445
636
  class TestIPAddress(ModelTestCases.BaseModelTestCase):
446
637
  model = IPAddress
447
638
 
448
- def test_get_duplicates(self):
449
- ips = (
450
- IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24")),
451
- IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24")),
452
- IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24")),
453
- )
454
- duplicate_ip_pks = [p.pk for p in ips[0].get_duplicates()]
455
-
456
- 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)
457
644
 
458
645
  #
459
646
  # Uniqueness enforcement tests
460
647
  #
461
648
 
462
- @override_settings(ENFORCE_GLOBAL_UNIQUE=False)
463
- def test_duplicate_global(self):
464
- IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24"))
465
- duplicate_ip = IPAddress(address=netaddr.IPNetwork("192.0.2.1/24"))
466
- self.assertIsNone(duplicate_ip.clean())
467
-
468
- @override_settings(ENFORCE_GLOBAL_UNIQUE=True)
469
649
  def test_duplicate_global_unique(self):
470
- IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24"))
471
- duplicate_ip = IPAddress(address=netaddr.IPNetwork("192.0.2.1/24"))
472
- self.assertRaises(ValidationError, duplicate_ip.clean)
473
-
474
- def test_duplicate_vrf(self):
475
- vrf = VRF.objects.filter(enforce_unique=False).first()
476
- IPAddress.objects.create(vrf=vrf, address=netaddr.IPNetwork("192.0.2.1/24"))
477
- duplicate_ip = IPAddress(vrf=vrf, address=netaddr.IPNetwork("192.0.2.1/24"))
478
- self.assertIsNone(duplicate_ip.clean())
479
-
480
- def test_duplicate_vrf_unique(self):
481
- vrf = VRF.objects.filter(enforce_unique=True).first()
482
- IPAddress.objects.create(vrf=vrf, address=netaddr.IPNetwork("192.0.2.1/24"))
483
- duplicate_ip = IPAddress(vrf=vrf, address=netaddr.IPNetwork("192.0.2.1/24"))
484
- self.assertRaises(ValidationError, duplicate_ip.clean)
485
-
486
- @override_settings(ENFORCE_GLOBAL_UNIQUE=True)
487
- def test_duplicate_nonunique_role(self):
488
- roles = Role.objects.get_for_model(IPAddress)
489
- IPAddress.objects.create(
490
- address=netaddr.IPNetwork("192.0.2.1/24"),
491
- role=roles[0],
492
- )
493
- IPAddress.objects.create(
494
- address=netaddr.IPNetwork("192.0.2.1/24"),
495
- role=roles[1],
496
- )
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)
497
654
 
498
- def test_multiple_nat_outside(self):
655
+ def test_multiple_nat_outside_list(self):
499
656
  """
500
- Test suite to test supporing multiple nat_inside related fields.
501
-
502
- Includes tests for legacy getter/setter for nat_outside.
657
+ Test suite to test supporing nat_outside_list.
503
658
  """
504
-
505
- # Setup mimicked legacy data model relationships: 1-to-1
506
- nat_inside = IPAddress.objects.create(address=netaddr.IPNetwork("192.168.0.1/24"))
507
- nat_outside1 = IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.1/24"), nat_inside=nat_inside)
508
- nat_inside.refresh_from_db()
509
-
510
- # Assert legacy getter behaves as expected for backwards compatibility, and that FK relationship works
511
- self.assertEqual(nat_inside.nat_outside, nat_outside1)
512
- self.assertEqual(nat_inside.nat_outside_list.count(), 1)
513
- self.assertEqual(nat_inside.nat_outside_list.first(), nat_outside1)
514
-
515
- # Create unassigned IPAddress
516
- nat_outside2 = IPAddress.objects.create(address=netaddr.IPNetwork("192.0.2.2/24"))
517
- nat_inside.nat_outside = nat_outside2
518
- nat_inside.refresh_from_db()
519
-
520
- # Test legacy setter behaves as expected for backwards compatibility and that previous FK relationship removed
521
- self.assertEqual(nat_inside.nat_outside, nat_outside2)
522
- self.assertEqual(nat_inside.nat_outside_list.count(), 1)
523
- self.assertEqual(nat_inside.nat_outside_list.first(), nat_outside2)
524
-
525
- # Create IPAddress with nat_inside assigned, setting up current 1-to-many relationship
526
- 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
+ )
527
670
  nat_inside.refresh_from_db()
528
-
529
- # Now ensure safeguards are in place when using legacy methods with 1-to-many relationships
530
- with self.assertRaises(IPAddress.NATOutsideMultipleObjectsReturned):
531
- nat_inside.nat_outside
532
- with self.assertRaises(IPAddress.NATOutsideMultipleObjectsReturned):
533
- nat_inside.nat_outside = nat_outside1
534
-
535
- # Assert FK relationship behaves as expected
536
- self.assertEqual(nat_inside.nat_outside_list.count(), 2)
537
- self.assertEqual(nat_inside.nat_outside_list.first(), nat_outside2)
538
- self.assertEqual(nat_inside.nat_outside_list.last(), nat_outside3)
671
+ self.assertEqual(nat_inside.nat_outside_list.count(), 3)
672
+ self.assertEqual(nat_inside.nat_outside_list.all()[0], nat_outside1)
673
+ self.assertEqual(nat_inside.nat_outside_list.all()[1], nat_outside2)
674
+ self.assertEqual(nat_inside.nat_outside_list.all()[2], nat_outside3)
539
675
 
540
676
  def test_create_ip_address_without_slaac_status(self):
541
- IPAddress.objects.filter(status__slug=IPAddressStatusChoices.STATUS_SLAAC).delete()
542
- Status.objects.get(slug=IPAddressStatusChoices.STATUS_SLAAC).delete()
543
- IPAddress.objects.create(address="1.1.1.1/32")
677
+ slaac_status_name = IPAddressStatusChoices.as_dict()[IPAddressStatusChoices.STATUS_SLAAC]
678
+ IPAddress.objects.filter(status__name=slaac_status_name).delete()
679
+ Status.objects.get(name=slaac_status_name).delete()
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)
544
682
  self.assertTrue(IPAddress.objects.filter(address="1.1.1.1/32").exists())
545
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
+
546
697
 
547
698
  class TestVLANGroup(ModelTestCases.BaseModelTestCase):
548
699
  model = VLANGroup
@@ -556,19 +707,19 @@ class TestVLANGroup(ModelTestCases.BaseModelTestCase):
556
707
  self.assertIn(f'VLAN groups may not associate to locations of type "{location_type.name}"', str(cm.exception))
557
708
 
558
709
  def test_get_next_available_vid(self):
559
-
560
710
  vlangroup = VLANGroup.objects.create(name="VLAN Group 1", slug="vlan-group-1")
711
+ status = Status.objects.get_for_model(VLAN).first()
561
712
  VLAN.objects.bulk_create(
562
713
  (
563
- VLAN(name="VLAN 1", vid=1, vlan_group=vlangroup),
564
- VLAN(name="VLAN 2", vid=2, vlan_group=vlangroup),
565
- VLAN(name="VLAN 3", vid=3, vlan_group=vlangroup),
566
- 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),
567
718
  )
568
719
  )
569
720
  self.assertEqual(vlangroup.get_next_available_vid(), 4)
570
721
 
571
- 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),))
572
723
  self.assertEqual(vlangroup.get_next_available_vid(), 6)
573
724
 
574
725
 
@@ -581,7 +732,7 @@ class VLANTestCase(ModelTestCases.BaseModelTestCase):
581
732
  location_type.validated_save()
582
733
  location = Location.objects.filter(location_type=location_type).first()
583
734
  vlan = VLAN(name="Group 1", vid=1, location=location)
584
- vlan.status = Status.objects.get_for_model(VLAN).get(slug="active")
735
+ vlan.status = Status.objects.get_for_model(VLAN).first()
585
736
  with self.assertRaises(ValidationError) as cm:
586
737
  vlan.validated_save()
587
738
  self.assertIn(f'VLANs may not associate to locations of type "{location_type.name}"', str(cm.exception))
@@ -589,7 +740,8 @@ class VLANTestCase(ModelTestCases.BaseModelTestCase):
589
740
  location_type.content_types.add(ContentType.objects.get_for_model(VLAN))
590
741
  group = VLANGroup.objects.create(name="Group 1")
591
742
  vlan.vlan_group = group
592
- 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)
593
745
  group.location = location_2
594
746
  group.save()
595
747
  with self.assertRaises(ValidationError) as cm: