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
@@ -0,0 +1,294 @@
1
+ """Test vpn forms."""
2
+
3
+ from django.contrib.contenttypes.models import ContentType
4
+
5
+ from nautobot.apps.testing import FormTestCases
6
+ from nautobot.dcim.models import Interface
7
+ from nautobot.extras.models import DynamicGroup, Role, SecretsGroup, Status
8
+ from nautobot.ipam.models import Prefix
9
+ from nautobot.tenancy.models import Tenant
10
+ from nautobot.vpn import choices, forms, models
11
+
12
+
13
+ class VPNProfileFormTest(FormTestCases.BaseFormTestCase):
14
+ """Test the VPNProfile form."""
15
+
16
+ form_class = forms.VPNProfileForm
17
+
18
+ def test_specifying_all_fields_success(self):
19
+ """Test specifying all fields."""
20
+ profile_role, _ = Role.objects.get_or_create(name="Default")
21
+ profile_role.content_types.add(ContentType.objects.get_for_model(models.VPNProfile))
22
+
23
+ data = {
24
+ "name": "test 1",
25
+ "description": "test value",
26
+ "role": profile_role,
27
+ "secrets_group": SecretsGroup.objects.first(),
28
+ "keepalive_enabled": True,
29
+ "keepalive_interval": 3,
30
+ "keepalive_retries": 5,
31
+ "nat_traversal": False,
32
+ "extra_options": {"option": "value"},
33
+ "tenant": Tenant.objects.first(),
34
+ }
35
+ form = self.form_class(data=data)
36
+ self.assertTrue(form.is_valid())
37
+ self.assertTrue(form.save())
38
+
39
+ def test_specifying_required_fields_success(self):
40
+ """Test specifying only required fields."""
41
+
42
+ data = {
43
+ "name": "test value",
44
+ }
45
+ form = self.form_class(data=data)
46
+ self.assertTrue(form.is_valid())
47
+ self.assertTrue(form.save())
48
+
49
+ def test_validate_name_is_required(self):
50
+ """Test that the name field is required."""
51
+
52
+ data = {
53
+ "description": "test value",
54
+ "keepalive_enabled": True,
55
+ "keepalive_interval": 3,
56
+ "keepalive_retries": 5,
57
+ "nat_traversal": False,
58
+ "extra_options": None,
59
+ }
60
+ form = self.form_class(data=data)
61
+ self.assertFalse(form.is_valid())
62
+ self.assertIn("This field is required.", form.errors["name"])
63
+
64
+
65
+ class VPNPhase1PolicyFormTest(FormTestCases.BaseFormTestCase):
66
+ """Test the VPNPhase1Policy form."""
67
+
68
+ form_class = forms.VPNPhase1PolicyForm
69
+
70
+ def test_specifying_all_fields_success(self):
71
+ """Test specifying all fields."""
72
+
73
+ data = {
74
+ "name": "test 1",
75
+ "description": "test value",
76
+ "ike_version": choices.IkeVersionChoices.ike_v2,
77
+ "aggressive_mode": False,
78
+ "encryption_algorithm": [choices.EncryptionAlgorithmChoices.aes_128_cbc],
79
+ "integrity_algorithm": [choices.IntegrityAlgorithmChoices.sha1],
80
+ "dh_group": [choices.DhGroupChoices.group5],
81
+ "lifetime_seconds": 10,
82
+ "lifetime_kb": 1024,
83
+ "authentication_method": choices.AuthenticationMethodChoices.rsa,
84
+ "tenant": Tenant.objects.first(),
85
+ }
86
+ form = self.form_class(data=data)
87
+ self.assertTrue(form.is_valid())
88
+ self.assertTrue(form.save())
89
+
90
+ def test_specifying_required_fields_success(self):
91
+ """Test specifying only required fields."""
92
+
93
+ data = {
94
+ "name": "test value",
95
+ }
96
+ form = self.form_class(data=data)
97
+ self.assertTrue(form.is_valid())
98
+ self.assertTrue(form.save())
99
+
100
+ def test_validate_name_is_required(self):
101
+ """Test that the name field is required."""
102
+
103
+ data = {
104
+ "description": "test value",
105
+ "ike_version": choices.IkeVersionChoices.ike_v2,
106
+ "aggressive_mode": False,
107
+ "encryption_algorithm": [choices.EncryptionAlgorithmChoices.aes_128_cbc],
108
+ "integrity_algorithm": [choices.IntegrityAlgorithmChoices.sha1],
109
+ "dh_group": [choices.DhGroupChoices.group5],
110
+ "lifetime_seconds": 10,
111
+ "lifetime_kb": 1024,
112
+ "authentication_method": choices.AuthenticationMethodChoices.rsa,
113
+ "tenant": Tenant.objects.first(),
114
+ }
115
+ form = self.form_class(data=data)
116
+ self.assertFalse(form.is_valid())
117
+ self.assertIn("This field is required.", form.errors["name"])
118
+
119
+
120
+ class VPNPhase2PolicyFormTest(FormTestCases.BaseFormTestCase):
121
+ """Test the VPNPhase2Policy form."""
122
+
123
+ form_class = forms.VPNPhase2PolicyForm
124
+
125
+ def test_specifying_all_fields_success(self):
126
+ """Test specifying all fields."""
127
+
128
+ data = {
129
+ "name": "test 1",
130
+ "description": "test value",
131
+ "encryption_algorithm": [choices.EncryptionAlgorithmChoices.aes_128_cbc],
132
+ "integrity_algorithm": [choices.IntegrityAlgorithmChoices.sha1],
133
+ "pfs_group": [choices.DhGroupChoices.group5],
134
+ "lifetime": 10,
135
+ "tenant": Tenant.objects.first(),
136
+ }
137
+ form = self.form_class(data=data)
138
+ self.assertTrue(form.is_valid())
139
+ self.assertTrue(form.save())
140
+
141
+ def test_specifying_required_fields_success(self):
142
+ """Test specifying only required fields."""
143
+
144
+ data = {
145
+ "name": "test value",
146
+ }
147
+ form = self.form_class(data=data)
148
+ self.assertTrue(form.is_valid())
149
+ self.assertTrue(form.save())
150
+
151
+ def test_validate_name_is_required(self):
152
+ """Test that the name field is required."""
153
+
154
+ data = {
155
+ "description": "test value",
156
+ "encryption_algorithm": [choices.EncryptionAlgorithmChoices.aes_128_cbc],
157
+ "integrity_algorithm": [choices.IntegrityAlgorithmChoices.sha1],
158
+ "pfs_group": [choices.DhGroupChoices.group5],
159
+ "lifetime": 10,
160
+ "tenant": Tenant.objects.first(),
161
+ }
162
+ form = self.form_class(data=data)
163
+ self.assertFalse(form.is_valid())
164
+ self.assertIn("This field is required.", form.errors["name"])
165
+
166
+
167
+ class VPNFormTest(FormTestCases.BaseFormTestCase):
168
+ """Test the VPN form."""
169
+
170
+ form_class = forms.VPNForm
171
+
172
+ def test_specifying_all_fields_success(self):
173
+ """Test specifying all fields."""
174
+ vpn_role, _ = Role.objects.get_or_create(name="Default")
175
+ vpn_role.content_types.add(ContentType.objects.get_for_model(models.VPN))
176
+
177
+ data = {
178
+ "name": "test 1",
179
+ "description": "test value",
180
+ "vpn_id": "test value",
181
+ "role": vpn_role,
182
+ "vpn_profile": models.VPNProfile.objects.first(),
183
+ "tenant": Tenant.objects.first(),
184
+ }
185
+ form = self.form_class(data=data)
186
+ self.assertTrue(form.is_valid())
187
+ self.assertTrue(form.save())
188
+
189
+ def test_specifying_required_fields_success(self):
190
+ """Test specifying only required fields."""
191
+
192
+ data = {
193
+ "name": "test value",
194
+ }
195
+ form = self.form_class(data=data)
196
+ self.assertTrue(form.is_valid())
197
+ self.assertTrue(form.save())
198
+
199
+ def test_validate_name_is_required(self):
200
+ """Test that the name field is required."""
201
+
202
+ data = {
203
+ "description": "test value",
204
+ "vpn_id": "test value",
205
+ "vpn_profile": models.VPNProfile.objects.first(),
206
+ "tenant": Tenant.objects.first(),
207
+ }
208
+ form = self.form_class(data=data)
209
+ self.assertFalse(form.is_valid())
210
+ self.assertIn("This field is required.", form.errors["name"])
211
+
212
+
213
+ class VPNTunnelFormTest(FormTestCases.BaseFormTestCase):
214
+ """Test the VPNTunnel form."""
215
+
216
+ form_class = forms.VPNTunnelForm
217
+
218
+ def test_specifying_all_fields_success(self):
219
+ """Test specifying all fields."""
220
+ endpoints = models.VPNTunnelEndpoint.objects.all()
221
+ tunnel_role, _ = Role.objects.get_or_create(name="Default")
222
+ tunnel_role.content_types.add(ContentType.objects.get_for_model(models.VPNTunnel))
223
+ data = {
224
+ "name": "test 1",
225
+ "description": "test value",
226
+ "vpn_profile": models.VPNProfile.objects.first(),
227
+ "vpn": models.VPN.objects.first(),
228
+ "tunnel_id": "test value",
229
+ "status": Status.objects.get(name="Active"),
230
+ "role": tunnel_role,
231
+ "encapsulation": choices.EncapsulationChoices.ipsec_tunnel,
232
+ "endpoint_a": endpoints[1],
233
+ "endpoint_z": endpoints[2],
234
+ "tenant": Tenant.objects.first(),
235
+ }
236
+ form = self.form_class(data=data)
237
+ self.assertTrue(form.is_valid())
238
+ self.assertTrue(form.save())
239
+
240
+ def test_specifying_required_fields_success(self):
241
+ """Test specifying only required fields."""
242
+
243
+ data = {
244
+ "name": "test value",
245
+ "status": Status.objects.get(name="Active"),
246
+ "encapsulation": choices.EncapsulationChoices.ipsec_tunnel,
247
+ }
248
+ form = self.form_class(data=data)
249
+ self.assertTrue(form.is_valid())
250
+ self.assertTrue(form.save())
251
+
252
+ def test_validate_name_is_required(self):
253
+ """Test that the name field is required."""
254
+
255
+ data = {
256
+ "description": "test value",
257
+ "vpn_profile": models.VPNProfile.objects.first(),
258
+ "vpn": models.VPN.objects.first(),
259
+ "tunnel_id": "test value",
260
+ "status": Status.objects.get(name="Active"),
261
+ "encapsulation": choices.EncapsulationChoices.ipsec_tunnel,
262
+ "tenant": Tenant.objects.first(),
263
+ }
264
+ form = self.form_class(data=data)
265
+ self.assertFalse(form.is_valid())
266
+ self.assertIn("This field is required.", form.errors["name"])
267
+
268
+
269
+ class VPNTunnelEndpointFormTest(FormTestCases.BaseFormTestCase):
270
+ """Test the VPNTunnelEndpoint form."""
271
+
272
+ form_class = forms.VPNTunnelEndpointForm
273
+
274
+ def test_specifying_all_fields_success(self):
275
+ """Test specifying all fields."""
276
+ interfaces = Interface.objects.filter(device__isnull=False, vpn_tunnel_endpoints_src_int__isnull=True)
277
+ data = {
278
+ "device": interfaces[0].device,
279
+ "source_interface": interfaces[0],
280
+ "vpn_profile": models.VPNProfile.objects.first(),
281
+ "protected_prefixes": [Prefix.objects.first()],
282
+ "protected_prefixes_dg": [DynamicGroup.objects.first()],
283
+ }
284
+ form = self.form_class(data=data)
285
+ self.assertTrue(form.is_valid())
286
+ self.assertTrue(form.save())
287
+
288
+ def test_specifying_required_fields_success(self):
289
+ """Test specifying only required fields."""
290
+
291
+ data = {"source_fqdn": "To Cloud"}
292
+ form = self.form_class(data=data)
293
+ self.assertTrue(form.is_valid())
294
+ self.assertTrue(form.save())
@@ -0,0 +1,97 @@
1
+ """Test vpn models."""
2
+
3
+ from django.core.exceptions import ValidationError
4
+
5
+ from nautobot.apps.testing import ModelTestCases
6
+ from nautobot.dcim.models import Interface
7
+ from nautobot.vpn import models
8
+
9
+
10
+ class TestVPNProfileModel(ModelTestCases.BaseModelTestCase):
11
+ """Test VPNProfile model."""
12
+
13
+ model = models.VPNProfile
14
+
15
+
16
+ class TestVPNPhase1PolicyModel(ModelTestCases.BaseModelTestCase):
17
+ """Test VPNPhase1Policy model."""
18
+
19
+ model = models.VPNPhase1Policy
20
+
21
+
22
+ class TestVPNPhase2PolicyModel(ModelTestCases.BaseModelTestCase):
23
+ """Test VPNPhase2Policy model."""
24
+
25
+ model = models.VPNPhase2Policy
26
+
27
+
28
+ class VPNProfilePhase1PolicyAssignmentModel(ModelTestCases.BaseModelTestCase):
29
+ """Test VPNPhase1Policy model."""
30
+
31
+ model = models.VPNProfilePhase1PolicyAssignment
32
+
33
+
34
+ class VPNProfilePhase2PolicyAssignmentModel(ModelTestCases.BaseModelTestCase):
35
+ """Test VPNPhase1Policy model."""
36
+
37
+ model = models.VPNProfilePhase2PolicyAssignment
38
+
39
+
40
+ class TestVPNModel(ModelTestCases.BaseModelTestCase):
41
+ """Test VPN model."""
42
+
43
+ model = models.VPN
44
+
45
+
46
+ class TestVPNTunnelModel(ModelTestCases.BaseModelTestCase):
47
+ """Test VPNTunnel model."""
48
+
49
+ model = models.VPNTunnel
50
+
51
+ def test_clean(self):
52
+ """Test custom clean method."""
53
+ endpoint = models.VPNTunnelEndpoint.objects.first()
54
+ tunnel = models.VPNTunnel(
55
+ endpoint_a=endpoint,
56
+ endpoint_z=endpoint,
57
+ )
58
+ with self.assertRaises(ValidationError):
59
+ tunnel.clean()
60
+
61
+
62
+ class TestVPNTunnelEndpointModel(ModelTestCases.BaseModelTestCase):
63
+ """Test VPNTunnelEndpoint model."""
64
+
65
+ model = models.VPNTunnelEndpoint
66
+
67
+ def test_name(self):
68
+ """Test dynamic name property."""
69
+ with self.subTest("Test name contains interface name and device name."):
70
+ endpoint = models.VPNTunnelEndpoint.objects.filter(source_interface__isnull=False).first()
71
+ self.assertIn(endpoint.source_interface.name, endpoint.name)
72
+ self.assertIn(endpoint.source_interface.device.name, endpoint.name)
73
+
74
+ with self.subTest("Test name contains interface name, device name and IP address."):
75
+ endpoint = models.VPNTunnelEndpoint.objects.filter(
76
+ source_interface__isnull=False, source_ipaddress__isnull=False
77
+ ).first()
78
+ self.assertIn(endpoint.source_interface.name, endpoint.name)
79
+ self.assertIn(endpoint.source_interface.device.name, endpoint.name)
80
+ self.assertIn(endpoint.source_ipaddress.host, endpoint.name)
81
+
82
+ with self.subTest("Test name is fqdn"):
83
+ endpoint = (
84
+ models.VPNTunnelEndpoint.objects.filter(source_fqdn__isnull=False).exclude(source_fqdn="").first()
85
+ )
86
+ self.assertEqual(endpoint.source_fqdn, endpoint.name)
87
+
88
+ def test_save(self):
89
+ """Test save adds dynamic fields."""
90
+ interface = Interface.objects.filter(vpn_tunnel_endpoints_src_int__isnull=True).first()
91
+ new_endpoint = models.VPNTunnelEndpoint.objects.create(source_interface=interface)
92
+
93
+ with self.subTest("Test device field is saved"):
94
+ self.assertEqual(new_endpoint.device, interface.device)
95
+
96
+ with self.subTest("Test name field is saved"):
97
+ self.assertEqual(new_endpoint.name, new_endpoint._name())
@@ -0,0 +1,281 @@
1
+ """Unit tests for vpn views."""
2
+
3
+ from django.test import override_settings
4
+ from django.urls import reverse
5
+
6
+ from nautobot.apps.testing import ViewTestCases
7
+ from nautobot.dcim.models import Interface
8
+ from nautobot.extras.models import Status
9
+ from nautobot.ipam.models import Prefix
10
+ from nautobot.vpn import choices, models
11
+
12
+
13
+ class VPNProfileViewTestCase(ViewTestCases.PrimaryObjectViewTestCase):
14
+ # pylint: disable=too-many-ancestors
15
+ """Test the VPNProfile views."""
16
+
17
+ model = models.VPNProfile
18
+
19
+ @classmethod
20
+ def setUpTestData(cls):
21
+ """Set up test data."""
22
+ super().setUpTestData()
23
+
24
+ cls.form_data = {
25
+ "name": "test value",
26
+ "description": "test value",
27
+ "keepalive_enabled": True,
28
+ "keepalive_interval": 10,
29
+ "keepalive_retries": 3,
30
+ "nat_traversal": False,
31
+ "extra_options": None,
32
+ # Management form fields required for the dynamic formset
33
+ "vpn_profile_phase1_policy_assignments-TOTAL_FORMS": "0",
34
+ "vpn_profile_phase1_policy_assignments-INITIAL_FORMS": "1",
35
+ "vpn_profile_phase1_policy_assignments-MIN_NUM_FORMS": "0",
36
+ "vpn_profile_phase1_policy_assignments-MAX_NUM_FORMS": "1000",
37
+ "vpn_profile_phase2_policy_assignments-TOTAL_FORMS": "0",
38
+ "vpn_profile_phase2_policy_assignments-INITIAL_FORMS": "1",
39
+ "vpn_profile_phase2_policy_assignments-MIN_NUM_FORMS": "0",
40
+ "vpn_profile_phase2_policy_assignments-MAX_NUM_FORMS": "1000",
41
+ }
42
+
43
+ cls.update_data = {
44
+ "name": "updated value",
45
+ "description": "updated value",
46
+ "keepalive_enabled": True,
47
+ "keepalive_interval": 15,
48
+ "keepalive_retries": 5,
49
+ "nat_traversal": False,
50
+ # Management form fields required for the dynamic formset
51
+ "vpn_profile_phase1_policy_assignments-TOTAL_FORMS": "0",
52
+ "vpn_profile_phase1_policy_assignments-INITIAL_FORMS": "1",
53
+ "vpn_profile_phase1_policy_assignments-MIN_NUM_FORMS": "0",
54
+ "vpn_profile_phase1_policy_assignments-MAX_NUM_FORMS": "1000",
55
+ "vpn_profile_phase2_policy_assignments-TOTAL_FORMS": "0",
56
+ "vpn_profile_phase2_policy_assignments-INITIAL_FORMS": "1",
57
+ "vpn_profile_phase2_policy_assignments-MIN_NUM_FORMS": "0",
58
+ "vpn_profile_phase2_policy_assignments-MAX_NUM_FORMS": "1000",
59
+ }
60
+
61
+ cls.bulk_edit_data = {
62
+ "description": "bulk updated value",
63
+ "keepalive_enabled": True,
64
+ "keepalive_interval": 30,
65
+ "keepalive_retries": 10,
66
+ "nat_traversal": True,
67
+ }
68
+
69
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
70
+ def test_profile_vpns(self):
71
+ self.add_permissions("vpn.view_vpn")
72
+ profile = models.VPNProfile.objects.filter(vpns__isnull=False).first()
73
+
74
+ url = reverse("vpn:vpnprofile_vpns", kwargs={"pk": profile.pk})
75
+ response = self.client.get(url)
76
+ self.assertHttpStatus(response, 200)
77
+
78
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
79
+ def test_profile_vpntunnels(self):
80
+ self.add_permissions("vpn.view_vpntunnel")
81
+ profile = models.VPNProfile.objects.filter(vpn_tunnels__isnull=False).first()
82
+
83
+ url = reverse("vpn:vpnprofile_vpntunnels", kwargs={"pk": profile.pk})
84
+ response = self.client.get(url)
85
+ self.assertHttpStatus(response, 200)
86
+
87
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
88
+ def test_profile_vpnendpoints(self):
89
+ self.add_permissions("vpn.view_vpntunnelendpoint")
90
+ profile = models.VPNProfile.objects.filter(vpn_tunnel_endpoints__isnull=False).first()
91
+
92
+ url = reverse("vpn:vpnprofile_vpnendpoints", kwargs={"pk": profile.pk})
93
+ response = self.client.get(url)
94
+ self.assertHttpStatus(response, 200)
95
+
96
+
97
+ class VPNPhase1PolicyViewTestCase(ViewTestCases.PrimaryObjectViewTestCase):
98
+ # pylint: disable=too-many-ancestors
99
+ """Test the VPNPhase1Policy views."""
100
+
101
+ model = models.VPNPhase1Policy
102
+
103
+ @classmethod
104
+ def setUpTestData(cls):
105
+ """Set up test data."""
106
+ super().setUpTestData()
107
+
108
+ cls.form_data = {
109
+ "name": "test value",
110
+ "description": "test value",
111
+ "ike_version": choices.IkeVersionChoices.ike_v2,
112
+ "aggressive_mode": False,
113
+ "encryption_algorithm": choices.EncryptionAlgorithmChoices.aes_128_cbc,
114
+ "integrity_algorithm": choices.IntegrityAlgorithmChoices.sha256,
115
+ "dh_group": choices.DhGroupChoices.group14,
116
+ "lifetime_seconds": 10,
117
+ "lifetime_kb": None,
118
+ "authentication_method": choices.AuthenticationMethodChoices.rsa,
119
+ }
120
+
121
+ cls.update_data = {
122
+ "name": "updated value",
123
+ "description": "updated value",
124
+ "ike_version": choices.IkeVersionChoices.ike_v2,
125
+ "aggressive_mode": False,
126
+ "encryption_algorithm": choices.EncryptionAlgorithmChoices.aes_192_cbc,
127
+ "integrity_algorithm": choices.IntegrityAlgorithmChoices.sha512,
128
+ "dh_group": choices.DhGroupChoices.group21,
129
+ "lifetime_seconds": 5,
130
+ "lifetime_kb": None,
131
+ "authentication_method": choices.AuthenticationMethodChoices.certificate,
132
+ }
133
+
134
+ cls.bulk_edit_data = {
135
+ "description": "bulk updated value",
136
+ "ike_version": choices.IkeVersionChoices.ike_v2,
137
+ "aggressive_mode": False,
138
+ "encryption_algorithm": choices.EncryptionAlgorithmChoices.aes_256_gcm,
139
+ "integrity_algorithm": choices.IntegrityAlgorithmChoices.sha1,
140
+ "dh_group": choices.DhGroupChoices.group5,
141
+ "lifetime_seconds": 15,
142
+ "lifetime_kb": None,
143
+ "authentication_method": choices.AuthenticationMethodChoices.ecdsa,
144
+ }
145
+
146
+
147
+ class VPNPhase2PolicyViewTestCase(ViewTestCases.PrimaryObjectViewTestCase):
148
+ # pylint: disable=too-many-ancestors
149
+ """Test the VPNPhase2Policy views."""
150
+
151
+ model = models.VPNPhase2Policy
152
+
153
+ @classmethod
154
+ def setUpTestData(cls):
155
+ """Set up test data."""
156
+ super().setUpTestData()
157
+
158
+ cls.form_data = {
159
+ "name": "test value",
160
+ "description": "test value",
161
+ "encryption_algorithm": choices.EncryptionAlgorithmChoices.aes_128_cbc,
162
+ "integrity_algorithm": choices.IntegrityAlgorithmChoices.sha256,
163
+ "pfs_group": choices.DhGroupChoices.group14,
164
+ "lifetime": 10,
165
+ }
166
+
167
+ cls.update_data = {
168
+ "name": "updated value",
169
+ "description": "updated value",
170
+ "encryption_algorithm": choices.EncryptionAlgorithmChoices.aes_192_cbc,
171
+ "integrity_algorithm": choices.IntegrityAlgorithmChoices.sha512,
172
+ "pfs_group": choices.DhGroupChoices.group21,
173
+ "lifetime": 5,
174
+ }
175
+
176
+ cls.bulk_edit_data = {
177
+ "description": "bulk updated value",
178
+ "encryption_algorithm": choices.EncryptionAlgorithmChoices.aes_256_gcm,
179
+ "integrity_algorithm": choices.IntegrityAlgorithmChoices.sha1,
180
+ "pfs_group": choices.DhGroupChoices.group5,
181
+ "lifetime": 15,
182
+ }
183
+
184
+
185
+ class VPNViewTestCase(ViewTestCases.PrimaryObjectViewTestCase):
186
+ # pylint: disable=too-many-ancestors
187
+ """Test the VPN views."""
188
+
189
+ model = models.VPN
190
+
191
+ @classmethod
192
+ def setUpTestData(cls):
193
+ """Set up test data."""
194
+ super().setUpTestData()
195
+
196
+ cls.form_data = {
197
+ "name": "test value",
198
+ "description": "test value",
199
+ "vpn_id": "test value",
200
+ "vpn_profile": models.VPNProfile.objects.first().pk,
201
+ }
202
+
203
+ cls.update_data = {
204
+ "name": "updated value",
205
+ "description": "updated value",
206
+ "vpn_id": "updated value",
207
+ "vpn_profile": models.VPNProfile.objects.last().pk,
208
+ }
209
+
210
+ cls.bulk_edit_data = {
211
+ "description": "bulk updated value",
212
+ }
213
+
214
+
215
+ class VPNTunnelViewTestCase(ViewTestCases.PrimaryObjectViewTestCase):
216
+ # pylint: disable=too-many-ancestors
217
+ """Test the VPNTunnel views."""
218
+
219
+ model = models.VPNTunnel
220
+
221
+ @classmethod
222
+ def setUpTestData(cls):
223
+ """Set up test data."""
224
+ super().setUpTestData()
225
+
226
+ cls.form_data = {
227
+ "name": "test value",
228
+ "description": "test value",
229
+ "vpn_profile": models.VPNProfile.objects.first().pk,
230
+ "vpn": models.VPN.objects.first().pk,
231
+ "tunnel_id": "test value",
232
+ "status": Status.objects.get(name="Active").pk,
233
+ "encapsulation": choices.EncapsulationChoices.ipsec_tunnel,
234
+ "endpoint_a": models.VPNTunnelEndpoint.objects.first().pk,
235
+ "endpoint_z": models.VPNTunnelEndpoint.objects.last().pk,
236
+ }
237
+
238
+ cls.update_data = {
239
+ "name": "test value",
240
+ "description": "updated value",
241
+ "vpn_profile": models.VPNProfile.objects.last().pk,
242
+ "vpn": models.VPN.objects.last().pk,
243
+ "tunnel_id": "updated value",
244
+ "status": Status.objects.get(name="Active").pk,
245
+ "encapsulation": choices.EncapsulationChoices.l2tp,
246
+ "endpoint_a": models.VPNTunnelEndpoint.objects.last().pk,
247
+ "endpoint_z": models.VPNTunnelEndpoint.objects.first().pk,
248
+ }
249
+
250
+ cls.bulk_edit_data = {
251
+ "description": "bulk updated value",
252
+ }
253
+
254
+
255
+ class VPNTunnelEndpointViewTestCase(ViewTestCases.PrimaryObjectViewTestCase):
256
+ # pylint: disable=too-many-ancestors
257
+ """Test the VPNTunnelEndpoint views."""
258
+
259
+ model = models.VPNTunnelEndpoint
260
+
261
+ @classmethod
262
+ def setUpTestData(cls):
263
+ """Set up test data."""
264
+ super().setUpTestData()
265
+
266
+ interfaces = Interface.objects.filter(device__isnull=False, vpn_tunnel_endpoints_src_int__isnull=True)
267
+
268
+ cls.form_data = {
269
+ "device": interfaces.first().device.pk,
270
+ "source_interface": interfaces.first().pk,
271
+ "vpn_profile": models.VPNProfile.objects.first().pk,
272
+ "protected_prefixes": [Prefix.objects.first().pk],
273
+ }
274
+
275
+ cls.update_data = {
276
+ "device": interfaces.last().device.pk,
277
+ "source_interface": interfaces.last().pk,
278
+ "protected_prefixes": [Prefix.objects.last().pk],
279
+ }
280
+
281
+ cls.bulk_edit_data = {"vpn_profile": models.VPNProfile.objects.last().pk}
nautobot/vpn/urls.py ADDED
@@ -0,0 +1,16 @@
1
+ from nautobot.core.views.routers import NautobotUIViewSetRouter
2
+
3
+ from . import views
4
+
5
+ app_name = "vpn"
6
+ router = NautobotUIViewSetRouter()
7
+
8
+ router.register("vpn-profiles", views.VPNProfileUIViewSet)
9
+ router.register("vpn-phase-1-policies", views.VPNPhase1PolicyUIViewSet)
10
+ router.register("vpn-phase-2-policies", views.VPNPhase2PolicyUIViewSet)
11
+ router.register("vpns", views.VPNUIViewSet)
12
+ router.register("vpn-tunnels", views.VPNTunnelUIViewSet)
13
+ router.register("vpn-tunnel-endpoints", views.VPNTunnelEndpointUIViewSet)
14
+
15
+ urlpatterns = []
16
+ urlpatterns += router.urls