nautobot 3.0.0a2__py3-none-any.whl → 3.0.0a3__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.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (420) hide show
  1. nautobot/apps/choices.py +0 -2
  2. nautobot/apps/filters.py +7 -9
  3. nautobot/apps/models.py +2 -2
  4. nautobot/apps/ui.py +9 -1
  5. nautobot/circuits/filters.py +3 -2
  6. nautobot/circuits/navigation.py +3 -2
  7. nautobot/circuits/templates/circuits/circuit.html +1 -1
  8. nautobot/circuits/templates/circuits/circuit_create.html +3 -3
  9. nautobot/circuits/templates/circuits/circuittermination.html +1 -1
  10. nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
  11. nautobot/circuits/templates/circuits/circuittype.html +1 -1
  12. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
  13. nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
  14. nautobot/circuits/templates/circuits/providernetwork.html +1 -1
  15. nautobot/circuits/tests/integration/test_circuit.py +10 -13
  16. nautobot/cloud/filters.py +1 -1
  17. nautobot/cloud/navigation.py +3 -2
  18. nautobot/core/api/schema.py +1 -1
  19. nautobot/core/api/serializers.py +6 -1
  20. nautobot/core/api/urls.py +1 -0
  21. nautobot/core/api/views.py +8 -0
  22. nautobot/core/apps/__init__.py +11 -10
  23. nautobot/core/celery/__init__.py +3 -5
  24. nautobot/core/checks.py +46 -0
  25. nautobot/core/cli/bootstrap_v3_to_v5.py +70 -1
  26. nautobot/core/cli/migrate_deprecated_templates.py +200 -0
  27. nautobot/core/constants.py +3 -0
  28. nautobot/core/context_processors.py +9 -1
  29. nautobot/core/forms/forms.py +1 -1
  30. nautobot/core/jobs/__init__.py +6 -3
  31. nautobot/core/jobs/groups.py +31 -1
  32. nautobot/core/management/commands/generate_test_data.py +28 -9
  33. nautobot/core/models/generics.py +9 -1
  34. nautobot/core/models/tree_queries.py +10 -5
  35. nautobot/core/settings.py +18 -12
  36. nautobot/core/settings.yaml +13 -7
  37. nautobot/core/signals.py +12 -1
  38. nautobot/core/tables.py +13 -6
  39. nautobot/core/templates/40x.html +1 -1
  40. nautobot/core/templates/500.html +2 -2
  41. nautobot/core/templates/admin/config/config.html +12 -12
  42. nautobot/core/templates/admin/index.html +3 -3
  43. nautobot/core/templates/buttons/export.html +1 -1
  44. nautobot/core/templates/components/button/dropdown.html +5 -3
  45. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  46. nautobot/core/templates/components/panel/panel.html +3 -3
  47. nautobot/core/templates/components/tab/content_wrapper.html +2 -3
  48. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
  49. nautobot/core/templates/echarts/echarts.html +1 -1
  50. nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
  51. nautobot/core/templates/generic/object_bulk_create.html +4 -3
  52. nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
  53. nautobot/core/templates/generic/object_bulk_remove.html +2 -2
  54. nautobot/core/templates/generic/object_bulk_update.html +5 -4
  55. nautobot/core/templates/generic/object_create.html +5 -4
  56. nautobot/core/templates/generic/object_import.html +2 -1
  57. nautobot/core/templates/generic/object_list.html +12 -4
  58. nautobot/core/templates/generic/object_notes.html +5 -3
  59. nautobot/core/templates/generic/object_retrieve.html +2 -3
  60. nautobot/core/templates/graphene/graphiql.html +7 -7
  61. nautobot/core/templates/home.html +1 -1
  62. nautobot/core/templates/import_success.html +2 -1
  63. nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
  64. nautobot/core/templates/inc/created_updated.html +7 -3
  65. nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
  66. nautobot/core/templates/inc/form_static_field.html +6 -0
  67. nautobot/core/templates/inc/header.html +1 -1
  68. nautobot/core/templates/inc/image_attachments.html +2 -1
  69. nautobot/core/templates/inc/nav_menu.html +2 -1
  70. nautobot/core/templates/inc/search_panel.html +4 -4
  71. nautobot/core/templates/login.html +4 -2
  72. nautobot/core/templates/nautobot_config.py.j2 +6 -5
  73. nautobot/core/templates/redoc_ui.html +7 -0
  74. nautobot/core/templates/search.html +1 -1
  75. nautobot/core/templates/swagger_ui.html +17 -3
  76. nautobot/core/templates/system_jobs/import_objects.html +1 -2
  77. nautobot/core/templates/utilities/confirmation_form.html +2 -2
  78. nautobot/core/templates/utilities/obj_table.html +10 -2
  79. nautobot/core/templates/utilities/render_field.html +7 -7
  80. nautobot/core/templates/utilities/render_jinja2.html +2 -2
  81. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +4 -4
  82. nautobot/core/templates/utilities/theme_preview.html +16 -3
  83. nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
  84. nautobot/core/templatetags/helpers.py +52 -6
  85. nautobot/core/testing/api.py +68 -9
  86. nautobot/core/testing/filters.py +0 -23
  87. nautobot/core/testing/integration.py +23 -10
  88. nautobot/core/testing/mixins.py +2 -0
  89. nautobot/core/testing/views.py +4 -0
  90. nautobot/core/tests/integration/test_app_home.py +34 -30
  91. nautobot/core/tests/integration/test_app_navbar.py +3 -0
  92. nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
  93. nautobot/core/tests/runner.py +9 -1
  94. nautobot/core/tests/test_api.py +5 -3
  95. nautobot/core/tests/test_breadcrumbs.py +6 -7
  96. nautobot/core/tests/test_checks.py +28 -0
  97. nautobot/core/tests/test_cli.py +40 -0
  98. nautobot/core/tests/test_config.py +2 -1
  99. nautobot/core/tests/test_forms.py +55 -13
  100. nautobot/core/tests/test_jobs.py +75 -1
  101. nautobot/core/tests/test_nautobot_server.py +2 -0
  102. nautobot/core/tests/test_navigations.py +76 -1
  103. nautobot/core/tests/test_patch_social_django.py +42 -0
  104. nautobot/core/tests/test_tables.py +3 -1
  105. nautobot/core/tests/test_templatetags_helpers.py +53 -13
  106. nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
  107. nautobot/core/tests/test_tree_queries.py +14 -1
  108. nautobot/core/tests/test_ui.py +1 -1
  109. nautobot/core/tests/test_utils.py +31 -4
  110. nautobot/core/tests/test_views.py +159 -31
  111. nautobot/core/ui/breadcrumbs.py +2 -12
  112. nautobot/core/ui/choices.py +142 -10
  113. nautobot/core/ui/constants.py +76 -12
  114. nautobot/core/ui/object_detail.py +92 -12
  115. nautobot/core/urls.py +12 -1
  116. nautobot/core/utils/cache.py +2 -1
  117. nautobot/core/utils/filtering.py +17 -17
  118. nautobot/core/utils/lookup.py +3 -8
  119. nautobot/core/utils/module_loading.py +21 -0
  120. nautobot/core/utils/patch_social_django.py +128 -0
  121. nautobot/core/views/__init__.py +38 -1
  122. nautobot/core/views/generic.py +3 -3
  123. nautobot/core/views/mixins.py +15 -3
  124. nautobot/core/views/renderers.py +2 -0
  125. nautobot/core/views/viewsets.py +2 -1
  126. nautobot/data_validation/apps.py +1 -5
  127. nautobot/data_validation/custom_validators.py +4 -4
  128. nautobot/data_validation/filters.py +1 -1
  129. nautobot/data_validation/forms.py +40 -0
  130. nautobot/data_validation/migrations/0001_initial.py +0 -7
  131. nautobot/data_validation/migrations/0002_data_migration_from_app.py +0 -12
  132. nautobot/data_validation/models.py +16 -7
  133. nautobot/data_validation/navigation.py +8 -1
  134. nautobot/data_validation/tables.py +12 -5
  135. nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
  136. nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
  137. nautobot/data_validation/tests/__init__.py +2 -2
  138. nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
  139. nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
  140. nautobot/data_validation/tests/test_filters.py +8 -6
  141. nautobot/data_validation/tests/test_models.py +15 -0
  142. nautobot/data_validation/tests/test_views.py +190 -32
  143. nautobot/data_validation/urls.py +2 -5
  144. nautobot/data_validation/views.py +73 -40
  145. nautobot/dcim/api/serializers.py +0 -13
  146. nautobot/dcim/apps.py +4 -0
  147. nautobot/dcim/choices.py +16 -0
  148. nautobot/dcim/custom_validators.py +84 -0
  149. nautobot/dcim/filter_mixins.py +353 -4
  150. nautobot/dcim/{filters/__init__.py → filters.py} +2 -35
  151. nautobot/dcim/forms.py +1 -1
  152. nautobot/dcim/migrations/0078_remove_device_location_tenant_name_uniqueness.py +16 -0
  153. nautobot/dcim/migrations/0079_device_name_data_migration.py +59 -0
  154. nautobot/dcim/models/device_components.py +81 -68
  155. nautobot/dcim/models/devices.py +13 -16
  156. nautobot/dcim/navigation.py +7 -6
  157. nautobot/dcim/tables/devices.py +3 -0
  158. nautobot/dcim/tables/template_code.py +14 -14
  159. nautobot/dcim/templates/dcim/cable.html +2 -61
  160. nautobot/dcim/templates/dcim/cable_connect.html +28 -112
  161. nautobot/dcim/templates/dcim/cable_edit.html +2 -5
  162. nautobot/dcim/templates/dcim/cable_retrieve.html +61 -0
  163. nautobot/dcim/templates/dcim/cable_trace.html +1 -3
  164. nautobot/dcim/templates/dcim/cable_update.html +5 -0
  165. nautobot/dcim/templates/dcim/consoleport.html +6 -5
  166. nautobot/dcim/templates/dcim/consoleserverport.html +6 -5
  167. nautobot/dcim/templates/dcim/device/config.html +2 -2
  168. nautobot/dcim/templates/dcim/device/consoleports.html +1 -1
  169. nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -1
  170. nautobot/dcim/templates/dcim/device/devicebays.html +1 -1
  171. nautobot/dcim/templates/dcim/device/frontports.html +1 -1
  172. nautobot/dcim/templates/dcim/device/interfaces.html +1 -1
  173. nautobot/dcim/templates/dcim/device/inventory.html +1 -1
  174. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
  175. nautobot/dcim/templates/dcim/device/modulebays.html +1 -1
  176. nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -1
  177. nautobot/dcim/templates/dcim/device/powerports.html +1 -1
  178. nautobot/dcim/templates/dcim/device/rearports.html +1 -1
  179. nautobot/dcim/templates/dcim/device/status.html +8 -8
  180. nautobot/dcim/templates/dcim/device/wireless.html +1 -1
  181. nautobot/dcim/templates/dcim/device.html +1 -1
  182. nautobot/dcim/templates/dcim/device_component_add.html +2 -2
  183. nautobot/dcim/templates/dcim/device_create.html +5 -3
  184. nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
  185. nautobot/dcim/templates/dcim/device_list.html +73 -10
  186. nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
  187. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  188. nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
  189. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
  190. nautobot/dcim/templates/dcim/frontport.html +9 -8
  191. nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
  192. nautobot/dcim/templates/dcim/interface.html +26 -6
  193. nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
  194. nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
  195. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  196. nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
  197. nautobot/dcim/templates/dcim/location_retrieve.html +1 -242
  198. nautobot/dcim/templates/dcim/module/base.html +49 -9
  199. nautobot/dcim/templates/dcim/module_list.html +57 -8
  200. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  201. nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
  202. nautobot/dcim/templates/dcim/platform_create.html +1 -1
  203. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  204. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  205. nautobot/dcim/templates/dcim/powerport.html +5 -4
  206. nautobot/dcim/templates/dcim/rack_elevation_list.html +16 -4
  207. nautobot/dcim/templates/dcim/rack_retrieve.html +33 -15
  208. nautobot/dcim/templates/dcim/rearport.html +7 -6
  209. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  210. nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
  211. nautobot/dcim/templates/dcim/virtualchassis_update.html +14 -6
  212. nautobot/dcim/tests/integration/test_controller.py +1 -0
  213. nautobot/dcim/tests/test_api.py +8 -0
  214. nautobot/dcim/tests/test_custom_validators.py +229 -0
  215. nautobot/dcim/tests/test_filters.py +12 -6
  216. nautobot/dcim/tests/test_models.py +63 -4
  217. nautobot/dcim/tests/test_views.py +63 -22
  218. nautobot/dcim/urls.py +64 -21
  219. nautobot/dcim/utils.py +3 -3
  220. nautobot/dcim/views.py +547 -273
  221. nautobot/extras/api/views.py +9 -1
  222. nautobot/extras/choices.py +2 -13
  223. nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
  224. nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
  225. nautobot/extras/{filters/__init__.py → filters.py} +14 -46
  226. nautobot/extras/forms/forms.py +5 -13
  227. nautobot/extras/forms/mixins.py +0 -41
  228. nautobot/extras/management/__init__.py +9 -0
  229. nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
  230. nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
  231. nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
  232. nautobot/extras/models/__init__.py +1 -2
  233. nautobot/extras/models/approvals.py +22 -13
  234. nautobot/extras/models/contacts.py +2 -0
  235. nautobot/extras/models/groups.py +44 -5
  236. nautobot/extras/models/jobs.py +59 -1
  237. nautobot/extras/models/mixins.py +28 -0
  238. nautobot/extras/models/models.py +13 -0
  239. nautobot/extras/models/secrets.py +1 -0
  240. nautobot/extras/models/statuses.py +0 -15
  241. nautobot/extras/navigation.py +13 -9
  242. nautobot/extras/plugins/__init__.py +33 -55
  243. nautobot/extras/plugins/tables.py +3 -3
  244. nautobot/extras/plugins/urls.py +2 -21
  245. nautobot/extras/plugins/utils.py +1 -33
  246. nautobot/extras/plugins/views.py +0 -4
  247. nautobot/extras/signals.py +20 -19
  248. nautobot/extras/tables.py +52 -68
  249. nautobot/extras/templates/extras/approval_dashboard.html +7 -5
  250. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
  251. nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
  252. nautobot/extras/templates/extras/computedfield.html +1 -1
  253. nautobot/extras/templates/extras/configcontext.html +1 -1
  254. nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
  255. nautobot/extras/templates/extras/customfield.html +1 -1
  256. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
  257. nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
  258. nautobot/extras/templates/extras/gitrepository_result.html +0 -2
  259. nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -96
  260. nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
  261. nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
  262. nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
  263. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
  264. nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
  265. nautobot/extras/templates/extras/inc/json_format.html +10 -3
  266. nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
  267. nautobot/extras/templates/extras/job.html +29 -12
  268. nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
  269. nautobot/extras/templates/extras/job_edit.html +52 -46
  270. nautobot/extras/templates/extras/job_list.html +29 -25
  271. nautobot/extras/templates/extras/marketplace.html +5 -9
  272. nautobot/extras/templates/extras/object_configcontext.html +1 -1
  273. nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
  274. nautobot/extras/templates/extras/objectchange_retrieve.html +19 -37
  275. nautobot/extras/templates/extras/plugin_detail.html +26 -21
  276. nautobot/extras/templates/extras/plugins_list.html +16 -26
  277. nautobot/extras/templates/extras/role_retrieve.html +64 -0
  278. nautobot/extras/templates/extras/scheduledjob.html +4 -2
  279. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  280. nautobot/extras/templates/extras/tag.html +1 -1
  281. nautobot/extras/templatetags/custom_links.py +12 -12
  282. nautobot/extras/templatetags/job_buttons.py +14 -12
  283. nautobot/extras/test_jobs/invalid_import.py +9 -0
  284. nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
  285. nautobot/extras/test_jobs/missing_import.py +11 -0
  286. nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
  287. nautobot/extras/tests/integration/test_customfields.py +8 -7
  288. nautobot/extras/tests/integration/test_dynamicgroups.py +5 -1
  289. nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
  290. nautobot/extras/tests/integration/test_plugins.py +18 -6
  291. nautobot/extras/tests/test_api.py +27 -18
  292. nautobot/extras/tests/test_approvals.py +38 -38
  293. nautobot/extras/tests/test_changelog.py +35 -3
  294. nautobot/extras/tests/test_customfields.py +22 -13
  295. nautobot/extras/tests/test_customfields_filters.py +479 -0
  296. nautobot/extras/tests/test_dynamicgroups.py +39 -1
  297. nautobot/extras/tests/test_filters.py +21 -19
  298. nautobot/extras/tests/test_forms.py +18 -21
  299. nautobot/extras/tests/test_jobs.py +25 -4
  300. nautobot/extras/tests/test_migrations.py +1 -0
  301. nautobot/extras/tests/test_models.py +13 -31
  302. nautobot/extras/tests/test_plugins.py +36 -10
  303. nautobot/extras/tests/test_views.py +31 -30
  304. nautobot/extras/views.py +81 -19
  305. nautobot/ipam/factory.py +7 -0
  306. nautobot/ipam/filter_mixins.py +38 -0
  307. nautobot/ipam/filters.py +27 -38
  308. nautobot/ipam/formfields.py +1 -1
  309. nautobot/ipam/forms.py +6 -3
  310. nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
  311. nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
  312. nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
  313. nautobot/ipam/models.py +29 -2
  314. nautobot/ipam/navigation.py +3 -2
  315. nautobot/ipam/signals.py +71 -0
  316. nautobot/ipam/tables.py +13 -6
  317. nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
  318. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
  319. nautobot/ipam/templates/ipam/ipaddress.html +14 -0
  320. nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
  321. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
  322. nautobot/ipam/templates/ipam/namespace_update.html +15 -0
  323. nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
  324. nautobot/ipam/templates/ipam/prefix_list.html +14 -13
  325. nautobot/ipam/templates/ipam/service.html +1 -1
  326. nautobot/ipam/templates/ipam/vlan.html +1 -1
  327. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  328. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  329. nautobot/ipam/tests/migration/test_migrations.py +89 -0
  330. nautobot/ipam/tests/test_api.py +13 -6
  331. nautobot/ipam/tests/test_filters.py +10 -0
  332. nautobot/ipam/tests/test_forms.py +1 -1
  333. nautobot/ipam/tests/test_models.py +43 -1
  334. nautobot/ipam/tests/test_tables.py +1 -2
  335. nautobot/ipam/tests/test_utils.py +1 -1
  336. nautobot/ipam/tests/test_views.py +13 -14
  337. nautobot/ipam/ui.py +0 -17
  338. nautobot/ipam/utils/migrations.py +16 -2
  339. nautobot/ipam/utils/testing.py +9 -3
  340. nautobot/ipam/views.py +46 -6
  341. nautobot/project-static/dist/css/nautobot.css +1 -1
  342. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  343. nautobot/project-static/dist/js/nautobot.js +1 -1
  344. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  345. nautobot/project-static/js/cabletrace.js +1 -1
  346. nautobot/project-static/js/interface_filtering.js +20 -16
  347. nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
  348. nautobot/project-static/nautobot-icons/cloud.svg +1 -1
  349. nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
  350. nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
  351. nautobot/project-static/nautobot-icons/elements.svg +1 -1
  352. nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
  353. nautobot/project-static/nautobot-icons/hammer.svg +1 -1
  354. nautobot/project-static/nautobot-icons/organization.svg +3 -0
  355. nautobot/project-static/nautobot-icons/secrets.svg +1 -1
  356. nautobot/project-static/nautobot-icons/security.svg +3 -0
  357. nautobot/project-static/nautobot-icons/server.svg +1 -1
  358. nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
  359. nautobot/project-static/nautobot-icons/star.svg +1 -1
  360. nautobot/tenancy/api/serializers.py +1 -0
  361. nautobot/tenancy/api/views.py +2 -1
  362. nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
  363. nautobot/tenancy/navigation.py +3 -1
  364. nautobot/tenancy/tests/test_filters.py +0 -2
  365. nautobot/tenancy/views.py +2 -1
  366. nautobot/ui/src/js/collapse.js +3 -3
  367. nautobot/ui/src/js/nautobot.js +16 -0
  368. nautobot/ui/src/scss/colors.scss +1 -1
  369. nautobot/ui/src/scss/nautobot.scss +61 -28
  370. nautobot/users/templates/users/profile.html +45 -12
  371. nautobot/users/templates/users/sessionkey_delete.html +1 -1
  372. nautobot/users/tests/test_api.py +4 -0
  373. nautobot/users/views.py +4 -2
  374. nautobot/virtualization/models.py +1 -68
  375. nautobot/virtualization/navigation.py +3 -2
  376. nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
  377. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  378. nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
  379. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
  380. nautobot/virtualization/tests/test_api.py +3 -0
  381. nautobot/virtualization/tests/test_models.py +44 -4
  382. nautobot/vpn/__init__.py +0 -0
  383. nautobot/vpn/api/serializers.py +113 -0
  384. nautobot/vpn/api/urls.py +19 -0
  385. nautobot/vpn/api/views.py +70 -0
  386. nautobot/vpn/apps.py +8 -0
  387. nautobot/vpn/choices.py +171 -0
  388. nautobot/vpn/factory.py +209 -0
  389. nautobot/vpn/filters.py +233 -0
  390. nautobot/vpn/forms.py +486 -0
  391. nautobot/vpn/homepage.py +19 -0
  392. nautobot/vpn/migrations/0001_initial.py +541 -0
  393. nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
  394. nautobot/vpn/migrations/__init__.py +0 -0
  395. nautobot/vpn/models.py +527 -0
  396. nautobot/vpn/navigation.py +98 -0
  397. nautobot/vpn/tables.py +380 -0
  398. nautobot/vpn/templates/vpn/vpnprofile.html +2 -0
  399. nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
  400. nautobot/vpn/tests/__init__.py +0 -0
  401. nautobot/vpn/tests/test_api.py +341 -0
  402. nautobot/vpn/tests/test_filters.py +139 -0
  403. nautobot/vpn/tests/test_forms.py +294 -0
  404. nautobot/vpn/tests/test_models.py +97 -0
  405. nautobot/vpn/tests/test_views.py +281 -0
  406. nautobot/vpn/urls.py +16 -0
  407. nautobot/vpn/views.py +437 -0
  408. nautobot/wireless/navigation.py +3 -2
  409. nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
  410. nautobot/wireless/tests/test_api.py +1 -1
  411. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/METADATA +14 -14
  412. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +417 -366
  413. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
  414. nautobot/data_validation/template_content.py +0 -42
  415. nautobot/dcim/filters/mixins.py +0 -354
  416. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
  417. /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
  418. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
  419. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
  420. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
@@ -40,6 +40,7 @@ from nautobot.ipam.models import (
40
40
  VRFDeviceAssignment,
41
41
  VRFPrefixAssignment,
42
42
  )
43
+ from nautobot.tenancy.models import Tenant
43
44
  from nautobot.virtualization.models import Cluster, ClusterType, VirtualMachine, VMInterface
44
45
 
45
46
 
@@ -57,22 +58,28 @@ class NamespaceTest(APIViewTestCases.APIViewTestCase):
57
58
  @classmethod
58
59
  def setUpTestData(cls):
59
60
  location = Location.objects.first()
61
+ tenant = Tenant.objects.first()
60
62
  cls.create_data = [
61
63
  {
62
64
  "name": "Purple Monkey Namespace 1",
63
- "description": "A perfectly cromulent namespace.",
65
+ "description": "A namespace with a tenant and location.",
64
66
  "location": location.pk,
67
+ "tenant": tenant.pk,
65
68
  },
66
69
  {
67
70
  "name": "Purple Monkey Namespace 2",
68
- "description": "A secondarily cromulent namespace.",
69
- "location": location.pk,
71
+ "description": "A namespace with a tenant and no location.",
72
+ "tenant": tenant.pk,
70
73
  },
71
74
  {
72
75
  "name": "Purple Monkey Namespace 3",
73
- "description": "A third cromulent namespace.",
76
+ "description": "A namespace with no tenant but with a location.",
74
77
  "location": location.pk,
75
78
  },
79
+ {
80
+ "name": "Purple Monkey Namespace 4",
81
+ "description": "A namespace with no tenant and no location.",
82
+ },
76
83
  ]
77
84
  cls.bulk_update_data = {
78
85
  "description": "A perfectly new description.",
@@ -194,7 +201,7 @@ class VRFDeviceAssignmentTest(APIViewTestCases.APIViewTestCase):
194
201
  },
195
202
  {
196
203
  "vrf": cls.vrfs[4].pk,
197
- "virtual_device_context": cls.vdcs[0].pk,
204
+ "virtual_device_context": cls.vdcs[1].pk,
198
205
  },
199
206
  ]
200
207
  cls.bulk_update_data = {
@@ -1619,7 +1626,7 @@ class IPAddressTest(APIViewTestCases.APIViewTestCase):
1619
1626
  self.assertHttpStatus(ip2, status.HTTP_201_CREATED)
1620
1627
 
1621
1628
  response = self.client.get(
1622
- self._get_detail_url(nat_inside) + "?depth=1",
1629
+ self._get_detail_url(nat_inside) + "?depth=1&exclude_m2m=false",
1623
1630
  **self.header,
1624
1631
  )
1625
1632
  self.assertHttpStatus(response, status.HTTP_200_OK)
@@ -17,6 +17,7 @@ from nautobot.ipam.choices import PrefixTypeChoices, ServiceProtocolChoices
17
17
  from nautobot.ipam.filters import (
18
18
  IPAddressFilterSet,
19
19
  IPAddressToInterfaceFilterSet,
20
+ NamespaceFilterSet,
20
21
  PrefixFilterSet,
21
22
  PrefixLocationAssignmentFilterSet,
22
23
  RIRFilterSet,
@@ -54,6 +55,15 @@ from nautobot.virtualization.models import (
54
55
  )
55
56
 
56
57
 
58
+ class NamespaceTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilterTestCaseMixin):
59
+ """Namespace FilterSet tests"""
60
+
61
+ queryset = Namespace.objects.all()
62
+ filterset = NamespaceFilterSet
63
+ tenancy_related_name = "namespaces"
64
+ generic_filter_tests = (("name",),)
65
+
66
+
57
67
  class VRFTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilterTestCaseMixin):
58
68
  """VRF Filterset tests
59
69
 
@@ -1,8 +1,8 @@
1
1
  """Test IPAM forms."""
2
2
 
3
3
  from django.forms import Form
4
- from django.test import TestCase
5
4
 
5
+ from nautobot.core.testing import TestCase
6
6
  from nautobot.core.testing.forms import FormTestCases
7
7
  from nautobot.extras.models import Status
8
8
  from nautobot.ipam import forms, models
@@ -5,9 +5,9 @@ from django.contrib.contenttypes.models import ContentType
5
5
  from django.core.exceptions import ValidationError
6
6
  from django.db import connection, IntegrityError
7
7
  from django.db.models import ProtectedError
8
- from django.test import TestCase
9
8
  import netaddr
10
9
 
10
+ from nautobot.core.testing import TestCase
11
11
  from nautobot.core.testing.models import ModelTestCases
12
12
  from nautobot.dcim import choices as dcim_choices
13
13
  from nautobot.dcim.models import Device, DeviceType, Interface, Location, LocationType, Module, ModuleBay, ModuleType
@@ -172,6 +172,48 @@ class IPAddressToInterfaceTest(TestCase):
172
172
  IPAddressToInterface.objects.create(vm_interface=None, interface=None, ip_address=ip_addr)
173
173
  self.assertIn("Must associate to either an Interface or a VMInterface.", str(cm.exception))
174
174
 
175
+ def test_m2m_save_signal_prevents_iface_ipaddress_and_vminterface_through_defaults(self):
176
+ """Test that the m2m save signal prevents a VMInterface from using the same IPAddressToInterface instance as an Interface."""
177
+ ip_addr = IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
178
+ with self.assertRaises(ValidationError) as cm:
179
+ self.test_int1.ip_addresses.add(ip_addr, through_defaults={"vm_interface": self.test_vmint1})
180
+
181
+ self.assertIn(
182
+ "Cannot use a single instance to associate to both an Interface and a VMInterface.", str(cm.exception)
183
+ )
184
+
185
+ def test_m2m_save_signal_prevents_vminterface_ipaddress_and_interface_through_defaults(self):
186
+ """Test that the m2m save signal prevents an Interface from using the same IPAddressToInterface instance as a VMInterface."""
187
+ ip_addr = IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
188
+ with self.assertRaises(ValidationError) as cm:
189
+ self.test_vmint1.ip_addresses.add(ip_addr, through_defaults={"interface": self.test_int1})
190
+
191
+ self.assertIn(
192
+ "Cannot use a single instance to associate to both an Interface and a VMInterface.", str(cm.exception)
193
+ )
194
+
195
+ def test_m2m_save_signal_prevents_ipaddress_interface_and_vminterface_through_defaults(self):
196
+ """Test that the m2m save signal prevents an Interface being added to an IPAddress with a VMInterface through_defaults."""
197
+ ip_addr = IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
198
+
199
+ with self.assertRaises(ValidationError) as cm:
200
+ ip_addr.interfaces.add(self.test_int1, through_defaults={"vm_interface": self.test_vmint1})
201
+
202
+ self.assertIn(
203
+ "Cannot use a single instance to associate to both an Interface and a VMInterface.", str(cm.exception)
204
+ )
205
+
206
+ def test_m2m_save_signal_prevents_ipaddress_vminterface_and_interface_through_defaults(self):
207
+ """Test that the m2m save signal prevents a VMInterface being added to an IPAddress with an Interface through_defaults."""
208
+ ip_addr = IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
209
+
210
+ with self.assertRaises(ValidationError) as cm:
211
+ ip_addr.vm_interfaces.add(self.test_vmint1, through_defaults={"interface": self.test_int1})
212
+
213
+ self.assertIn(
214
+ "Cannot use a single instance to associate to both an Interface and a VMInterface.", str(cm.exception)
215
+ )
216
+
175
217
  def test_primary_ip_retained_when_deleted_from_device_or_module_interface(self):
176
218
  """Test primary_ip4 remains set when the same IP is assigned to multiple interfaces and deleted from one."""
177
219
 
@@ -1,6 +1,5 @@
1
- from django.test import TestCase
2
-
3
1
  from nautobot.core.models.querysets import count_related
2
+ from nautobot.core.testing import TestCase
4
3
  from nautobot.dcim.models.locations import Location
5
4
  from nautobot.ipam.models import Prefix
6
5
  from nautobot.ipam.tables import PrefixTable
@@ -1,7 +1,7 @@
1
- from django.test import TestCase
2
1
  import netaddr
3
2
 
4
3
  from nautobot.core.forms.utils import parse_numeric_range
4
+ from nautobot.core.testing import TestCase
5
5
  from nautobot.extras.models import Status
6
6
  from nautobot.ipam.models import IPAddress, Namespace, Prefix, VLAN, VLANGroup
7
7
  from nautobot.ipam.utils import add_available_ipaddresses, add_available_vlans
@@ -51,17 +51,7 @@ from nautobot.users.models import ObjectPermission
51
51
  from nautobot.virtualization.models import Cluster, ClusterType, VirtualMachine
52
52
 
53
53
 
54
- class NamespaceTestCase(
55
- ViewTestCases.GetObjectViewTestCase,
56
- ViewTestCases.GetObjectChangelogViewTestCase,
57
- ViewTestCases.GetObjectNotesViewTestCase,
58
- ViewTestCases.CreateObjectViewTestCase,
59
- ViewTestCases.EditObjectViewTestCase,
60
- ViewTestCases.DeleteObjectViewTestCase,
61
- ViewTestCases.ListObjectsViewTestCase,
62
- ViewTestCases.BulkEditObjectsViewTestCase,
63
- ViewTestCases.BulkDeleteObjectsViewTestCase,
64
- ):
54
+ class NamespaceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
65
55
  model = Namespace
66
56
  custom_action_required_permissions = {
67
57
  "ipam:namespace_vrfs": ["ipam.view_namespace", "ipam.view_vrf"],
@@ -72,11 +62,18 @@ class NamespaceTestCase(
72
62
  @classmethod
73
63
  def setUpTestData(cls):
74
64
  locations = Location.objects.get_for_model(Namespace)
65
+ tenants = Tenant.objects.all()[:2]
75
66
 
76
- cls.form_data = {"name": "Namespace X", "location": locations[0].pk, "description": "A new Namespace"}
67
+ cls.form_data = {
68
+ "name": "Namespace X",
69
+ "location": locations[0].pk,
70
+ "tenant": tenants[0].pk,
71
+ "description": "A new Namespace",
72
+ }
77
73
 
78
74
  cls.bulk_edit_data = {
79
75
  "description": "New description",
76
+ "tenant": tenants[1].pk,
80
77
  "location": locations[1].pk,
81
78
  }
82
79
 
@@ -165,7 +162,7 @@ class RIRTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
165
162
  if count > 1:
166
163
  self.assertBodyContains(
167
164
  response,
168
- f'<a href="{prefix_list_url}?{urlencode({"rir": rir.name})}" class="badge">{count}</a>',
165
+ f'<a href="{prefix_list_url}?{urlencode({"rir": rir.name})}" class="badge bg-primary">{count}</a>',
169
166
  )
170
167
  elif count == 1:
171
168
  self.assertBodyContains(response, hyperlinked_object(rir.prefixes.first()))
@@ -250,7 +247,8 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase, ViewTestCases.List
250
247
  count = prefix.locations.count()
251
248
  if count > 1:
252
249
  self.assertBodyContains(
253
- response, f'<a href="{locations_list_url}?prefixes={prefix.pk}" class="badge">{count}</a>'
250
+ response,
251
+ f'<a href="{locations_list_url}?prefixes={prefix.pk}" class="badge bg-primary">{count}</a>',
254
252
  )
255
253
  elif count == 1:
256
254
  self.assertBodyContains(response, hyperlinked_object(prefix.locations.first(), "name"))
@@ -756,6 +754,7 @@ class IPAddressMergeTestCase(ModelViewTestCase):
756
754
  self.add_permissions("ipam.change_ipaddress")
757
755
  num_ips_before = IPAddress.objects.all().count()
758
756
  ips = IPAddress.objects.all().exclude(pk__in=[self.dup_ip_1.pk, self.dup_ip_2.pk, self.dup_ip_3.pk])
757
+ self.assertGreaterEqual(len(ips), 6)
759
758
  ip_ct = ContentType.objects.get_for_model(IPAddress)
760
759
  locations = Location.objects.all()
761
760
  location_ct = ContentType.objects.get_for_model(Location)
nautobot/ipam/ui.py CHANGED
@@ -10,29 +10,12 @@ from nautobot.core.ui.object_detail import (
10
10
  Button,
11
11
  KeyValueTablePanel,
12
12
  ObjectFieldsPanel,
13
- ObjectsTablePanel,
14
13
  )
15
14
  from nautobot.core.views.utils import get_obj_from_context
16
15
 
17
16
  logger = logging.getLogger(__name__)
18
17
 
19
18
 
20
- # TODO: can be removed as a part of NAUTOBOT-1051
21
- class PrefixChildTablePanel(ObjectsTablePanel):
22
- def should_render(self, context: Context):
23
- if not super().should_render(context):
24
- return False
25
- return context.get("active_tab") == "prefixes"
26
-
27
-
28
- # TODO: can be removed as a part of NAUTOBOT-1051
29
- class IPAddressTablePanel(ObjectsTablePanel):
30
- def should_render(self, context: Context):
31
- if not super().should_render(context):
32
- return False
33
- return context.get("active_tab") == "ip-addresses"
34
-
35
-
36
19
  class AddChildPrefixButton(Button):
37
20
  """Custom button to add a child prefix inside a Prefix detail view."""
38
21
 
@@ -146,7 +146,9 @@ def process_vrfs(apps):
146
146
  Namespace = apps.get_model("ipam", "Namespace")
147
147
  VRF = apps.get_model("ipam", "VRF")
148
148
 
149
- global_ns = Namespace.objects.get(name=GLOBAL_NS)
149
+ global_ns, _ = Namespace.objects.get_or_create(
150
+ name=GLOBAL_NS, defaults={"description": "Default Global namespace. Created by Nautobot."}
151
+ )
150
152
  vrfs = VRF.objects.all().order_by("name", "rd")
151
153
  unique_non_empty_vrfs = vrfs.filter(enforce_unique=True).exclude(ip_addresses__isnull=True, prefixes__isnull=True)
152
154
  # At the point in the migration where we iterate through vrfs in global_ns_vrfs, every vrf that
@@ -257,6 +259,11 @@ def process_ip_addresses(apps):
257
259
  broadcast = new_parent_cidr[-1]
258
260
  # This can result in duplicate Prefixes being created in the global_ns but that will be
259
261
  # cleaned up subsequently in `process_prefix_duplicates`.
262
+ if orphaned_ip.vrf and orphaned_ip.vrf.namespace:
263
+ prefix_namespace = orphaned_ip.vrf.namespace
264
+ else:
265
+ prefix_namespace = global_ns
266
+
260
267
  new_parent = Prefix.objects.create(
261
268
  ip_version=orphaned_ip.ip_version,
262
269
  network=network,
@@ -264,7 +271,7 @@ def process_ip_addresses(apps):
264
271
  tenant=orphaned_ip.tenant,
265
272
  vrf=orphaned_ip.vrf,
266
273
  prefix_length=prefix_length,
267
- namespace=orphaned_ip.vrf.namespace if orphaned_ip.vrf else global_ns,
274
+ namespace=prefix_namespace,
268
275
  description=DESCRIPTION,
269
276
  )
270
277
  orphaned_ip.parent = new_parent
@@ -296,6 +303,13 @@ def process_prefix_duplicates(apps):
296
303
  Prefix = apps.get_model("ipam", "Prefix")
297
304
  global_namespace = Namespace.objects.get(name=GLOBAL_NS)
298
305
 
306
+ # First, assign any remaining prefixes with null namespace to Global namespace
307
+ null_namespace_prefixes = Prefix.objects.filter(namespace__isnull=True)
308
+ if null_namespace_prefixes.exists():
309
+ if "test" not in sys.argv:
310
+ print(f">>> Assigning {null_namespace_prefixes.count()} prefixes with null namespace to Global namespace")
311
+ null_namespace_prefixes.update(namespace=global_namespace)
312
+
299
313
  namespaces = list(Namespace.objects.all())
300
314
  # Always start with the Global Namespace.
301
315
  namespaces.remove(global_namespace)
@@ -5,8 +5,6 @@ import random
5
5
  from django.apps import apps
6
6
  from netaddr import IPNetwork
7
7
 
8
- from nautobot.ipam.models import get_default_namespace_pk
9
-
10
8
  # Calculate the probabilities to use for the maybe_subdivide() function defined below.
11
9
 
12
10
  # Frequency of IPv4 (leaf, network) Prefixes by each given mask length in a "realistic" data set.
@@ -129,7 +127,15 @@ def create_prefixes_and_ips(initial_subnet: str, apps=apps, seed="Nautobot"): #
129
127
 
130
128
  all_tenants = list(Tenant.objects.all())
131
129
  if hasattr(VRF, "namespace"):
132
- all_vrfs = list(VRF.objects.filter(namespace_id=get_default_namespace_pk()))
130
+ # Simulate mid-migration state: create Global namespace and assign all VRFs to it
131
+ # This creates more realistic test conditions for namespace-aware migration logic
132
+ Namespace = apps.get_model("ipam", "Namespace")
133
+ global_ns, _ = Namespace.objects.get_or_create(
134
+ name="Global", defaults={"description": "Default Global namespace. Created by Nautobot."}
135
+ )
136
+ # Pre-assign all VRFs to Global namespace to simulate early migration state
137
+ VRF.objects.all().update(namespace=global_ns)
138
+ all_vrfs = list(VRF.objects.filter(namespace_id=global_ns.pk))
133
139
  else:
134
140
  all_vrfs = list(VRF.objects.all())
135
141
 
nautobot/ipam/views.py CHANGED
@@ -44,6 +44,7 @@ from nautobot.extras.models import Role, SavedView, Status, Tag
44
44
  from nautobot.ipam.api import serializers
45
45
  from nautobot.tenancy.models import Tenant
46
46
  from nautobot.virtualization.models import VirtualMachine, VMInterface
47
+ from nautobot.vpn.tables import VPNTunnelEndpointTable
47
48
 
48
49
  from . import filters, forms, tables, ui
49
50
  from .models import (
@@ -423,6 +424,25 @@ class PrefixUIViewSet(NautobotUIViewSet):
423
424
  paginate=False,
424
425
  show_table_config_button=False,
425
426
  ),
427
+ object_detail.ObjectsTablePanel(
428
+ section=SectionChoices.RIGHT_HALF,
429
+ weight=300,
430
+ table_class=VPNTunnelEndpointTable,
431
+ table_attribute="vpn_tunnel_endpoints",
432
+ table_title="VPN Tunnel Endpoints",
433
+ exclude_columns=[
434
+ "vpn_profile",
435
+ "destination_ipaddress",
436
+ "destination_fqdn",
437
+ "protected_prefixes_dg_count",
438
+ "protected_prefixes_count",
439
+ "actions",
440
+ ],
441
+ related_field_name="vpn_tunnel_endpoints",
442
+ add_button_route=None,
443
+ paginate=False,
444
+ show_table_config_button=False,
445
+ ),
426
446
  ],
427
447
  extra_tabs=[
428
448
  object_detail.DistinctViewTab(
@@ -432,13 +452,13 @@ class PrefixUIViewSet(NautobotUIViewSet):
432
452
  related_object_attribute="default_descendants",
433
453
  url_name="ipam:prefix_prefixes",
434
454
  panels=(
435
- ui.PrefixChildTablePanel(
455
+ object_detail.ObjectsTablePanel(
436
456
  section=SectionChoices.FULL_WIDTH,
437
457
  weight=100,
438
458
  context_table_key="prefix_table",
439
459
  add_button_route=None,
440
460
  include_paginator=True,
441
- header_extra_content_template_path="ipam/inc/prefix_header_extra_content_table.html",
461
+ related_field_name="within",
442
462
  ),
443
463
  ),
444
464
  ),
@@ -449,18 +469,24 @@ class PrefixUIViewSet(NautobotUIViewSet):
449
469
  related_object_attribute="all_ips",
450
470
  url_name="ipam:prefix_ipaddresses",
451
471
  panels=[
452
- ui.IPAddressTablePanel(
472
+ object_detail.ObjectsTablePanel(
453
473
  section=SectionChoices.FULL_WIDTH,
454
474
  weight=100,
455
475
  context_table_key="ip_table",
456
476
  add_button_route=None,
457
477
  include_paginator=True,
458
- header_extra_content_template_path="ipam/inc/prefix_header_extra_content_table.html",
478
+ related_field_name="prefix",
459
479
  ),
460
480
  ],
461
481
  ),
462
482
  ],
463
483
  extra_buttons=[
484
+ object_detail.Button(
485
+ weight=100,
486
+ label="Available",
487
+ render_on_tab_id="prefixes",
488
+ template_path="ipam/inc/toggle_available.html",
489
+ ),
464
490
  ui.AddChildPrefixButton(
465
491
  weight=200,
466
492
  label="Add Child Prefix",
@@ -470,8 +496,14 @@ class PrefixUIViewSet(NautobotUIViewSet):
470
496
  required_permissions=["ipam.add_prefix"],
471
497
  render_on_tab_id="prefixes",
472
498
  ),
499
+ object_detail.Button(
500
+ weight=100,
501
+ label="Available",
502
+ render_on_tab_id="ip-addresses",
503
+ template_path="ipam/inc/toggle_available.html",
504
+ ),
473
505
  ui.AddIPAddressButton(
474
- weight=300,
506
+ weight=200,
475
507
  label="Add an IP Address",
476
508
  link_name="ipam:ipaddress_add",
477
509
  color=ButtonActionColorChoices.SUBMIT,
@@ -516,7 +548,9 @@ class PrefixUIViewSet(NautobotUIViewSet):
516
548
 
517
549
  prefix_table = tables.PrefixDetailTable(
518
550
  child_prefixes,
551
+ configurable=True,
519
552
  exclude=["namespace"],
553
+ user=request.user,
520
554
  data_transform_callback=data_transform_callback,
521
555
  )
522
556
  if request.user.has_perm("ipam.change_prefix") or request.user.has_perm("ipam.delete_prefix"):
@@ -547,6 +581,7 @@ class PrefixUIViewSet(NautobotUIViewSet):
547
581
  "active_tab": "prefixes",
548
582
  "view_action": "prefixes",
549
583
  "show_available": request.GET.get("show_available", "true") == "true",
584
+ "badge_count_override": child_prefixes.count(),
550
585
  }
551
586
  )
552
587
 
@@ -568,7 +603,11 @@ class PrefixUIViewSet(NautobotUIViewSet):
568
603
  )
569
604
 
570
605
  ip_table = tables.IPAddressTable(
571
- ipaddresses, exclude=["parent__namespace"], data_transform_callback=data_transform_callback
606
+ ipaddresses,
607
+ configurable=True,
608
+ exclude=["parent__namespace"],
609
+ user=request.user,
610
+ data_transform_callback=data_transform_callback,
572
611
  )
573
612
  if request.user.has_perm("ipam.change_ipaddress") or request.user.has_perm("ipam.delete_ipaddress"):
574
613
  ip_table.columns.show("pk")
@@ -597,6 +636,7 @@ class PrefixUIViewSet(NautobotUIViewSet):
597
636
  "active_tab": "ip-addresses",
598
637
  "view_action": "ip_addresses",
599
638
  "show_available": request.GET.get("show_available", "true") == "true",
639
+ "badge_count_override": ipaddresses.count(),
600
640
  }
601
641
  )
602
642