nautobot 3.0.0a3__py3-none-any.whl → 3.0.0rc1__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 (388) hide show
  1. nautobot/apps/choices.py +4 -0
  2. nautobot/apps/ui.py +4 -0
  3. nautobot/apps/utils.py +8 -0
  4. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
  5. nautobot/circuits/views.py +6 -2
  6. nautobot/core/api/serializers.py +1 -1
  7. nautobot/core/api/urls.py +1 -0
  8. nautobot/core/api/views.py +4 -0
  9. nautobot/core/choices.py +1 -1
  10. nautobot/core/cli/bootstrap_v3_to_v5.py +36 -13
  11. nautobot/core/cli/migrate_deprecated_templates.py +36 -9
  12. nautobot/core/filters.py +4 -0
  13. nautobot/core/forms/__init__.py +2 -0
  14. nautobot/core/forms/widgets.py +21 -2
  15. nautobot/core/jobs/__init__.py +56 -0
  16. nautobot/core/management/commands/generate_test_data.py +3 -3
  17. nautobot/core/models/__init__.py +11 -0
  18. nautobot/core/models/utils.py +1 -1
  19. nautobot/core/settings.py +17 -7
  20. nautobot/core/settings.yaml +4 -26
  21. nautobot/core/templates/admin/base.html +1 -2
  22. nautobot/core/templates/admin/change_list.html +9 -12
  23. nautobot/core/templates/base_django.html +1 -2
  24. nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
  25. nautobot/core/templates/components/tab/content_wrapper.html +4 -4
  26. nautobot/core/templates/echarts/echarts.html +21 -8
  27. nautobot/core/templates/generic/object_bulk_create.html +2 -2
  28. nautobot/core/templates/generic/object_bulk_delete.html +1 -1
  29. nautobot/core/templates/generic/object_bulk_edit.html +1 -1
  30. nautobot/core/templates/generic/object_bulk_import.html +1 -1
  31. nautobot/core/templates/generic/object_delete.html +1 -1
  32. nautobot/core/templates/generic/object_detail.html +1 -1
  33. nautobot/core/templates/generic/object_edit.html +1 -1
  34. nautobot/core/templates/generic/object_retrieve.html +2 -2
  35. nautobot/core/templates/graphene/graphiql.html +0 -1
  36. nautobot/core/templates/inc/footer.html +3 -1
  37. nautobot/core/templates/inc/header.html +10 -0
  38. nautobot/core/templates/inc/media.html +14 -0
  39. nautobot/core/templates/inc/nav_menu.html +1 -8
  40. nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
  41. nautobot/core/templates/nautobot_config.py.j2 +0 -6
  42. nautobot/core/templates/rest_framework/api.html +103 -2
  43. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +33 -0
  44. nautobot/core/templates/utilities/theme_preview.html +3 -0
  45. nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
  46. nautobot/core/templatetags/helpers.py +24 -12
  47. nautobot/core/testing/integration.py +24 -13
  48. nautobot/core/testing/utils.py +18 -4
  49. nautobot/core/testing/views.py +104 -17
  50. nautobot/core/tests/integration/test_filters.py +48 -11
  51. nautobot/core/tests/integration/test_theme.py +22 -21
  52. nautobot/core/tests/nautobot_config.py +3 -0
  53. nautobot/core/tests/runner.py +1 -2
  54. nautobot/core/tests/test_breadcrumbs.py +21 -21
  55. nautobot/core/tests/test_jobs.py +73 -6
  56. nautobot/core/tests/test_renderers.py +59 -0
  57. nautobot/core/tests/test_settings_schema.py +1 -0
  58. nautobot/core/tests/test_templatetags_helpers.py +9 -0
  59. nautobot/core/tests/test_titles.py +0 -16
  60. nautobot/core/tests/test_ui.py +122 -3
  61. nautobot/core/tests/test_utils.py +41 -1
  62. nautobot/core/ui/breadcrumbs.py +68 -17
  63. nautobot/core/ui/bulk_buttons.py +1 -1
  64. nautobot/core/ui/choices.py +49 -65
  65. nautobot/core/ui/echarts.py +15 -20
  66. nautobot/core/ui/object_detail.py +54 -46
  67. nautobot/core/ui/titles.py +3 -6
  68. nautobot/core/urls.py +8 -8
  69. nautobot/core/utils/filtering.py +11 -1
  70. nautobot/core/utils/lookup.py +46 -0
  71. nautobot/core/views/mixins.py +31 -20
  72. nautobot/core/views/renderers.py +2 -3
  73. nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -2
  74. nautobot/dcim/api/serializers.py +3 -0
  75. nautobot/dcim/choices.py +49 -0
  76. nautobot/dcim/constants.py +7 -0
  77. nautobot/dcim/factory.py +1 -1
  78. nautobot/dcim/filters.py +13 -1
  79. nautobot/dcim/forms.py +89 -3
  80. nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
  81. nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
  82. nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
  83. nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
  84. nautobot/dcim/migrations/{0078_remove_device_location_tenant_name_uniqueness.py → 0079_remove_device_location_tenant_name_uniqueness.py} +1 -1
  85. nautobot/dcim/migrations/{0079_device_name_data_migration.py → 0080_device_name_data_migration.py} +1 -1
  86. nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
  87. nautobot/dcim/models/device_component_templates.py +33 -1
  88. nautobot/dcim/models/device_components.py +22 -1
  89. nautobot/dcim/models/devices.py +17 -4
  90. nautobot/dcim/tables/devices.py +15 -0
  91. nautobot/dcim/tables/devicetypes.py +8 -1
  92. nautobot/dcim/tables/racks.py +0 -2
  93. nautobot/dcim/tables/template_code.py +1 -1
  94. nautobot/dcim/templates/dcim/cable_trace.html +0 -2
  95. nautobot/dcim/templates/dcim/consoleport.html +1 -1
  96. nautobot/dcim/templates/dcim/consoleserverport.html +1 -1
  97. nautobot/dcim/templates/dcim/devicebay.html +1 -1
  98. nautobot/dcim/templates/dcim/frontport.html +1 -1
  99. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  100. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
  101. nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
  102. nautobot/dcim/templates/dcim/interface.html +9 -1
  103. nautobot/dcim/templates/dcim/interface_edit.html +2 -0
  104. nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
  105. nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
  106. nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
  107. nautobot/dcim/templates/dcim/module_frontports.html +1 -1
  108. nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
  109. nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
  110. nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
  111. nautobot/dcim/templates/dcim/module_powerports.html +1 -1
  112. nautobot/dcim/templates/dcim/module_rearports.html +1 -1
  113. nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
  114. nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
  115. nautobot/dcim/templates/dcim/powerport.html +1 -1
  116. nautobot/dcim/templates/dcim/rack_elevation_list.html +1 -1
  117. nautobot/dcim/templates/dcim/rack_retrieve.html +0 -11
  118. nautobot/dcim/templates/dcim/rearport.html +1 -1
  119. nautobot/dcim/templates/dcim/trace/cable.html +1 -1
  120. nautobot/dcim/templates/dcim/virtualchassis_update.html +1 -1
  121. nautobot/dcim/tests/integration/test_controller.py +3 -6
  122. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
  123. nautobot/dcim/tests/integration/test_create_device.py +0 -2
  124. nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
  125. nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
  126. nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
  127. nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
  128. nautobot/dcim/tests/test_api.py +186 -6
  129. nautobot/dcim/tests/test_filters.py +43 -1
  130. nautobot/dcim/tests/test_forms.py +110 -8
  131. nautobot/dcim/tests/test_graphql.py +44 -1
  132. nautobot/dcim/tests/test_models.py +265 -0
  133. nautobot/dcim/tests/test_tables.py +160 -0
  134. nautobot/dcim/tests/test_views.py +69 -7
  135. nautobot/dcim/views.py +232 -126
  136. nautobot/extras/api/views.py +51 -44
  137. nautobot/extras/datasources/git.py +3 -1
  138. nautobot/extras/filters.py +19 -2
  139. nautobot/extras/forms/forms.py +9 -2
  140. nautobot/extras/jobs.py +2 -0
  141. nautobot/extras/jobs_ui.py +4 -3
  142. nautobot/extras/management/__init__.py +2 -0
  143. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
  144. nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
  145. nautobot/extras/models/approvals.py +11 -1
  146. nautobot/extras/models/change_logging.py +4 -0
  147. nautobot/extras/models/jobs.py +1 -3
  148. nautobot/extras/models/models.py +10 -2
  149. nautobot/extras/plugins/marketplace_manifest.yml +49 -1
  150. nautobot/extras/plugins/views.py +0 -5
  151. nautobot/extras/querysets.py +8 -0
  152. nautobot/extras/tables.py +12 -0
  153. nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
  154. nautobot/extras/templates/extras/configcontext_update.html +1 -0
  155. nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
  156. nautobot/extras/templates/extras/objectchange_retrieve.html +0 -2
  157. nautobot/extras/templates/extras/plugin_detail.html +3 -3
  158. nautobot/extras/templates/extras/secret_create.html +1 -1
  159. nautobot/extras/tests/integration/test_computedfields.py +8 -9
  160. nautobot/extras/tests/integration/test_customfields.py +1 -3
  161. nautobot/extras/tests/integration/test_dynamicgroups.py +7 -8
  162. nautobot/extras/tests/integration/test_relationships.py +0 -2
  163. nautobot/extras/tests/test_api.py +63 -0
  164. nautobot/extras/tests/test_changelog.py +24 -2
  165. nautobot/extras/tests/test_filters.py +36 -3
  166. nautobot/extras/tests/test_models.py +38 -2
  167. nautobot/extras/tests/test_utils.py +3 -4
  168. nautobot/extras/tests/test_views.py +22 -83
  169. nautobot/extras/urls.py +0 -14
  170. nautobot/extras/views.py +83 -52
  171. nautobot/ipam/filters.py +26 -0
  172. nautobot/ipam/tables.py +6 -0
  173. nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
  174. nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
  175. nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
  176. nautobot/ipam/tests/test_filters.py +26 -1
  177. nautobot/ipam/tests/test_models.py +1 -1
  178. nautobot/ipam/views.py +9 -7
  179. nautobot/load_balancers/__init__.py +0 -0
  180. nautobot/load_balancers/api/__init__.py +1 -0
  181. nautobot/load_balancers/api/serializers.py +75 -0
  182. nautobot/load_balancers/api/urls.py +23 -0
  183. nautobot/load_balancers/api/views.py +61 -0
  184. nautobot/load_balancers/apps.py +17 -0
  185. nautobot/load_balancers/choices.py +167 -0
  186. nautobot/load_balancers/filters.py +225 -0
  187. nautobot/load_balancers/forms.py +532 -0
  188. nautobot/load_balancers/management/commands/__init__.py +0 -0
  189. nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
  190. nautobot/load_balancers/migrations/0001_initial.py +465 -0
  191. nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
  192. nautobot/load_balancers/migrations/__init__.py +0 -0
  193. nautobot/load_balancers/models.py +423 -0
  194. nautobot/load_balancers/navigation.py +80 -0
  195. nautobot/load_balancers/tables.py +255 -0
  196. nautobot/load_balancers/tests/__init__.py +474 -0
  197. nautobot/load_balancers/tests/test_api.py +353 -0
  198. nautobot/load_balancers/tests/test_filters.py +134 -0
  199. nautobot/load_balancers/tests/test_forms.py +266 -0
  200. nautobot/load_balancers/tests/test_models.py +195 -0
  201. nautobot/load_balancers/tests/test_views.py +229 -0
  202. nautobot/load_balancers/urls.py +17 -0
  203. nautobot/load_balancers/views.py +248 -0
  204. nautobot/project-static/dist/css/github-dark.min.css +10 -0
  205. nautobot/project-static/dist/css/github.min.css +10 -0
  206. nautobot/project-static/dist/css/nautobot.css +1 -11
  207. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  208. nautobot/project-static/dist/js/libraries.js +1 -1
  209. nautobot/project-static/dist/js/libraries.js.map +1 -1
  210. nautobot/project-static/dist/js/nautobot.js +1 -1
  211. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  212. nautobot/project-static/js/forms.js +13 -0
  213. nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
  214. nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
  215. nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
  216. nautobot/ui/package-lock.json +87 -4
  217. nautobot/ui/package.json +2 -1
  218. nautobot/ui/src/js/nautobot.js +0 -1
  219. nautobot/ui/src/js/select2.js +53 -2
  220. nautobot/ui/src/scss/nautobot.scss +51 -2
  221. nautobot/ui/webpack.config.js +13 -0
  222. nautobot/users/templates/users/preferences.html +11 -2
  223. nautobot/virtualization/filters.py +6 -1
  224. nautobot/virtualization/tests/test_filters.py +10 -1
  225. nautobot/virtualization/tests/test_models.py +1 -0
  226. nautobot/virtualization/views.py +4 -1
  227. nautobot/vpn/factory.py +25 -15
  228. nautobot/vpn/filters.py +1 -0
  229. nautobot/vpn/forms.py +1 -0
  230. nautobot/vpn/migrations/0001_initial.py +1 -1
  231. nautobot/vpn/models.py +16 -8
  232. nautobot/vpn/tables.py +5 -2
  233. nautobot/vpn/tests/test_api.py +0 -5
  234. nautobot/vpn/tests/test_forms.py +1 -2
  235. nautobot/vpn/tests/test_models.py +57 -7
  236. nautobot/vpn/tests/test_views.py +22 -3
  237. nautobot/vpn/views.py +78 -20
  238. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +4 -4
  239. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +243 -352
  240. nautobot/circuits/templates/circuits/circuit.html +0 -2
  241. nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
  242. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
  243. nautobot/circuits/templates/circuits/circuit_update.html +0 -1
  244. nautobot/circuits/templates/circuits/circuittermination.html +0 -2
  245. nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
  246. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
  247. nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
  248. nautobot/circuits/templates/circuits/circuittype.html +0 -2
  249. nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
  250. nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
  251. nautobot/circuits/templates/circuits/provider.html +0 -2
  252. nautobot/circuits/templates/circuits/provider_edit.html +0 -2
  253. nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
  254. nautobot/circuits/templates/circuits/provider_update.html +0 -1
  255. nautobot/circuits/templates/circuits/providernetwork.html +0 -2
  256. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
  257. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
  258. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
  259. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
  260. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
  261. nautobot/core/templates/buttons/import.html +0 -9
  262. nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
  263. nautobot/dcim/templates/dcim/cable.html +0 -2
  264. nautobot/dcim/templates/dcim/cable_edit.html +0 -2
  265. nautobot/dcim/templates/dcim/controller/base.html +0 -2
  266. nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
  267. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
  268. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
  269. nautobot/dcim/templates/dcim/device/base.html +0 -2
  270. nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
  271. nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
  272. nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
  273. nautobot/dcim/templates/dcim/device/frontports.html +0 -2
  274. nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
  275. nautobot/dcim/templates/dcim/device/inventory.html +0 -2
  276. nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
  277. nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
  278. nautobot/dcim/templates/dcim/device/powerports.html +0 -2
  279. nautobot/dcim/templates/dcim/device/rearports.html +0 -2
  280. nautobot/dcim/templates/dcim/device/wireless.html +0 -2
  281. nautobot/dcim/templates/dcim/device_component.html +0 -2
  282. nautobot/dcim/templates/dcim/device_edit.html +0 -2
  283. nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
  284. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
  285. nautobot/dcim/templates/dcim/devicetype.html +0 -2
  286. nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
  287. nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
  288. nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
  289. nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
  290. nautobot/dcim/templates/dcim/location.html +0 -2
  291. nautobot/dcim/templates/dcim/location_edit.html +0 -2
  292. nautobot/dcim/templates/dcim/location_retrieve.html +0 -2
  293. nautobot/dcim/templates/dcim/locationtype.html +0 -2
  294. nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
  295. nautobot/dcim/templates/dcim/manufacturer.html +0 -2
  296. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
  297. nautobot/dcim/templates/dcim/platform.html +0 -2
  298. nautobot/dcim/templates/dcim/powerfeed.html +0 -2
  299. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
  300. nautobot/dcim/templates/dcim/powerpanel.html +0 -2
  301. nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
  302. nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
  303. nautobot/dcim/templates/dcim/rack.html +0 -2
  304. nautobot/dcim/templates/dcim/rack_edit.html +0 -2
  305. nautobot/dcim/templates/dcim/rackgroup.html +0 -2
  306. nautobot/dcim/templates/dcim/rackreservation.html +0 -2
  307. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
  308. nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
  309. nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
  310. nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
  311. nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
  312. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
  313. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
  314. nautobot/dcim/ui.py +0 -29
  315. nautobot/extras/templates/extras/computedfield.html +0 -2
  316. nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
  317. nautobot/extras/templates/extras/configcontext.html +0 -2
  318. nautobot/extras/templates/extras/configcontext_edit.html +0 -2
  319. nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
  320. nautobot/extras/templates/extras/configcontextschema.html +0 -2
  321. nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
  322. nautobot/extras/templates/extras/contact_retrieve.html +0 -2
  323. nautobot/extras/templates/extras/customfield.html +0 -2
  324. nautobot/extras/templates/extras/customfield_edit.html +0 -2
  325. nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
  326. nautobot/extras/templates/extras/customlink.html +0 -2
  327. nautobot/extras/templates/extras/dynamicgroup.html +0 -2
  328. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
  329. nautobot/extras/templates/extras/exporttemplate.html +0 -2
  330. nautobot/extras/templates/extras/gitrepository.html +0 -2
  331. nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
  332. nautobot/extras/templates/extras/graphqlquery.html +0 -2
  333. nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
  334. nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -2
  335. nautobot/extras/templates/extras/job_detail.html +0 -2
  336. nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
  337. nautobot/extras/templates/extras/jobhook.html +0 -2
  338. nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
  339. nautobot/extras/templates/extras/jobresult.html +0 -2
  340. nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
  341. nautobot/extras/templates/extras/note.html +0 -2
  342. nautobot/extras/templates/extras/note_retrieve.html +0 -1
  343. nautobot/extras/templates/extras/object_changelog.html +0 -2
  344. nautobot/extras/templates/extras/object_notes.html +0 -2
  345. nautobot/extras/templates/extras/objectchange.html +0 -2
  346. nautobot/extras/templates/extras/objectchange_list.html +0 -3
  347. nautobot/extras/templates/extras/relationship.html +0 -1
  348. nautobot/extras/templates/extras/secret.html +0 -1
  349. nautobot/extras/templates/extras/secret_edit.html +0 -1
  350. nautobot/extras/templates/extras/secretsgroup.html +0 -2
  351. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
  352. nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
  353. nautobot/extras/templates/extras/status.html +0 -2
  354. nautobot/extras/templates/extras/tag.html +0 -2
  355. nautobot/extras/templates/extras/tag_edit.html +0 -2
  356. nautobot/extras/templates/extras/tag_retrieve.html +0 -2
  357. nautobot/extras/templates/extras/team_retrieve.html +0 -2
  358. nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
  359. nautobot/ipam/templates/ipam/prefix.html +0 -2
  360. nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
  361. nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
  362. nautobot/ipam/templates/ipam/rir.html +0 -2
  363. nautobot/ipam/templates/ipam/routetarget.html +0 -1
  364. nautobot/ipam/templates/ipam/service.html +0 -2
  365. nautobot/ipam/templates/ipam/service_edit.html +0 -2
  366. nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
  367. nautobot/ipam/templates/ipam/vlan.html +0 -2
  368. nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
  369. nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
  370. nautobot/ipam/templates/ipam/vlangroup.html +0 -2
  371. nautobot/ipam/templates/ipam/vrf.html +0 -1
  372. nautobot/tenancy/templates/tenancy/tenant.html +0 -2
  373. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
  374. nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
  375. nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
  376. nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
  377. nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
  378. nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
  379. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
  380. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
  381. nautobot/vpn/templates/vpn/vpnprofile.html +0 -2
  382. nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
  383. nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
  384. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
  385. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
  386. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
  387. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
  388. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +0 -0
@@ -17,7 +17,7 @@
17
17
  "graphiql": "2.4.7",
18
18
  "graphql": "16.10.0",
19
19
  "graphql-ws": "5.13.1",
20
- "highlight.js": "11.9.0",
20
+ "highlight.js": "^11.11.1",
21
21
  "htmx.org": "^2.0.6",
22
22
  "jquery": "^3.7.1",
23
23
  "jquery-ui": "^1.14.1",
@@ -32,6 +32,7 @@
32
32
  "devDependencies": {
33
33
  "@eslint/js": "^9.32.0",
34
34
  "autoprefixer": "^10.4.20",
35
+ "copy-webpack-plugin": "^13.0.1",
35
36
  "css-loader": "^7.1.2",
36
37
  "eslint": "^9.32.0",
37
38
  "eslint-plugin-import": "^2.32.0",
@@ -2055,6 +2056,30 @@
2055
2056
  "toggle-selection": "^1.0.6"
2056
2057
  }
2057
2058
  },
2059
+ "node_modules/copy-webpack-plugin": {
2060
+ "version": "13.0.1",
2061
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz",
2062
+ "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==",
2063
+ "dev": true,
2064
+ "license": "MIT",
2065
+ "dependencies": {
2066
+ "glob-parent": "^6.0.1",
2067
+ "normalize-path": "^3.0.0",
2068
+ "schema-utils": "^4.2.0",
2069
+ "serialize-javascript": "^6.0.2",
2070
+ "tinyglobby": "^0.2.12"
2071
+ },
2072
+ "engines": {
2073
+ "node": ">= 18.12.0"
2074
+ },
2075
+ "funding": {
2076
+ "type": "opencollective",
2077
+ "url": "https://opencollective.com/webpack"
2078
+ },
2079
+ "peerDependencies": {
2080
+ "webpack": "^5.1.0"
2081
+ }
2082
+ },
2058
2083
  "node_modules/cosmiconfig": {
2059
2084
  "version": "9.0.0",
2060
2085
  "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
@@ -3507,9 +3532,9 @@
3507
3532
  }
3508
3533
  },
3509
3534
  "node_modules/highlight.js": {
3510
- "version": "11.9.0",
3511
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
3512
- "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
3535
+ "version": "11.11.1",
3536
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
3537
+ "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
3513
3538
  "license": "BSD-3-Clause",
3514
3539
  "engines": {
3515
3540
  "node": ">=12.0.0"
@@ -4467,6 +4492,16 @@
4467
4492
  "dev": true,
4468
4493
  "license": "MIT"
4469
4494
  },
4495
+ "node_modules/normalize-path": {
4496
+ "version": "3.0.0",
4497
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
4498
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
4499
+ "dev": true,
4500
+ "license": "MIT",
4501
+ "engines": {
4502
+ "node": ">=0.10.0"
4503
+ }
4504
+ },
4470
4505
  "node_modules/normalize-range": {
4471
4506
  "version": "0.1.2",
4472
4507
  "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
@@ -6028,6 +6063,54 @@
6028
6063
  "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
6029
6064
  "license": "MIT"
6030
6065
  },
6066
+ "node_modules/tinyglobby": {
6067
+ "version": "0.2.15",
6068
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
6069
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
6070
+ "dev": true,
6071
+ "license": "MIT",
6072
+ "dependencies": {
6073
+ "fdir": "^6.5.0",
6074
+ "picomatch": "^4.0.3"
6075
+ },
6076
+ "engines": {
6077
+ "node": ">=12.0.0"
6078
+ },
6079
+ "funding": {
6080
+ "url": "https://github.com/sponsors/SuperchupuDev"
6081
+ }
6082
+ },
6083
+ "node_modules/tinyglobby/node_modules/fdir": {
6084
+ "version": "6.5.0",
6085
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
6086
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
6087
+ "dev": true,
6088
+ "license": "MIT",
6089
+ "engines": {
6090
+ "node": ">=12.0.0"
6091
+ },
6092
+ "peerDependencies": {
6093
+ "picomatch": "^3 || ^4"
6094
+ },
6095
+ "peerDependenciesMeta": {
6096
+ "picomatch": {
6097
+ "optional": true
6098
+ }
6099
+ }
6100
+ },
6101
+ "node_modules/tinyglobby/node_modules/picomatch": {
6102
+ "version": "4.0.3",
6103
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
6104
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
6105
+ "dev": true,
6106
+ "license": "MIT",
6107
+ "engines": {
6108
+ "node": ">=12"
6109
+ },
6110
+ "funding": {
6111
+ "url": "https://github.com/sponsors/jonschlinkert"
6112
+ }
6113
+ },
6031
6114
  "node_modules/to-regex-range": {
6032
6115
  "version": "5.0.1",
6033
6116
  "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
nautobot/ui/package.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "graphiql": "2.4.7",
24
24
  "graphql": "16.10.0",
25
25
  "graphql-ws": "5.13.1",
26
- "highlight.js": "11.9.0",
26
+ "highlight.js": "^11.11.1",
27
27
  "htmx.org": "^2.0.6",
28
28
  "jquery": "^3.7.1",
29
29
  "jquery-ui": "^1.14.1",
@@ -38,6 +38,7 @@
38
38
  "devDependencies": {
39
39
  "@eslint/js": "^9.32.0",
40
40
  "autoprefixer": "^10.4.20",
41
+ "copy-webpack-plugin": "^13.0.1",
41
42
  "css-loader": "^7.1.2",
42
43
  "eslint": "^9.32.0",
43
44
  "eslint-plugin-import": "^2.32.0",
@@ -15,7 +15,6 @@ import flatpickr from 'flatpickr';
15
15
  window.flatpickr = flatpickr;
16
16
 
17
17
  import hljs from 'highlight.js/lib/core';
18
- import 'highlight.js/styles/github.css';
19
18
  import graphql from 'highlight.js/lib/languages/graphql';
20
19
  import json from 'highlight.js/lib/languages/json';
21
20
  import xml from 'highlight.js/lib/languages/xml';
@@ -81,7 +81,7 @@ const initializeSelect2 = (context, selector, options) =>
81
81
  [...getElement(context).querySelectorAll(selector)].forEach((element) =>
82
82
  $(element).select2({
83
83
  allowClear: true,
84
- placeholder: element.getAttribute('placeholder') || '---------',
84
+ placeholder: '---------',
85
85
  selectionCssClass: 'select2--small',
86
86
  theme: 'bootstrap-5',
87
87
  width: 'off',
@@ -292,14 +292,65 @@ const initializeDynamicChoiceSelection = (context, dropdownParent = null) => {
292
292
  });
293
293
  };
294
294
 
295
- const initializeMultiValueChar = (context, dropdownParent = null) =>
295
+ const initializeMultiValueChar = (context, dropdownParent = null) => {
296
296
  initializeSelect2(context, '.nautobot-select2-multi-value-char', {
297
297
  dropdownParent,
298
298
  language: { noResults: () => 'Type something to add it as an option' },
299
299
  multiple: true,
300
300
  tags: true,
301
+ tokenSeparators: [','],
301
302
  });
302
303
 
304
+ // Ensure pressing Enter in the Select2 search adds the current token instead of submitting the form
305
+ [...getElement(context).querySelectorAll('.nautobot-select2-multi-value-char')].forEach((element) => {
306
+ $(element).on('select2:open', () => {
307
+ const container = document.querySelector('.select2-container--open');
308
+ if (!container) {
309
+ return;
310
+ }
311
+ const search = container.querySelector('input.select2-search__field');
312
+ if (!search) {
313
+ return;
314
+ }
315
+
316
+ // Avoid stacking multiple handlers
317
+ if (search.getAttribute('data-enter-binds')) {
318
+ return;
319
+ }
320
+ search.setAttribute('data-enter-binds', '1');
321
+
322
+ search.addEventListener('keydown', (ev) => {
323
+ if (ev.key === 'Enter') {
324
+ ev.preventDefault();
325
+ ev.stopPropagation();
326
+ const val = search.value.trim();
327
+ if (!val) {
328
+ return;
329
+ }
330
+ const sel = $(element).get(0);
331
+ // If option doesn't exist, create it; otherwise select it
332
+ const found = Array.prototype.find.call(sel.options, (opt) => String(opt.value) === String(val));
333
+ if (found) {
334
+ found.selected = true;
335
+ } else {
336
+ sel.add(new Option(val, val, true, true));
337
+ }
338
+ // Clear the search box and notify Select2
339
+ search.value = '';
340
+ $(element).trigger('change');
341
+ // Close the dropdown so it doesn't linger after add
342
+ try {
343
+ $(element).select2('close');
344
+ // eslint-disable-next-line no-unused-vars
345
+ } catch (exception) {
346
+ // Intentional no-op
347
+ }
348
+ }
349
+ });
350
+ });
351
+ });
352
+ };
353
+
303
354
  const initializeStaticChoiceSelection = (context, dropdownParent = null) =>
304
355
  initializeSelect2(context, '.nautobot-select2-static', { dropdownParent });
305
356
 
@@ -907,6 +907,12 @@ $table-variants: (
907
907
 
908
908
  color: var(--#{$prefix}table-color);
909
909
  border-color: var(--#{$prefix}table-border-color);
910
+
911
+ @if $color == "primary" or $color == "info" {
912
+ a {
913
+ text-decoration: underline;
914
+ }
915
+ }
910
916
  }
911
917
  }
912
918
 
@@ -915,6 +921,10 @@ $table-variants: (
915
921
  white-space: nowrap;
916
922
  }
917
923
 
924
+ table.attr-table td:nth-child(1) {
925
+ width: 25%;
926
+ }
927
+
918
928
  /* Buttons */
919
929
  .btn {
920
930
  --#{$prefix}btn-box-shadow: #{$btn-box-shadow};
@@ -1262,6 +1272,18 @@ textarea {
1262
1272
  }
1263
1273
  }
1264
1274
 
1275
+ .card-body {
1276
+ .nav-tabs {
1277
+ margin-inline: $card-spacer-x * -1;
1278
+ }
1279
+
1280
+ .tab-content {
1281
+ > .tab-pane {
1282
+ padding-block-start: $card-spacer-y;
1283
+ }
1284
+ }
1285
+ }
1286
+
1265
1287
  .card-header {
1266
1288
  position: relative;
1267
1289
  text-transform: uppercase;
@@ -1397,6 +1419,11 @@ textarea {
1397
1419
  }
1398
1420
  }
1399
1421
 
1422
+ /* Alerts */
1423
+ .alert:is(.alert-primary, .alert-info) a {
1424
+ text-decoration: underline;
1425
+ }
1426
+
1400
1427
  /* Progress bars */
1401
1428
  .progress {
1402
1429
  td & {
@@ -1656,7 +1683,7 @@ body {
1656
1683
  }
1657
1684
 
1658
1685
  img {
1659
- height: 1.875rem; /* 30px */
1686
+ height: 2rem; /* 32px */
1660
1687
  }
1661
1688
  }
1662
1689
 
@@ -1876,7 +1903,7 @@ body {
1876
1903
  @include fade-in;
1877
1904
  @include transition($transition-base);
1878
1905
  right: -0.8125rem; /* -13px */
1879
- top: 1.375rem; /* 22px */
1906
+ top: 1.4375rem; /* 23px */
1880
1907
 
1881
1908
  &::before { /* Chevron icon */
1882
1909
  @include chevron(1rem, $navy-0); /* 16px */
@@ -2354,6 +2381,11 @@ pre code.hljs {
2354
2381
  border-radius: $border-radius;
2355
2382
  }
2356
2383
 
2384
+ /* Swagger docs */
2385
+ pre.version, pre.version-stamp {
2386
+ background-color: inherit !important;
2387
+ }
2388
+
2357
2389
  /* Rendered Markdown */
2358
2390
  .nb-rendered-markdown {
2359
2391
  table {
@@ -2469,3 +2501,20 @@ pre code.hljs {
2469
2501
  .d-none {
2470
2502
  display: none !important;
2471
2503
  }
2504
+
2505
+ /* Dark mode workaround */
2506
+ @include color-mode(dark, true) {
2507
+ .invert-in-dark-mode {
2508
+ filter: invert(1) hue-rotate(180deg);
2509
+ }
2510
+ }
2511
+
2512
+ /*
2513
+ * FIXME(norbert-mieczkowski-codilime): bootstrap-filestyle library, which is used by `ClearableFileInput` widget,
2514
+ * currently in version 1.2.3, still uses Bootstrap 3. This `.hidden` class "polyfill" is required until the library
2515
+ * is updated or removed/replaced completely. Bootstrap 5 supports file inputs out of the box, so it may be a good
2516
+ * time to get rid of bootstrap-filestyle.
2517
+ */
2518
+ .hidden {
2519
+ @extend .d-none;
2520
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import path from 'node:path';
4
4
  import autoprefixer from 'autoprefixer';
5
+ import CopyPlugin from 'copy-webpack-plugin';
5
6
  import miniCssExtractPlugin from 'mini-css-extract-plugin'
6
7
 
7
8
  const __dirname = import.meta.dirname;
@@ -24,6 +25,18 @@ export default [
24
25
  }
25
26
  },
26
27
  plugins: [
28
+ new CopyPlugin({
29
+ patterns: [
30
+ {
31
+ from: path.resolve(__dirname, 'node_modules', 'highlight.js', 'styles', 'github.min.css'),
32
+ to: path.resolve(__dirname, '..', 'project-static', 'dist', 'css'),
33
+ },
34
+ {
35
+ from: path.resolve(__dirname, 'node_modules', 'highlight.js', 'styles', 'github-dark.min.css'),
36
+ to: path.resolve(__dirname, '..', 'project-static', 'dist', 'css'),
37
+ },
38
+ ],
39
+ }),
27
40
  new miniCssExtractPlugin(
28
41
  {
29
42
  filename: 'css/[name].css'
@@ -13,7 +13,11 @@
13
13
  <table class="table table-striped">
14
14
  <thead>
15
15
  <tr>
16
- <th><input type="checkbox" class="toggle" title="Toggle all"></th>
16
+ <th>
17
+ <input class="form-check-input nb-form-check-input-sm mt-2 toggle"
18
+ title="Toggle all"
19
+ type="checkbox">
20
+ </th>
17
21
  <th>Preference</th>
18
22
  <th>Value</th>
19
23
  </tr>
@@ -21,7 +25,12 @@
21
25
  <tbody>
22
26
  {% for key, value in preferences.items %}
23
27
  <tr>
24
- <td class="nb-w-0"><input type="checkbox" name="pk" value="{{ key }}"></td>
28
+ <td class="nb-w-0">
29
+ <input class="form-check-input nb-form-check-input-sm mt-2"
30
+ name="pk"
31
+ type="checkbox"
32
+ value="{{ key }}">
33
+ </td>
25
34
  <td><samp>{{ key }}</samp></td>
26
35
  <td><samp>{{ value }}</samp></td>
27
36
  </tr>
@@ -22,7 +22,7 @@ from nautobot.extras.filters import (
22
22
  RoleModelFilterSetMixin,
23
23
  StatusModelFilterSetMixin,
24
24
  )
25
- from nautobot.ipam.models import IPAddress, Service, VLAN
25
+ from nautobot.ipam.models import IPAddress, Service, VLAN, VRF
26
26
  from nautobot.tenancy.filters import TenancyModelFilterSetMixin
27
27
 
28
28
  from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@@ -224,6 +224,11 @@ class VirtualMachineFilterSet(
224
224
  distinct=True,
225
225
  )
226
226
  has_ip_addresses = RelatedMembershipBooleanFilter(field_name="interfaces__ip_addresses", label="Has IP addresses")
227
+ vrfs = NaturalKeyOrPKMultipleChoiceFilter(
228
+ queryset=VRF.objects.all(),
229
+ to_field_name="rd",
230
+ label="VRFs (ID or RD)",
231
+ )
227
232
 
228
233
  def filter_ip_addresses(self, queryset, name, value):
229
234
  pk_values = set(item for item in value if is_uuid(item))
@@ -3,7 +3,7 @@ from nautobot.dcim.choices import InterfaceModeChoices
3
3
  from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer, Platform, SoftwareVersion
4
4
  from nautobot.extras.models import Role, Status, Tag
5
5
  from nautobot.ipam.choices import ServiceProtocolChoices
6
- from nautobot.ipam.models import IPAddress, Namespace, Prefix, Service, VLAN
6
+ from nautobot.ipam.models import IPAddress, Namespace, Prefix, Service, VLAN, VRF, VRFDeviceAssignment
7
7
  from nautobot.tenancy.models import Tenant
8
8
  from nautobot.virtualization.filters import (
9
9
  ClusterFilterSet,
@@ -238,6 +238,8 @@ class VirtualMachineTestCase(FilterTestCases.FilterTestCase, FilterTestCases.Ten
238
238
  ("status", "status__id"),
239
239
  ("status", "status__name"),
240
240
  ("vcpus",),
241
+ ("vrfs", "vrfs__id"),
242
+ ("vrfs", "vrfs__rd"),
241
243
  )
242
244
 
243
245
  @classmethod
@@ -446,6 +448,13 @@ class VirtualMachineTestCase(FilterTestCases.FilterTestCase, FilterTestCases.Ten
446
448
  vms[0].tags.set(Tag.objects.get_for_model(VirtualMachine))
447
449
  vms[1].tags.set(Tag.objects.get_for_model(VirtualMachine)[:3])
448
450
 
451
+ vrfs = (
452
+ VRF.objects.create(name="VRF 1", rd="1:1"),
453
+ VRF.objects.create(name="VRF 2", rd="1:2"),
454
+ )
455
+ VRFDeviceAssignment.objects.create(virtual_machine=vms[0], vrf=vrfs[0])
456
+ VRFDeviceAssignment.objects.create(virtual_machine=vms[1], vrf=vrfs[1])
457
+
449
458
  def test_filters_generic(self):
450
459
  # Assign more than 2 different software versions to VirtualMachine before we test generic filters
451
460
  software_versions = list(SoftwareVersion.objects.all())
@@ -145,6 +145,7 @@ class VMInterfaceTestCase(TestCase): # TODO: change to BaseModelTestCase
145
145
  name="Int1", virtual_machine=self.virtualmachine, status=self.int_status
146
146
  )
147
147
  ips = list(IPAddress.objects.all()[:10])
148
+ self.assertEqual(len(ips), 10)
148
149
 
149
150
  # baseline (no vm_interface to ip address relationships exists)
150
151
  self.assertFalse(IPAddressToInterface.objects.filter(vm_interface=vm_interface).exists())
@@ -191,6 +191,8 @@ class VirtualMachineUIViewSet(NautobotUIViewSet):
191
191
  table_class=VRFDeviceAssignmentTable,
192
192
  table_filter="virtual_machine",
193
193
  exclude_columns=["related_object_type", "related_object_name"],
194
+ related_list_url_name="ipam:vrf_list",
195
+ related_field_name="virtual_machines",
194
196
  ),
195
197
  object_detail.ObjectFieldsPanel(
196
198
  weight=200,
@@ -262,6 +264,7 @@ class VirtualMachineUIViewSet(NautobotUIViewSet):
262
264
  detail=True,
263
265
  url_path="config-context",
264
266
  url_name="configcontext",
267
+ custom_view_base_action="view",
265
268
  custom_view_additional_permissions=["extras.view_configcontext"],
266
269
  )
267
270
  def config_context(self, request, pk):
@@ -288,7 +291,7 @@ class VirtualMachineUIViewSet(NautobotUIViewSet):
288
291
  "source_contexts": ConfigContext.objects.restrict(request.user, "view").get_for_object(instance),
289
292
  "format": data_format,
290
293
  "template": "extras/object_configcontext.html",
291
- "base_template": "virtualization/virtualmachine.html",
294
+ "base_template": "generic/object_retrieve.html",
292
295
  }
293
296
 
294
297
  return Response(context)
nautobot/vpn/factory.py CHANGED
@@ -1,3 +1,4 @@
1
+ from django.contrib.contenttypes.models import ContentType
1
2
  import factory
2
3
  import faker
3
4
 
@@ -8,9 +9,9 @@ from nautobot.core.factory import (
8
9
  random_instance,
9
10
  UniqueFaker,
10
11
  )
11
- from nautobot.dcim.models import Device, Interface
12
+ from nautobot.dcim.models import Interface
12
13
  from nautobot.extras.models import DynamicGroup, Role, SecretsGroup, Status
13
- from nautobot.ipam.models import IPAddress, Prefix
14
+ from nautobot.ipam.models import Prefix
14
15
  from nautobot.tenancy.models import Tenant
15
16
  from nautobot.vpn import choices, models
16
17
 
@@ -163,24 +164,26 @@ class VPNTunnelFactory(PrimaryModelFactory):
163
164
  class VPNTunnelEndpointFactory(PrimaryModelFactory):
164
165
  class Meta:
165
166
  model = models.VPNTunnelEndpoint
166
- exclude = ("has_device", "has_profile", "has_role", "has_tenant")
167
+ exclude = ("has_source_interface", "has_profile", "has_role", "has_tenant")
167
168
 
168
- has_device = NautobotBoolIterator()
169
- device = factory.Maybe("has_device", random_instance(Device), None)
169
+ has_source_interface = NautobotBoolIterator()
170
170
  source_interface = factory.Maybe(
171
- "has_device",
171
+ "has_source_interface",
172
172
  random_instance(
173
173
  lambda: Interface.objects.filter(vpn_tunnel_endpoints_src_int__isnull=True, device__isnull=False)
174
174
  ),
175
175
  None,
176
176
  )
177
- source_ipaddress = factory.Maybe("has_device", random_instance(IPAddress), None)
178
- source_fqdn = factory.Maybe("has_device", "", factory.Faker("word"))
179
- tunnel_interface = factory.Maybe(
180
- "has_device",
181
- random_instance(lambda: Interface.objects.filter(type="tunnel", vpn_tunnel_endpoints_tunnel__isnull=True)),
182
- None,
183
- )
177
+ source_fqdn = factory.Maybe("has_source_interface", "", factory.Faker("hostname"))
178
+
179
+ @factory.lazy_attribute
180
+ def tunnel_interface(self):
181
+ """Filter tunnel interfaces on the same device as source_interface."""
182
+ if self.has_source_interface:
183
+ qs = Interface.objects.filter(type="tunnel", device=self.source_interface.device)
184
+ return factory.random.randgen.choice(qs) if qs.exists() else None
185
+ return None
186
+
184
187
  has_profile = NautobotBoolIterator()
185
188
  vpn_profile = factory.Maybe("has_profile", random_instance(models.VPNProfile), None)
186
189
  has_role = NautobotBoolIterator()
@@ -198,7 +201,10 @@ class VPNTunnelEndpointFactory(PrimaryModelFactory):
198
201
  if extracted:
199
202
  self.protected_prefixes.set(extracted)
200
203
  else:
201
- self.protected_prefixes.set(get_random_instances(Prefix, minimum=0))
204
+ # TODO Investigate https://github.com/nautobot/nautobot/actions/runs/11019738391/job/30603271529
205
+ # to uncomment the line below.
206
+ # self.protected_prefixes.set(get_random_instances(Prefix))
207
+ self.protected_prefixes.set(get_random_instances(model_or_queryset_or_lambda=Prefix, maximum=1))
202
208
 
203
209
  @factory.post_generation
204
210
  def protected_prefixes_dg(self, create, extracted, **kwargs):
@@ -206,4 +212,8 @@ class VPNTunnelEndpointFactory(PrimaryModelFactory):
206
212
  if extracted:
207
213
  self.protected_prefixes_dg.set(extracted)
208
214
  else:
209
- self.protected_prefixes_dg.set(get_random_instances(DynamicGroup, minimum=0))
215
+ self.protected_prefixes_dg.set(
216
+ get_random_instances(
217
+ DynamicGroup.objects.filter(content_type=ContentType.objects.get_for_model(Prefix)), minimum=0
218
+ )
219
+ )
nautobot/vpn/filters.py CHANGED
@@ -193,6 +193,7 @@ class VPNTunnelEndpointFilterSet(TenancyModelFilterSetMixin, NautobotFilterSet):
193
193
  q = SearchFilter(
194
194
  filter_predicates={
195
195
  "source_fqdn": "icontains",
196
+ "device__name": "icontains",
196
197
  }
197
198
  )
198
199
  device = NaturalKeyOrPKMultipleChoiceFilter(
nautobot/vpn/forms.py CHANGED
@@ -446,6 +446,7 @@ class VPNTunnelEndpointForm(NautobotModelForm, TenancyForm): # pylint: disable=
446
446
  required=False,
447
447
  label="Protected Prefixes Dynamic Group",
448
448
  to_field_name="name",
449
+ query_params={"content_type": "ipam.prefix"},
449
450
  help_text="Protected Prefixes behind the tunnel endpoint.",
450
451
  )
451
452
 
@@ -19,7 +19,7 @@ class Migration(migrations.Migration):
19
19
  ("tenancy", "0009_update_all_charfields_max_length_to_255"),
20
20
  ("extras", "0130_jobresult_generate_log_entry_counts"),
21
21
  ("ipam", "0053_alter_vrfdeviceassignment_options_and_more"),
22
- ("dcim", "0079_device_name_data_migration"),
22
+ ("dcim", "0080_device_name_data_migration"),
23
23
  ]
24
24
 
25
25
  operations = [
nautobot/vpn/models.py CHANGED
@@ -497,11 +497,7 @@ class VPNTunnelEndpoint(PrimaryModel): # pylint: disable=too-many-ancestors
497
497
  def _name(self):
498
498
  """Dynamic name field."""
499
499
  if self.source_interface:
500
- if self.source_interface.parent:
501
- parent_intf = f"{self.source_interface.parent.name} {self.source_interface.name}"
502
- else:
503
- # Interface on a module that isn't installed in a device at present
504
- parent_intf = f"{self.source_interface.module} {self.source_interface.name}"
500
+ parent_intf = f"{self.source_interface.parent.name} {self.source_interface.name}"
505
501
  if self.source_ipaddress:
506
502
  return f"{parent_intf} ({self.source_ipaddress.address})"
507
503
  return parent_intf
@@ -516,12 +512,24 @@ class VPNTunnelEndpoint(PrimaryModel): # pylint: disable=too-many-ancestors
516
512
  raise ValidationError("Source IP Address and Source FQDN are mutually exclusive fields. Select only one.")
517
513
  if not any([self.source_interface, self.source_ipaddress, self.source_fqdn]):
518
514
  raise ValidationError("Source Interface or Source IP Address or Source FQDN Is required.")
519
- if self.source_interface and not self.source_interface.device:
515
+ if self.source_interface and not self.source_interface.parent:
520
516
  raise ValidationError("Source Interface must belong to a device.")
517
+ if (
518
+ self.source_ipaddress
519
+ and self.source_interface
520
+ and (self.source_ipaddress not in self.source_interface.ip_addresses.all())
521
+ ):
522
+ raise ValidationError("Source IP address must be assigned to Source Interface.")
523
+ if (
524
+ self.tunnel_interface
525
+ and self.source_interface
526
+ and (self.tunnel_interface not in self.source_interface.parent.all_interfaces)
527
+ ):
528
+ raise ValidationError("Tunnel Interface and Source Interface must be on the same device")
521
529
  return super().clean()
522
530
 
523
531
  def save(self, *args, **kwargs):
524
- if self.source_interface and not self.device:
525
- self.device = self.source_interface.device
532
+ if self.source_interface:
533
+ self.device = self.source_interface.parent
526
534
  self.name = self._name()
527
535
  super().save(*args, **kwargs)
nautobot/vpn/tables.py CHANGED
@@ -5,6 +5,7 @@ import django_tables2 as tables
5
5
 
6
6
  from nautobot.apps.tables import (
7
7
  BaseTable,
8
+ BooleanColumn,
8
9
  ButtonsColumn,
9
10
  LinkedCountColumn,
10
11
  RoleTableMixin,
@@ -25,17 +26,19 @@ class VPNProfileTable(RoleTableMixin, BaseTable):
25
26
  name = tables.Column(linkify=True)
26
27
  vpn_phase1_policy_count = LinkedCountColumn(
27
28
  viewname="vpn:vpnphase1policy_list",
28
- verbose_name="VPN Phase 1 Policy",
29
+ verbose_name="Phase 1 Policies",
29
30
  url_params={"vpn_profiles": "pk"},
30
31
  )
31
32
  vpn_phase2_policy_count = LinkedCountColumn(
32
33
  viewname="vpn:vpnphase2policy_list",
33
- verbose_name="VPN Phase 2 Policy",
34
+ verbose_name="Phase 2 Policies",
34
35
  url_params={"vpn_profiles": "pk"},
35
36
  )
36
37
  tenant = TenantColumn()
37
38
  actions = ButtonsColumn(models.VPNProfile)
38
39
  tags = TagColumn(url_name="vpn:vpnprofile_list")
40
+ keepalive_enabled = BooleanColumn()
41
+ nat_traversal = BooleanColumn()
39
42
 
40
43
  class Meta(BaseTable.Meta):
41
44
  """Meta attributes."""