nautobot 3.0.0a2__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 (659) hide show
  1. nautobot/apps/choices.py +4 -2
  2. nautobot/apps/filters.py +7 -9
  3. nautobot/apps/models.py +2 -2
  4. nautobot/apps/ui.py +13 -1
  5. nautobot/apps/utils.py +8 -0
  6. nautobot/circuits/filters.py +3 -2
  7. nautobot/circuits/navigation.py +3 -2
  8. nautobot/circuits/templates/circuits/circuit_create.html +3 -3
  9. nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
  10. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
  11. nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
  12. nautobot/circuits/tests/integration/test_circuit.py +10 -13
  13. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
  14. nautobot/circuits/views.py +6 -2
  15. nautobot/cloud/filters.py +1 -1
  16. nautobot/cloud/navigation.py +3 -2
  17. nautobot/core/api/schema.py +1 -1
  18. nautobot/core/api/serializers.py +6 -1
  19. nautobot/core/api/urls.py +2 -0
  20. nautobot/core/api/views.py +12 -0
  21. nautobot/core/apps/__init__.py +11 -10
  22. nautobot/core/celery/__init__.py +3 -5
  23. nautobot/core/checks.py +46 -0
  24. nautobot/core/choices.py +1 -1
  25. nautobot/core/cli/bootstrap_v3_to_v5.py +105 -13
  26. nautobot/core/cli/migrate_deprecated_templates.py +227 -0
  27. nautobot/core/constants.py +3 -0
  28. nautobot/core/context_processors.py +9 -1
  29. nautobot/core/filters.py +4 -0
  30. nautobot/core/forms/__init__.py +2 -0
  31. nautobot/core/forms/forms.py +1 -1
  32. nautobot/core/forms/widgets.py +21 -2
  33. nautobot/core/jobs/__init__.py +62 -3
  34. nautobot/core/jobs/groups.py +31 -1
  35. nautobot/core/management/commands/generate_test_data.py +28 -9
  36. nautobot/core/models/__init__.py +11 -0
  37. nautobot/core/models/generics.py +9 -1
  38. nautobot/core/models/tree_queries.py +10 -5
  39. nautobot/core/models/utils.py +1 -1
  40. nautobot/core/settings.py +35 -19
  41. nautobot/core/settings.yaml +17 -33
  42. nautobot/core/signals.py +12 -1
  43. nautobot/core/tables.py +13 -6
  44. nautobot/core/templates/40x.html +1 -1
  45. nautobot/core/templates/500.html +2 -2
  46. nautobot/core/templates/admin/base.html +1 -2
  47. nautobot/core/templates/admin/change_list.html +9 -12
  48. nautobot/core/templates/admin/config/config.html +12 -12
  49. nautobot/core/templates/admin/index.html +3 -3
  50. nautobot/core/templates/base_django.html +1 -2
  51. nautobot/core/templates/buttons/export.html +1 -1
  52. nautobot/core/templates/components/button/dropdown.html +5 -3
  53. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  54. nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
  55. nautobot/core/templates/components/panel/panel.html +3 -3
  56. nautobot/core/templates/components/tab/content_wrapper.html +6 -7
  57. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
  58. nautobot/core/templates/echarts/echarts.html +22 -9
  59. nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
  60. nautobot/core/templates/generic/object_bulk_create.html +6 -5
  61. nautobot/core/templates/generic/object_bulk_delete.html +1 -1
  62. nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
  63. nautobot/core/templates/generic/object_bulk_edit.html +1 -1
  64. nautobot/core/templates/generic/object_bulk_import.html +1 -1
  65. nautobot/core/templates/generic/object_bulk_remove.html +2 -2
  66. nautobot/core/templates/generic/object_bulk_update.html +5 -4
  67. nautobot/core/templates/generic/object_create.html +5 -4
  68. nautobot/core/templates/generic/object_delete.html +1 -1
  69. nautobot/core/templates/generic/object_detail.html +1 -1
  70. nautobot/core/templates/generic/object_edit.html +1 -1
  71. nautobot/core/templates/generic/object_import.html +2 -1
  72. nautobot/core/templates/generic/object_list.html +12 -4
  73. nautobot/core/templates/generic/object_notes.html +5 -3
  74. nautobot/core/templates/generic/object_retrieve.html +4 -5
  75. nautobot/core/templates/graphene/graphiql.html +7 -8
  76. nautobot/core/templates/home.html +1 -1
  77. nautobot/core/templates/import_success.html +2 -1
  78. nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
  79. nautobot/core/templates/inc/created_updated.html +7 -3
  80. nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
  81. nautobot/core/templates/inc/footer.html +3 -1
  82. nautobot/core/templates/inc/form_static_field.html +6 -0
  83. nautobot/core/templates/inc/header.html +11 -1
  84. nautobot/core/templates/inc/image_attachments.html +2 -1
  85. nautobot/core/templates/inc/media.html +14 -0
  86. nautobot/core/templates/inc/nav_menu.html +3 -9
  87. nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
  88. nautobot/core/templates/inc/search_panel.html +4 -4
  89. nautobot/core/templates/login.html +4 -2
  90. nautobot/core/templates/nautobot_config.py.j2 +6 -11
  91. nautobot/core/templates/redoc_ui.html +7 -0
  92. nautobot/core/templates/rest_framework/api.html +103 -2
  93. nautobot/core/templates/search.html +1 -1
  94. nautobot/core/templates/swagger_ui.html +17 -3
  95. nautobot/core/templates/system_jobs/import_objects.html +1 -2
  96. nautobot/core/templates/utilities/confirmation_form.html +2 -2
  97. nautobot/core/templates/utilities/obj_table.html +10 -2
  98. nautobot/core/templates/utilities/render_field.html +7 -7
  99. nautobot/core/templates/utilities/render_jinja2.html +2 -2
  100. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +37 -4
  101. nautobot/core/templates/utilities/theme_preview.html +19 -3
  102. nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
  103. nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
  104. nautobot/core/templatetags/helpers.py +76 -18
  105. nautobot/core/testing/api.py +68 -9
  106. nautobot/core/testing/filters.py +0 -23
  107. nautobot/core/testing/integration.py +41 -17
  108. nautobot/core/testing/mixins.py +2 -0
  109. nautobot/core/testing/utils.py +18 -4
  110. nautobot/core/testing/views.py +104 -13
  111. nautobot/core/tests/integration/test_app_home.py +34 -30
  112. nautobot/core/tests/integration/test_app_navbar.py +3 -0
  113. nautobot/core/tests/integration/test_filters.py +48 -11
  114. nautobot/core/tests/integration/test_theme.py +22 -21
  115. nautobot/core/tests/nautobot_config.py +3 -0
  116. nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
  117. nautobot/core/tests/runner.py +8 -1
  118. nautobot/core/tests/test_api.py +5 -3
  119. nautobot/core/tests/test_breadcrumbs.py +27 -28
  120. nautobot/core/tests/test_checks.py +28 -0
  121. nautobot/core/tests/test_cli.py +40 -0
  122. nautobot/core/tests/test_config.py +2 -1
  123. nautobot/core/tests/test_forms.py +55 -13
  124. nautobot/core/tests/test_jobs.py +144 -3
  125. nautobot/core/tests/test_nautobot_server.py +2 -0
  126. nautobot/core/tests/test_navigations.py +76 -1
  127. nautobot/core/tests/test_patch_social_django.py +42 -0
  128. nautobot/core/tests/test_renderers.py +59 -0
  129. nautobot/core/tests/test_settings_schema.py +1 -0
  130. nautobot/core/tests/test_tables.py +3 -1
  131. nautobot/core/tests/test_templatetags_helpers.py +62 -13
  132. nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
  133. nautobot/core/tests/test_titles.py +0 -16
  134. nautobot/core/tests/test_tree_queries.py +14 -1
  135. nautobot/core/tests/test_ui.py +123 -4
  136. nautobot/core/tests/test_utils.py +72 -5
  137. nautobot/core/tests/test_views.py +159 -31
  138. nautobot/core/ui/breadcrumbs.py +70 -29
  139. nautobot/core/ui/bulk_buttons.py +1 -1
  140. nautobot/core/ui/choices.py +143 -27
  141. nautobot/core/ui/constants.py +76 -12
  142. nautobot/core/ui/echarts.py +15 -20
  143. nautobot/core/ui/object_detail.py +143 -55
  144. nautobot/core/ui/titles.py +3 -6
  145. nautobot/core/urls.py +20 -9
  146. nautobot/core/utils/cache.py +2 -1
  147. nautobot/core/utils/filtering.py +28 -18
  148. nautobot/core/utils/lookup.py +49 -8
  149. nautobot/core/utils/module_loading.py +21 -0
  150. nautobot/core/utils/patch_social_django.py +128 -0
  151. nautobot/core/views/__init__.py +38 -1
  152. nautobot/core/views/generic.py +3 -3
  153. nautobot/core/views/mixins.py +45 -22
  154. nautobot/core/views/renderers.py +4 -3
  155. nautobot/core/views/viewsets.py +2 -1
  156. nautobot/data_validation/apps.py +1 -5
  157. nautobot/data_validation/custom_validators.py +4 -4
  158. nautobot/data_validation/filters.py +1 -1
  159. nautobot/data_validation/forms.py +40 -0
  160. nautobot/data_validation/migrations/0001_initial.py +0 -7
  161. nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -14
  162. nautobot/data_validation/models.py +16 -7
  163. nautobot/data_validation/navigation.py +8 -1
  164. nautobot/data_validation/tables.py +12 -5
  165. nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
  166. nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
  167. nautobot/data_validation/tests/__init__.py +2 -2
  168. nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
  169. nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
  170. nautobot/data_validation/tests/test_filters.py +8 -6
  171. nautobot/data_validation/tests/test_models.py +15 -0
  172. nautobot/data_validation/tests/test_views.py +190 -32
  173. nautobot/data_validation/urls.py +2 -5
  174. nautobot/data_validation/views.py +73 -40
  175. nautobot/dcim/api/serializers.py +3 -13
  176. nautobot/dcim/apps.py +4 -0
  177. nautobot/dcim/choices.py +65 -0
  178. nautobot/dcim/constants.py +7 -0
  179. nautobot/dcim/custom_validators.py +84 -0
  180. nautobot/dcim/factory.py +1 -1
  181. nautobot/dcim/filter_mixins.py +353 -4
  182. nautobot/dcim/{filters/__init__.py → filters.py} +15 -36
  183. nautobot/dcim/forms.py +90 -4
  184. nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
  185. nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
  186. nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
  187. nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
  188. nautobot/dcim/migrations/0079_remove_device_location_tenant_name_uniqueness.py +16 -0
  189. nautobot/dcim/migrations/0080_device_name_data_migration.py +59 -0
  190. nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
  191. nautobot/dcim/models/device_component_templates.py +33 -1
  192. nautobot/dcim/models/device_components.py +98 -64
  193. nautobot/dcim/models/devices.py +30 -20
  194. nautobot/dcim/navigation.py +7 -6
  195. nautobot/dcim/tables/devices.py +18 -0
  196. nautobot/dcim/tables/devicetypes.py +8 -1
  197. nautobot/dcim/tables/racks.py +0 -2
  198. nautobot/dcim/tables/template_code.py +15 -15
  199. nautobot/dcim/templates/dcim/cable_connect.html +28 -112
  200. nautobot/dcim/templates/dcim/cable_trace.html +0 -4
  201. nautobot/dcim/templates/dcim/{cable_edit.html → cable_update.html} +1 -1
  202. nautobot/dcim/templates/dcim/consoleport.html +7 -6
  203. nautobot/dcim/templates/dcim/consoleserverport.html +7 -6
  204. nautobot/dcim/templates/dcim/device/config.html +2 -2
  205. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
  206. nautobot/dcim/templates/dcim/device/status.html +8 -8
  207. nautobot/dcim/templates/dcim/device.html +1 -1
  208. nautobot/dcim/templates/dcim/device_component_add.html +2 -2
  209. nautobot/dcim/templates/dcim/device_create.html +5 -3
  210. nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
  211. nautobot/dcim/templates/dcim/device_list.html +73 -10
  212. nautobot/dcim/templates/dcim/devicebay.html +1 -1
  213. nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
  214. nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
  215. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
  216. nautobot/dcim/templates/dcim/frontport.html +10 -9
  217. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  218. nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
  219. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
  220. nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
  221. nautobot/dcim/templates/dcim/interface.html +35 -7
  222. nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
  223. nautobot/dcim/templates/dcim/interface_edit.html +2 -0
  224. nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
  225. nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
  226. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  227. nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
  228. nautobot/dcim/templates/dcim/module/base.html +49 -9
  229. nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
  230. nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
  231. nautobot/dcim/templates/dcim/module_frontports.html +1 -1
  232. nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
  233. nautobot/dcim/templates/dcim/module_list.html +57 -8
  234. nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
  235. nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
  236. nautobot/dcim/templates/dcim/module_powerports.html +1 -1
  237. nautobot/dcim/templates/dcim/module_rearports.html +1 -1
  238. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  239. nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
  240. nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
  241. nautobot/dcim/templates/dcim/platform_create.html +1 -1
  242. nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
  243. nautobot/dcim/templates/dcim/powerport.html +6 -5
  244. nautobot/dcim/templates/dcim/rack_elevation_list.html +17 -5
  245. nautobot/dcim/templates/dcim/rack_retrieve.html +22 -15
  246. nautobot/dcim/templates/dcim/rearport.html +8 -7
  247. nautobot/dcim/templates/dcim/trace/cable.html +1 -1
  248. nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
  249. nautobot/dcim/templates/dcim/virtualchassis_update.html +15 -7
  250. nautobot/dcim/tests/integration/test_controller.py +4 -6
  251. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
  252. nautobot/dcim/tests/integration/test_create_device.py +0 -2
  253. nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
  254. nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
  255. nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
  256. nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
  257. nautobot/dcim/tests/test_api.py +194 -6
  258. nautobot/dcim/tests/test_custom_validators.py +229 -0
  259. nautobot/dcim/tests/test_filters.py +55 -7
  260. nautobot/dcim/tests/test_forms.py +110 -8
  261. nautobot/dcim/tests/test_graphql.py +44 -1
  262. nautobot/dcim/tests/test_models.py +328 -4
  263. nautobot/dcim/tests/test_tables.py +160 -0
  264. nautobot/dcim/tests/test_views.py +132 -29
  265. nautobot/dcim/urls.py +64 -21
  266. nautobot/dcim/utils.py +3 -3
  267. nautobot/dcim/views.py +777 -397
  268. nautobot/extras/api/views.py +60 -45
  269. nautobot/extras/choices.py +2 -13
  270. nautobot/extras/datasources/git.py +3 -1
  271. nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
  272. nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
  273. nautobot/extras/{filters/__init__.py → filters.py} +33 -48
  274. nautobot/extras/forms/forms.py +14 -15
  275. nautobot/extras/forms/mixins.py +0 -41
  276. nautobot/extras/jobs.py +2 -0
  277. nautobot/extras/jobs_ui.py +4 -3
  278. nautobot/extras/management/__init__.py +11 -0
  279. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
  280. nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
  281. nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
  282. nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
  283. nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
  284. nautobot/extras/models/__init__.py +1 -2
  285. nautobot/extras/models/approvals.py +33 -14
  286. nautobot/extras/models/change_logging.py +4 -0
  287. nautobot/extras/models/contacts.py +2 -0
  288. nautobot/extras/models/groups.py +44 -5
  289. nautobot/extras/models/jobs.py +60 -4
  290. nautobot/extras/models/mixins.py +28 -0
  291. nautobot/extras/models/models.py +23 -2
  292. nautobot/extras/models/secrets.py +1 -0
  293. nautobot/extras/models/statuses.py +0 -15
  294. nautobot/extras/navigation.py +13 -9
  295. nautobot/extras/plugins/__init__.py +33 -55
  296. nautobot/extras/plugins/marketplace_manifest.yml +49 -1
  297. nautobot/extras/plugins/tables.py +3 -3
  298. nautobot/extras/plugins/urls.py +2 -21
  299. nautobot/extras/plugins/utils.py +1 -33
  300. nautobot/extras/plugins/views.py +0 -9
  301. nautobot/extras/querysets.py +8 -0
  302. nautobot/extras/signals.py +20 -19
  303. nautobot/extras/tables.py +64 -68
  304. nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
  305. nautobot/extras/templates/extras/approval_dashboard.html +7 -5
  306. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
  307. nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
  308. nautobot/extras/templates/extras/configcontext_update.html +1 -0
  309. nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
  310. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
  311. nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
  312. nautobot/extras/templates/extras/gitrepository_result.html +0 -2
  313. nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
  314. nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
  315. nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
  316. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
  317. nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
  318. nautobot/extras/templates/extras/inc/json_format.html +10 -3
  319. nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
  320. nautobot/extras/templates/extras/job.html +29 -12
  321. nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
  322. nautobot/extras/templates/extras/job_edit.html +52 -46
  323. nautobot/extras/templates/extras/job_list.html +29 -25
  324. nautobot/extras/templates/extras/marketplace.html +5 -9
  325. nautobot/extras/templates/extras/object_configcontext.html +1 -1
  326. nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
  327. nautobot/extras/templates/extras/objectchange_retrieve.html +19 -39
  328. nautobot/extras/templates/extras/plugin_detail.html +29 -24
  329. nautobot/extras/templates/extras/plugins_list.html +16 -26
  330. nautobot/extras/templates/extras/role_retrieve.html +64 -0
  331. nautobot/extras/templates/extras/scheduledjob.html +4 -2
  332. nautobot/extras/templates/extras/secret_create.html +1 -1
  333. nautobot/extras/templatetags/custom_links.py +12 -12
  334. nautobot/extras/templatetags/job_buttons.py +14 -12
  335. nautobot/extras/test_jobs/invalid_import.py +9 -0
  336. nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
  337. nautobot/extras/test_jobs/missing_import.py +11 -0
  338. nautobot/extras/tests/integration/test_computedfields.py +8 -9
  339. nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
  340. nautobot/extras/tests/integration/test_customfields.py +9 -10
  341. nautobot/extras/tests/integration/test_dynamicgroups.py +12 -9
  342. nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
  343. nautobot/extras/tests/integration/test_plugins.py +18 -6
  344. nautobot/extras/tests/integration/test_relationships.py +0 -2
  345. nautobot/extras/tests/test_api.py +90 -18
  346. nautobot/extras/tests/test_approvals.py +38 -38
  347. nautobot/extras/tests/test_changelog.py +59 -5
  348. nautobot/extras/tests/test_customfields.py +22 -13
  349. nautobot/extras/tests/test_customfields_filters.py +479 -0
  350. nautobot/extras/tests/test_dynamicgroups.py +39 -1
  351. nautobot/extras/tests/test_filters.py +57 -22
  352. nautobot/extras/tests/test_forms.py +18 -21
  353. nautobot/extras/tests/test_jobs.py +25 -4
  354. nautobot/extras/tests/test_migrations.py +1 -0
  355. nautobot/extras/tests/test_models.py +51 -33
  356. nautobot/extras/tests/test_plugins.py +36 -10
  357. nautobot/extras/tests/test_utils.py +3 -4
  358. nautobot/extras/tests/test_views.py +52 -112
  359. nautobot/extras/urls.py +0 -14
  360. nautobot/extras/views.py +164 -71
  361. nautobot/ipam/factory.py +7 -0
  362. nautobot/ipam/filter_mixins.py +38 -0
  363. nautobot/ipam/filters.py +53 -38
  364. nautobot/ipam/formfields.py +1 -1
  365. nautobot/ipam/forms.py +6 -3
  366. nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
  367. nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
  368. nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
  369. nautobot/ipam/models.py +29 -2
  370. nautobot/ipam/navigation.py +3 -2
  371. nautobot/ipam/signals.py +71 -0
  372. nautobot/ipam/tables.py +19 -6
  373. nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
  374. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
  375. nautobot/ipam/templates/ipam/ipaddress.html +14 -0
  376. nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
  377. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
  378. nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
  379. nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
  380. nautobot/ipam/templates/ipam/namespace_update.html +15 -0
  381. nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
  382. nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
  383. nautobot/ipam/templates/ipam/prefix_list.html +14 -13
  384. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  385. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  386. nautobot/ipam/tests/migration/test_migrations.py +89 -0
  387. nautobot/ipam/tests/test_api.py +13 -6
  388. nautobot/ipam/tests/test_filters.py +36 -1
  389. nautobot/ipam/tests/test_forms.py +1 -1
  390. nautobot/ipam/tests/test_models.py +44 -2
  391. nautobot/ipam/tests/test_tables.py +1 -2
  392. nautobot/ipam/tests/test_utils.py +1 -1
  393. nautobot/ipam/tests/test_views.py +13 -14
  394. nautobot/ipam/ui.py +0 -17
  395. nautobot/ipam/utils/migrations.py +16 -2
  396. nautobot/ipam/utils/testing.py +9 -3
  397. nautobot/ipam/views.py +53 -11
  398. nautobot/load_balancers/__init__.py +0 -0
  399. nautobot/load_balancers/api/__init__.py +1 -0
  400. nautobot/load_balancers/api/serializers.py +75 -0
  401. nautobot/load_balancers/api/urls.py +23 -0
  402. nautobot/load_balancers/api/views.py +61 -0
  403. nautobot/load_balancers/apps.py +17 -0
  404. nautobot/load_balancers/choices.py +167 -0
  405. nautobot/load_balancers/filters.py +225 -0
  406. nautobot/load_balancers/forms.py +532 -0
  407. nautobot/load_balancers/management/commands/__init__.py +0 -0
  408. nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
  409. nautobot/load_balancers/migrations/0001_initial.py +465 -0
  410. nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
  411. nautobot/load_balancers/migrations/__init__.py +0 -0
  412. nautobot/load_balancers/models.py +423 -0
  413. nautobot/load_balancers/navigation.py +80 -0
  414. nautobot/load_balancers/tables.py +255 -0
  415. nautobot/load_balancers/tests/__init__.py +474 -0
  416. nautobot/load_balancers/tests/test_api.py +353 -0
  417. nautobot/load_balancers/tests/test_filters.py +134 -0
  418. nautobot/load_balancers/tests/test_forms.py +266 -0
  419. nautobot/load_balancers/tests/test_models.py +195 -0
  420. nautobot/load_balancers/tests/test_views.py +229 -0
  421. nautobot/load_balancers/urls.py +17 -0
  422. nautobot/load_balancers/views.py +248 -0
  423. nautobot/project-static/dist/css/github-dark.min.css +10 -0
  424. nautobot/project-static/dist/css/github.min.css +10 -0
  425. nautobot/project-static/dist/css/nautobot.css +1 -11
  426. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  427. nautobot/project-static/dist/js/libraries.js +1 -1
  428. nautobot/project-static/dist/js/libraries.js.map +1 -1
  429. nautobot/project-static/dist/js/nautobot.js +1 -1
  430. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  431. nautobot/project-static/js/cabletrace.js +1 -1
  432. nautobot/project-static/js/forms.js +13 -0
  433. nautobot/project-static/js/interface_filtering.js +20 -16
  434. nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
  435. nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
  436. nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
  437. nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
  438. nautobot/project-static/nautobot-icons/cloud.svg +1 -1
  439. nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
  440. nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
  441. nautobot/project-static/nautobot-icons/elements.svg +1 -1
  442. nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
  443. nautobot/project-static/nautobot-icons/hammer.svg +1 -1
  444. nautobot/project-static/nautobot-icons/organization.svg +3 -0
  445. nautobot/project-static/nautobot-icons/secrets.svg +1 -1
  446. nautobot/project-static/nautobot-icons/security.svg +3 -0
  447. nautobot/project-static/nautobot-icons/server.svg +1 -1
  448. nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
  449. nautobot/project-static/nautobot-icons/star.svg +1 -1
  450. nautobot/tenancy/api/serializers.py +1 -0
  451. nautobot/tenancy/api/views.py +2 -1
  452. nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
  453. nautobot/tenancy/navigation.py +3 -1
  454. nautobot/tenancy/tests/test_filters.py +0 -2
  455. nautobot/tenancy/views.py +2 -1
  456. nautobot/ui/package-lock.json +87 -4
  457. nautobot/ui/package.json +2 -1
  458. nautobot/ui/src/js/collapse.js +3 -3
  459. nautobot/ui/src/js/nautobot.js +16 -1
  460. nautobot/ui/src/js/select2.js +53 -2
  461. nautobot/ui/src/scss/colors.scss +1 -1
  462. nautobot/ui/src/scss/nautobot.scss +112 -30
  463. nautobot/ui/webpack.config.js +13 -0
  464. nautobot/users/templates/users/preferences.html +11 -2
  465. nautobot/users/templates/users/profile.html +45 -12
  466. nautobot/users/templates/users/sessionkey_delete.html +1 -1
  467. nautobot/users/tests/test_api.py +4 -0
  468. nautobot/users/views.py +4 -2
  469. nautobot/virtualization/filters.py +6 -1
  470. nautobot/virtualization/models.py +1 -68
  471. nautobot/virtualization/navigation.py +3 -2
  472. nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
  473. nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
  474. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
  475. nautobot/virtualization/tests/test_api.py +3 -0
  476. nautobot/virtualization/tests/test_filters.py +10 -1
  477. nautobot/virtualization/tests/test_models.py +45 -4
  478. nautobot/virtualization/views.py +4 -1
  479. nautobot/vpn/__init__.py +0 -0
  480. nautobot/vpn/api/serializers.py +113 -0
  481. nautobot/vpn/api/urls.py +19 -0
  482. nautobot/vpn/api/views.py +70 -0
  483. nautobot/vpn/apps.py +8 -0
  484. nautobot/vpn/choices.py +171 -0
  485. nautobot/vpn/factory.py +219 -0
  486. nautobot/vpn/filters.py +234 -0
  487. nautobot/vpn/forms.py +487 -0
  488. nautobot/vpn/homepage.py +19 -0
  489. nautobot/vpn/migrations/0001_initial.py +541 -0
  490. nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
  491. nautobot/vpn/migrations/__init__.py +0 -0
  492. nautobot/vpn/models.py +535 -0
  493. nautobot/vpn/navigation.py +98 -0
  494. nautobot/vpn/tables.py +383 -0
  495. nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
  496. nautobot/vpn/tests/__init__.py +0 -0
  497. nautobot/vpn/tests/test_api.py +336 -0
  498. nautobot/vpn/tests/test_filters.py +139 -0
  499. nautobot/vpn/tests/test_forms.py +293 -0
  500. nautobot/vpn/tests/test_models.py +147 -0
  501. nautobot/vpn/tests/test_views.py +300 -0
  502. nautobot/vpn/urls.py +16 -0
  503. nautobot/vpn/views.py +495 -0
  504. nautobot/wireless/navigation.py +3 -2
  505. nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
  506. nautobot/wireless/tests/test_api.py +1 -1
  507. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +15 -15
  508. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +514 -572
  509. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +1 -0
  510. nautobot/circuits/templates/circuits/circuit.html +0 -2
  511. nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
  512. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
  513. nautobot/circuits/templates/circuits/circuit_update.html +0 -1
  514. nautobot/circuits/templates/circuits/circuittermination.html +0 -2
  515. nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
  516. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
  517. nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
  518. nautobot/circuits/templates/circuits/circuittype.html +0 -2
  519. nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
  520. nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
  521. nautobot/circuits/templates/circuits/provider.html +0 -2
  522. nautobot/circuits/templates/circuits/provider_edit.html +0 -2
  523. nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
  524. nautobot/circuits/templates/circuits/provider_update.html +0 -1
  525. nautobot/circuits/templates/circuits/providernetwork.html +0 -2
  526. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
  527. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
  528. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
  529. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
  530. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
  531. nautobot/core/templates/buttons/import.html +0 -9
  532. nautobot/data_validation/template_content.py +0 -42
  533. nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
  534. nautobot/dcim/filters/mixins.py +0 -354
  535. nautobot/dcim/templates/dcim/controller/base.html +0 -2
  536. nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
  537. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
  538. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
  539. nautobot/dcim/templates/dcim/device/base.html +0 -2
  540. nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
  541. nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
  542. nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
  543. nautobot/dcim/templates/dcim/device/frontports.html +0 -2
  544. nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
  545. nautobot/dcim/templates/dcim/device/inventory.html +0 -2
  546. nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
  547. nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
  548. nautobot/dcim/templates/dcim/device/powerports.html +0 -2
  549. nautobot/dcim/templates/dcim/device/rearports.html +0 -2
  550. nautobot/dcim/templates/dcim/device/wireless.html +0 -2
  551. nautobot/dcim/templates/dcim/device_component.html +0 -2
  552. nautobot/dcim/templates/dcim/device_edit.html +0 -2
  553. nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
  554. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
  555. nautobot/dcim/templates/dcim/devicetype.html +0 -2
  556. nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
  557. nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
  558. nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
  559. nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
  560. nautobot/dcim/templates/dcim/location.html +0 -2
  561. nautobot/dcim/templates/dcim/location_edit.html +0 -2
  562. nautobot/dcim/templates/dcim/location_retrieve.html +0 -243
  563. nautobot/dcim/templates/dcim/locationtype.html +0 -2
  564. nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
  565. nautobot/dcim/templates/dcim/manufacturer.html +0 -2
  566. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
  567. nautobot/dcim/templates/dcim/platform.html +0 -2
  568. nautobot/dcim/templates/dcim/powerfeed.html +0 -2
  569. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
  570. nautobot/dcim/templates/dcim/powerpanel.html +0 -2
  571. nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
  572. nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
  573. nautobot/dcim/templates/dcim/rack.html +0 -2
  574. nautobot/dcim/templates/dcim/rack_edit.html +0 -2
  575. nautobot/dcim/templates/dcim/rackgroup.html +0 -2
  576. nautobot/dcim/templates/dcim/rackreservation.html +0 -2
  577. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
  578. nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
  579. nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
  580. nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
  581. nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
  582. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
  583. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
  584. nautobot/dcim/ui.py +0 -29
  585. nautobot/extras/templates/extras/computedfield.html +0 -2
  586. nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
  587. nautobot/extras/templates/extras/configcontext.html +0 -2
  588. nautobot/extras/templates/extras/configcontext_edit.html +0 -2
  589. nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
  590. nautobot/extras/templates/extras/configcontextschema.html +0 -2
  591. nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
  592. nautobot/extras/templates/extras/contact_retrieve.html +0 -2
  593. nautobot/extras/templates/extras/customfield.html +0 -2
  594. nautobot/extras/templates/extras/customfield_edit.html +0 -2
  595. nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
  596. nautobot/extras/templates/extras/customlink.html +0 -2
  597. nautobot/extras/templates/extras/dynamicgroup.html +0 -2
  598. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
  599. nautobot/extras/templates/extras/exporttemplate.html +0 -2
  600. nautobot/extras/templates/extras/gitrepository.html +0 -2
  601. nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
  602. nautobot/extras/templates/extras/graphqlquery.html +0 -2
  603. nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
  604. nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -97
  605. nautobot/extras/templates/extras/job_detail.html +0 -2
  606. nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
  607. nautobot/extras/templates/extras/jobhook.html +0 -2
  608. nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
  609. nautobot/extras/templates/extras/jobresult.html +0 -2
  610. nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
  611. nautobot/extras/templates/extras/note.html +0 -2
  612. nautobot/extras/templates/extras/note_retrieve.html +0 -1
  613. nautobot/extras/templates/extras/object_changelog.html +0 -2
  614. nautobot/extras/templates/extras/object_notes.html +0 -2
  615. nautobot/extras/templates/extras/objectchange.html +0 -2
  616. nautobot/extras/templates/extras/objectchange_list.html +0 -3
  617. nautobot/extras/templates/extras/relationship.html +0 -1
  618. nautobot/extras/templates/extras/secret.html +0 -1
  619. nautobot/extras/templates/extras/secret_edit.html +0 -1
  620. nautobot/extras/templates/extras/secretsgroup.html +0 -2
  621. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
  622. nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
  623. nautobot/extras/templates/extras/status.html +0 -2
  624. nautobot/extras/templates/extras/tag.html +0 -2
  625. nautobot/extras/templates/extras/tag_edit.html +0 -2
  626. nautobot/extras/templates/extras/tag_retrieve.html +0 -2
  627. nautobot/extras/templates/extras/team_retrieve.html +0 -2
  628. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
  629. nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
  630. nautobot/ipam/templates/ipam/prefix.html +0 -2
  631. nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
  632. nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
  633. nautobot/ipam/templates/ipam/rir.html +0 -2
  634. nautobot/ipam/templates/ipam/routetarget.html +0 -1
  635. nautobot/ipam/templates/ipam/service.html +0 -2
  636. nautobot/ipam/templates/ipam/service_edit.html +0 -2
  637. nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
  638. nautobot/ipam/templates/ipam/vlan.html +0 -2
  639. nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
  640. nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
  641. nautobot/ipam/templates/ipam/vlangroup.html +0 -2
  642. nautobot/ipam/templates/ipam/vrf.html +0 -1
  643. nautobot/tenancy/templates/tenancy/tenant.html +0 -2
  644. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
  645. nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
  646. nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
  647. nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
  648. nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
  649. nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
  650. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
  651. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
  652. nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
  653. nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
  654. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
  655. /nautobot/dcim/templates/dcim/{cable.html → cable_retrieve.html} +0 -0
  656. /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
  657. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
  658. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
  659. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
nautobot/extras/views.py CHANGED
@@ -1,3 +1,4 @@
1
+ from functools import partial
1
2
  import logging
2
3
  from typing import Optional
3
4
  from urllib.parse import parse_qs
@@ -24,6 +25,7 @@ from django_tables2 import RequestConfig
24
25
  from jsonschema.validators import Draft7Validator
25
26
  from rest_framework.decorators import action
26
27
  from rest_framework.permissions import IsAuthenticated
28
+ from rest_framework.response import Response
27
29
 
28
30
  from nautobot.core.choices import ButtonActionColorChoices
29
31
  from nautobot.core.constants import PAGINATE_COUNT_DEFAULT
@@ -59,6 +61,7 @@ from nautobot.core.views.mixins import (
59
61
  ObjectBulkDestroyViewMixin,
60
62
  ObjectBulkUpdateViewMixin,
61
63
  ObjectChangeLogViewMixin,
64
+ ObjectDataComplianceViewMixin,
62
65
  ObjectDestroyViewMixin,
63
66
  ObjectDetailViewMixin,
64
67
  ObjectEditViewMixin,
@@ -90,6 +93,8 @@ from nautobot.ipam.models import IPAddress, Prefix, VLAN
90
93
  from nautobot.ipam.tables import IPAddressTable, PrefixTable, VLANTable
91
94
  from nautobot.virtualization.models import VirtualMachine, VMInterface
92
95
  from nautobot.virtualization.tables import VirtualMachineTable, VMInterfaceTable
96
+ from nautobot.vpn.models import VPN, VPNProfile, VPNTunnel, VPNTunnelEndpoint
97
+ from nautobot.vpn.tables import VPNProfileTable, VPNTable, VPNTunnelEndpointTable, VPNTunnelTable
93
98
 
94
99
  from . import filters, forms, jobs_ui, tables
95
100
  from .api import serializers
@@ -323,6 +328,7 @@ class ApprovalWorkflowUIViewSet(
323
328
  section=SectionChoices.RIGHT_HALF,
324
329
  exclude_columns=["approval_workflow"],
325
330
  add_button_route=None,
331
+ enable_related_link=False,
326
332
  ),
327
333
  ],
328
334
  )
@@ -409,6 +415,7 @@ class ApprovalWorkflowStageUIViewSet(
409
415
  section=SectionChoices.RIGHT_HALF,
410
416
  exclude_columns=["approval_workflow_stage"],
411
417
  table_title="Responses",
418
+ enable_related_link=False,
412
419
  ),
413
420
  ],
414
421
  )
@@ -754,6 +761,7 @@ class ConfigContextUIViewSet(NautobotUIViewSet):
754
761
  "locations",
755
762
  "roles",
756
763
  "device_types",
764
+ "device_families",
757
765
  "platforms",
758
766
  "cluster_groups",
759
767
  "clusters",
@@ -964,6 +972,7 @@ class ContactUIViewSet(NautobotUIViewSet):
964
972
  table_filter="contact",
965
973
  table_title="Contact For",
966
974
  add_button_route=None,
975
+ enable_related_link=False,
967
976
  ),
968
977
  ),
969
978
  )
@@ -1262,12 +1271,7 @@ class DynamicGroupUIViewSet(NautobotUIViewSet):
1262
1271
  elif self.action == "retrieve":
1263
1272
  model = instance.model
1264
1273
  table_class = get_table_for_model(model)
1265
- if instance.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
1266
- # Ensure that members cache is up-to-date for this specific group
1267
- members = instance.update_cached_members()
1268
- messages.success(request, f"Refreshed cached members list for {instance}")
1269
- else:
1270
- members = instance.members
1274
+ members = instance.members
1271
1275
  if table_class is not None:
1272
1276
  if hasattr(members, "without_tree_fields"):
1273
1277
  members = members.without_tree_fields()
@@ -1322,7 +1326,7 @@ class DynamicGroupUIViewSet(NautobotUIViewSet):
1322
1326
 
1323
1327
  return context
1324
1328
 
1325
- def form_save(self, form, **kwargs):
1329
+ def form_save(self, form, commit=True, **kwargs):
1326
1330
  obj = form.save(commit=False)
1327
1331
  context = self.get_extra_context(self.request, obj)
1328
1332
 
@@ -1341,10 +1345,18 @@ class DynamicGroupUIViewSet(NautobotUIViewSet):
1341
1345
  form.add_error(None, msg)
1342
1346
  raise
1343
1347
 
1344
- # After filters have been set, now we save the object to the database.
1345
- obj.save()
1346
- # Save m2m fields, such as Tags https://docs.djangoproject.com/en/3.2/topics/forms/modelforms/#the-save-method
1347
- form.save_m2m()
1348
+ if commit:
1349
+ # After filters have been set, now we save the object to the database.
1350
+ obj.save(update_cached_members=False)
1351
+ # Save m2m fields, such as Tags https://docs.djangoproject.com/en/3.2/topics/forms/modelforms/#the-save-method
1352
+ form.save_m2m()
1353
+
1354
+ if obj.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
1355
+ messages.warning(
1356
+ self.request,
1357
+ "Dynamic Group membership is not automatically recalculated after creating/editing the group, "
1358
+ 'as it may take some time to complete. You can use the "Refresh Members" button when ready.',
1359
+ )
1348
1360
 
1349
1361
  # Process the formsets for children
1350
1362
  children = context.get("children")
@@ -1359,7 +1371,7 @@ class DynamicGroupUIViewSet(NautobotUIViewSet):
1359
1371
  added_errors.add(msg)
1360
1372
  raise ValidationError("invalid DynamicGroupMembershipFormSet")
1361
1373
 
1362
- if children:
1374
+ if commit and children:
1363
1375
  children.save()
1364
1376
 
1365
1377
  return obj
@@ -1658,6 +1670,7 @@ class GitRepositoryUIViewSet(NautobotUIViewSet):
1658
1670
  filterset_class = filters.GitRepositoryFilterSet
1659
1671
  serializer_class = serializers.GitRepositorySerializer
1660
1672
  table_class = tables.GitRepositoryTable
1673
+ view_titles = Titles(titles={"result": "{{ object.display|default:object }} - Synchronization Status"})
1661
1674
 
1662
1675
  def get_extra_context(self, request, instance=None):
1663
1676
  context = super().get_extra_context(request, instance)
@@ -1701,14 +1714,18 @@ class GitRepositoryUIViewSet(NautobotUIViewSet):
1701
1714
  job_result = instance.get_latest_sync()
1702
1715
 
1703
1716
  context = {
1717
+ **super().get_extra_context(request, instance),
1704
1718
  "result": job_result or {},
1705
- "base_template": "extras/gitrepository.html",
1719
+ "base_template": "extras/gitrepository_retrieve.html",
1706
1720
  "object": instance,
1707
1721
  "active_tab": "result",
1708
1722
  "verbose_name": instance._meta.verbose_name,
1709
1723
  }
1710
1724
 
1711
- return render(request, "extras/gitrepository_result.html", context)
1725
+ return Response(
1726
+ context,
1727
+ template_name="extras/gitrepository_result.html",
1728
+ )
1712
1729
 
1713
1730
  @action(
1714
1731
  detail=True,
@@ -1745,6 +1762,7 @@ class GraphQLQueryUIViewSet(
1745
1762
  ObjectDestroyViewMixin,
1746
1763
  ObjectBulkDestroyViewMixin,
1747
1764
  ObjectChangeLogViewMixin,
1765
+ ObjectDataComplianceViewMixin,
1748
1766
  ObjectNotesViewMixin,
1749
1767
  ):
1750
1768
  filterset_form_class = forms.GraphQLQueryFilterForm
@@ -1755,6 +1773,27 @@ class GraphQLQueryUIViewSet(
1755
1773
  table_class = tables.GraphQLQueryTable
1756
1774
  action_buttons = ("add",)
1757
1775
 
1776
+ object_detail_content = object_detail.ObjectDetailContent(
1777
+ panels=(
1778
+ object_detail.ObjectFieldsPanel(
1779
+ label="Query",
1780
+ weight=100,
1781
+ section=SectionChoices.LEFT_HALF,
1782
+ fields=["name", "query", "variables"],
1783
+ value_transforms={
1784
+ "query": [lambda val: format_html('<pre><code class="language-graphql">{}</code></pre>', val)],
1785
+ "variables": [lambda val: helpers.render_json(val, syntax_highlight=True, pretty_print=True)],
1786
+ },
1787
+ ),
1788
+ object_detail.Panel(
1789
+ weight=100,
1790
+ section=object_detail.SectionChoices.RIGHT_HALF,
1791
+ body_content_template_path="extras/inc/graphqlquery_execute.html",
1792
+ body_wrapper_template_path="components/panel/body_wrapper_table.html",
1793
+ ),
1794
+ )
1795
+ )
1796
+
1758
1797
 
1759
1798
  #
1760
1799
  # Image attachments
@@ -2010,55 +2049,66 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
2010
2049
  ignore_singleton_lock = job_form.cleaned_data.pop("_ignore_singleton_lock", False)
2011
2050
  schedule_type = schedule_form.cleaned_data["_schedule_type"]
2012
2051
 
2013
- scheduled_job = ScheduledJob.create_schedule(
2014
- job_model,
2015
- request.user,
2016
- name=schedule_form.cleaned_data.get("_schedule_name"),
2017
- start_time=schedule_form.cleaned_data.get("_schedule_start_time"),
2018
- interval=schedule_type,
2019
- crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
2020
- job_queue=job_queue,
2021
- profile=profile,
2022
- ignore_singleton_lock=ignore_singleton_lock,
2023
- validated_save=False,
2024
- **job_class.serialize_data(job_form.cleaned_data),
2025
- )
2026
- scheduled_job_has_approval_workflow = scheduled_job.has_approval_workflow_definition()
2027
- is_scheduled = schedule_type in JobExecutionType.SCHEDULE_CHOICES
2028
- if job_model.has_sensitive_variables and scheduled_job_has_approval_workflow:
2029
- messages.error(
2030
- request,
2031
- "Unable to run or schedule job: "
2032
- "This job is flagged as possibly having sensitive variables but also has an applicable approval workflow definition."
2033
- "Modify or remove the approval workflow definition or modify the job to set `has_sensitive_variables` to False.",
2052
+ with transaction.atomic():
2053
+ scheduled_job = ScheduledJob.create_schedule(
2054
+ job_model,
2055
+ request.user,
2056
+ name=schedule_form.cleaned_data.get("_schedule_name"),
2057
+ start_time=schedule_form.cleaned_data.get("_schedule_start_time"),
2058
+ interval=schedule_type,
2059
+ crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
2060
+ job_queue=job_queue,
2061
+ profile=profile,
2062
+ ignore_singleton_lock=ignore_singleton_lock,
2063
+ **job_class.serialize_data(job_form.cleaned_data),
2034
2064
  )
2035
- else:
2036
- if dryrun and not is_scheduled:
2037
- # Enqueue job for immediate execution when dryrun and (no schedule, no has_sensitive_variables)
2065
+ scheduled_job_has_approval_workflow = scheduled_job.has_approval_workflow_definition()
2066
+ is_scheduled = schedule_type in JobExecutionType.SCHEDULE_CHOICES
2067
+ if job_model.has_sensitive_variables and scheduled_job_has_approval_workflow:
2068
+ messages.error(
2069
+ request,
2070
+ "Unable to run or schedule job: "
2071
+ "This job is flagged as possibly having sensitive variables but also has an applicable approval workflow definition."
2072
+ "Modify or remove the approval workflow definition or modify the job to set `has_sensitive_variables` to False.",
2073
+ )
2074
+ scheduled_job.delete()
2075
+ scheduled_job = None
2076
+ else:
2077
+ if dryrun and not is_scheduled:
2078
+ # Enqueue job for immediate execution when dryrun and (no schedule, no has_sensitive_variables)
2079
+ scheduled_job.delete()
2080
+ scheduled_job = None
2081
+ return self._handle_immediate_execution(
2082
+ request,
2083
+ job_model,
2084
+ job_class,
2085
+ job_form,
2086
+ profile,
2087
+ ignore_singleton_lock,
2088
+ job_queue,
2089
+ return_url,
2090
+ )
2091
+ # Step 1: Check if approval is required
2092
+ if scheduled_job_has_approval_workflow:
2093
+ return self._handle_approval_workflow_response(request, scheduled_job, return_url)
2094
+
2095
+ # Step 3: If approval is not required
2096
+ if is_scheduled:
2097
+ return self._handle_scheduled_job_response(request, scheduled_job, return_url)
2098
+
2099
+ # Step 4: Immediate execution (no schedule, no approval)
2100
+ scheduled_job.delete()
2101
+ scheduled_job = None
2038
2102
  return self._handle_immediate_execution(
2039
- request, job_model, job_class, job_form, profile, ignore_singleton_lock, job_queue, return_url
2103
+ request,
2104
+ job_model,
2105
+ job_class,
2106
+ job_form,
2107
+ profile,
2108
+ ignore_singleton_lock,
2109
+ job_queue,
2110
+ return_url,
2040
2111
  )
2041
- # Step 1: Check if approval is required
2042
- if scheduled_job_has_approval_workflow:
2043
- scheduled_job.validated_save()
2044
- return self._handle_approval_workflow_response(request, scheduled_job, return_url)
2045
-
2046
- # Step 3: If approval is not required
2047
- if is_scheduled:
2048
- scheduled_job.validated_save()
2049
- return self._handle_scheduled_job_response(request, scheduled_job, return_url)
2050
-
2051
- # Step 4: Immediate execution (no schedule, no approval)
2052
- return self._handle_immediate_execution(
2053
- request,
2054
- job_model,
2055
- job_class,
2056
- job_form,
2057
- profile,
2058
- ignore_singleton_lock,
2059
- job_queue,
2060
- return_url,
2061
- )
2062
2112
 
2063
2113
  if return_url:
2064
2114
  return redirect(return_url)
@@ -2086,7 +2136,7 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
2086
2136
 
2087
2137
  class JobView(generic.ObjectView):
2088
2138
  queryset = JobModel.objects.all()
2089
- template_name = "extras/job_detail.html"
2139
+ template_name = "generic/object_retrieve.html"
2090
2140
  object_detail_content = object_detail.ObjectDetailContent(
2091
2141
  panels=[
2092
2142
  object_detail.ObjectFieldsPanel(
@@ -2107,13 +2157,17 @@ class JobView(generic.ObjectView):
2107
2157
  section=SectionChoices.LEFT_HALF,
2108
2158
  label="Job",
2109
2159
  fields=["grouping", "name", "description", "enabled"],
2160
+ value_transforms={
2161
+ "description": [helpers.render_markdown],
2162
+ },
2110
2163
  ),
2111
2164
  object_detail.ObjectsTablePanel(
2112
2165
  weight=100,
2113
2166
  section=SectionChoices.FULL_WIDTH,
2114
2167
  table_class=tables.JobResultTable,
2115
- table_title="JobResults",
2116
- table_filter=["job_model"],
2168
+ table_title="Job Results",
2169
+ table_filter="job_model",
2170
+ exclude_columns=["name", "job_model"],
2117
2171
  ),
2118
2172
  jobs_ui.JobObjectFieldsPanel(
2119
2173
  weight=100,
@@ -2469,7 +2523,7 @@ class SavedViewUIViewSet(
2469
2523
 
2470
2524
 
2471
2525
  class ScheduledJobListView(generic.ObjectListView):
2472
- queryset = ScheduledJob.objects.enabled()
2526
+ queryset = ScheduledJob.objects.all()
2473
2527
  table = tables.ScheduledJobTable
2474
2528
  filterset = filters.ScheduledJobFilterSet
2475
2529
  filterset_form = forms.ScheduledJobFilterForm
@@ -2701,6 +2755,12 @@ class ObjectChangeUIViewSet(ObjectDetailViewMixin, ObjectListViewMixin):
2701
2755
  table_class = tables.ObjectChangeTable
2702
2756
  action_buttons = ("export",)
2703
2757
 
2758
+ def __init__(self, *args, **kwargs):
2759
+ super().__init__(*args, **kwargs)
2760
+ self.object_detail_content = object_detail.ObjectDetailContent()
2761
+ # Remove "Advanced" tab while keeping the main.
2762
+ self.object_detail_content.tabs = self.object_detail_content.tabs[:1]
2763
+
2704
2764
  # 2.0 TODO: Remove this remapping and solve it at the `BaseFilterSet` as it is addressing a breaking change.
2705
2765
  def get(self, request, *args, **kwargs):
2706
2766
  # Remappings below allow previous queries of time_before and time_after to use
@@ -2789,7 +2849,7 @@ class ObjectChangeLogView(generic.GenericView):
2789
2849
 
2790
2850
  return render(
2791
2851
  request,
2792
- "extras/object_changelog.html",
2852
+ "generic/object_changelog.html",
2793
2853
  {
2794
2854
  "object": obj,
2795
2855
  "verbose_name": obj._meta.verbose_name,
@@ -2885,7 +2945,12 @@ class ObjectMetadataUIViewSet(
2885
2945
 
2886
2946
 
2887
2947
  class NoteUIViewSet(
2888
- ObjectChangeLogViewMixin, ObjectDestroyViewMixin, ObjectDetailViewMixin, ObjectEditViewMixin, ObjectListViewMixin
2948
+ ObjectDestroyViewMixin,
2949
+ ObjectDetailViewMixin,
2950
+ ObjectEditViewMixin,
2951
+ ObjectListViewMixin,
2952
+ ObjectChangeLogViewMixin,
2953
+ ObjectDataComplianceViewMixin,
2889
2954
  ):
2890
2955
  filterset_class = filters.NoteFilterSet
2891
2956
  filterset_form_class = forms.NoteFilterForm
@@ -2996,7 +3061,7 @@ class ObjectNotesView(generic.GenericView):
2996
3061
 
2997
3062
  return render(
2998
3063
  request,
2999
- "extras/object_notes.html",
3064
+ "generic/object_notes.html",
3000
3065
  {
3001
3066
  "object": obj,
3002
3067
  "verbose_name": obj._meta.verbose_name,
@@ -3171,6 +3236,30 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
3171
3236
  vdc_table.columns.hide("role")
3172
3237
  RequestConfig(request, paginate).configure(vdc_table)
3173
3238
  context["vdc_table"] = vdc_table
3239
+ if ContentType.objects.get_for_model(VPN) in context["content_types"]:
3240
+ vpns = instance.vpns.restrict(request.user, "view")
3241
+ vpn_table = VPNTable(vpns)
3242
+ vpn_table.columns.hide("role")
3243
+ RequestConfig(request, paginate).configure(vpn_table)
3244
+ context["vpn_table"] = vpn_table
3245
+ if ContentType.objects.get_for_model(VPNProfile) in context["content_types"]:
3246
+ vpn_profiles = instance.vpn_profiles.restrict(request.user, "view")
3247
+ vpn_profile_table = VPNProfileTable(vpn_profiles)
3248
+ vpn_profile_table.columns.hide("role")
3249
+ RequestConfig(request, paginate).configure(vpn_profile_table)
3250
+ context["vpn_profile_table"] = vpn_profile_table
3251
+ if ContentType.objects.get_for_model(VPNTunnel) in context["content_types"]:
3252
+ vpn_tunnels = instance.vpn_tunnels.restrict(request.user, "view")
3253
+ vpn_tunnel_table = VPNTunnelTable(vpn_tunnels)
3254
+ vpn_tunnel_table.columns.hide("role")
3255
+ RequestConfig(request, paginate).configure(vpn_tunnel_table)
3256
+ context["vpn_tunnel_table"] = vpn_tunnel_table
3257
+ if ContentType.objects.get_for_model(VPNTunnelEndpoint) in context["content_types"]:
3258
+ vpn_tunnel_endpoints = instance.vpn_tunnel_endpoints.restrict(request.user, "view")
3259
+ vpn_tunnel_endpoint_table = VPNTunnelEndpointTable(vpn_tunnel_endpoints)
3260
+ vpn_tunnel_endpoint_table.columns.hide("role")
3261
+ RequestConfig(request, paginate).configure(vpn_tunnel_endpoint_table)
3262
+ context["vpn_tunnel_endpoint_table"] = vpn_tunnel_endpoint_table
3174
3263
  return context
3175
3264
 
3176
3265
 
@@ -3188,6 +3277,7 @@ class SecretUIViewSet(
3188
3277
  ObjectBulkDestroyViewMixin,
3189
3278
  # no ObjectBulkUpdateViewMixin here yet
3190
3279
  ObjectChangeLogViewMixin,
3280
+ ObjectDataComplianceViewMixin,
3191
3281
  ObjectNotesViewMixin,
3192
3282
  ):
3193
3283
  queryset = Secret.objects.all()
@@ -3269,7 +3359,8 @@ class SecretsGroupUIViewSet(NautobotUIViewSet):
3269
3359
  object_detail.ObjectsTablePanel(
3270
3360
  table_class=tables.SecretsGroupAssociationTable,
3271
3361
  table_filter="secrets_group",
3272
- related_field_name="secrets_group",
3362
+ related_field_name="secrets_groups",
3363
+ related_list_url_name="extras:secret_list",
3273
3364
  table_title="Secrets",
3274
3365
  section=SectionChoices.LEFT_HALF,
3275
3366
  weight=200,
@@ -3375,6 +3466,7 @@ class TagUIViewSet(NautobotUIViewSet):
3375
3466
  select_related_fields=["content_type"],
3376
3467
  prefetch_related_fields=["content_object"],
3377
3468
  include_paginator=True,
3469
+ enable_related_link=False,
3378
3470
  ),
3379
3471
  ),
3380
3472
  )
@@ -3431,6 +3523,7 @@ class TeamUIViewSet(NautobotUIViewSet):
3431
3523
  table_filter="team",
3432
3524
  table_title="Contact For",
3433
3525
  add_button_route=None,
3526
+ enable_related_link=False,
3434
3527
  ),
3435
3528
  )
3436
3529
  )
@@ -3463,7 +3556,7 @@ class WebhookUIViewSet(NautobotUIViewSet):
3463
3556
  section=SectionChoices.LEFT_HALF,
3464
3557
  weight=100,
3465
3558
  fields=("http_method", "http_content_type", "payload_url", "additional_headers"),
3466
- value_transforms={"additional_headers": [helpers.pre_tag]},
3559
+ value_transforms={"additional_headers": [partial(helpers.pre_tag, format_empty_value=False)]},
3467
3560
  ),
3468
3561
  object_detail.ObjectFieldsPanel(
3469
3562
  label="Security",
@@ -3490,8 +3583,8 @@ class WebhookUIViewSet(NautobotUIViewSet):
3490
3583
 
3491
3584
 
3492
3585
  class JobObjectChangeLogView(ObjectChangeLogView):
3493
- base_template = "extras/job_detail.html"
3586
+ base_template = "generic/object_retrieve.html"
3494
3587
 
3495
3588
 
3496
3589
  class JobObjectNotesView(ObjectNotesView):
3497
- base_template = "extras/job_detail.html"
3590
+ base_template = "generic/object_retrieve.html"
nautobot/ipam/factory.py CHANGED
@@ -278,9 +278,16 @@ class NamespaceFactory(PrimaryModelFactory):
278
278
 
279
279
  class Meta:
280
280
  model = Namespace
281
+ exclude = ("has_tenant", "has_description")
281
282
 
282
283
  name = UniqueFaker("text", max_nb_chars=20) # could be up to CHARFIELD_MAX_LENGTH but that gets unwieldy fast
283
284
 
285
+ has_tenant = NautobotBoolIterator()
286
+ tenant = factory.Maybe("has_tenant", random_instance(Tenant))
287
+
288
+ has_description = NautobotBoolIterator()
289
+ description = factory.Maybe("has_description", factory.Faker("text", max_nb_chars=CHARFIELD_MAX_LENGTH), "")
290
+
284
291
 
285
292
  class PrefixFactory(PrimaryModelFactory):
286
293
  """Create random Prefix objects with randomized data.
@@ -0,0 +1,38 @@
1
+ import uuid
2
+
3
+ from nautobot.core.filters import NaturalKeyOrPKMultipleChoiceFilter
4
+ from nautobot.ipam import formfields
5
+ from nautobot.ipam.models import Prefix
6
+
7
+
8
+ class PrefixFilter(NaturalKeyOrPKMultipleChoiceFilter):
9
+ """
10
+ Filter that supports filtering a foreign key to Prefix by either its PK or by a literal `prefix` string.
11
+ """
12
+
13
+ field_class = formfields.PrefixFilterFormField
14
+
15
+ def __init__(self, *args, **kwargs):
16
+ kwargs.setdefault("to_field_name", "pk")
17
+ kwargs.setdefault("label", "Prefix (ID or prefix string)")
18
+ kwargs.setdefault("queryset", Prefix.objects.all())
19
+ super().__init__(*args, **kwargs)
20
+
21
+ def get_filter_predicate(self, v):
22
+ # Null value filtering
23
+ if v is None:
24
+ return {f"{self.field_name}__isnull": True}
25
+
26
+ # If value is a model instance, stringify it to a pk.
27
+ if isinstance(v, Prefix):
28
+ v = v.pk
29
+
30
+ # Try to cast the value to a UUID to distinguish between PKs and prefix strings
31
+ v = str(v)
32
+ try:
33
+ uuid.UUID(v)
34
+ return {self.field_name: v}
35
+ except (AttributeError, TypeError, ValueError):
36
+ # It's a prefix string
37
+ prefixes_queryset = Prefix.objects.net_equals(v)
38
+ return {f"{self.field_name}__in": prefixes_queryset.values_list("pk", flat=True)}
nautobot/ipam/filters.py CHANGED
@@ -19,12 +19,15 @@ from nautobot.core.filters import (
19
19
  SearchFilter,
20
20
  TreeNodeMultipleChoiceFilter,
21
21
  )
22
+ from nautobot.core.utils.data import is_uuid
22
23
  from nautobot.dcim.filters import LocatableModelFilterSetMixin
23
24
  from nautobot.dcim.models import Device, Interface, Location, VirtualDeviceContext
24
25
  from nautobot.extras.filters import NautobotFilterSet, RoleModelFilterSetMixin, StatusModelFilterSetMixin
25
- from nautobot.ipam import choices, formfields
26
- from nautobot.tenancy.filters.mixins import TenancyModelFilterSetMixin
26
+ from nautobot.ipam import choices
27
+ from nautobot.ipam.filter_mixins import PrefixFilter
28
+ from nautobot.tenancy.filter_mixins import TenancyModelFilterSetMixin
27
29
  from nautobot.virtualization.models import VirtualMachine, VMInterface
30
+ from nautobot.vpn.models import VPNTunnelEndpoint
28
31
 
29
32
  from .models import (
30
33
  IPAddress,
@@ -57,40 +60,7 @@ __all__ = (
57
60
  )
58
61
 
59
62
 
60
- class PrefixFilter(NaturalKeyOrPKMultipleChoiceFilter):
61
- """
62
- Filter that supports filtering a foreign key to Prefix by either its PK or by a literal `prefix` string.
63
- """
64
-
65
- field_class = formfields.PrefixFilterFormField
66
-
67
- def __init__(self, *args, **kwargs):
68
- kwargs.setdefault("to_field_name", "pk")
69
- kwargs.setdefault("label", "Prefix (ID or prefix string)")
70
- kwargs.setdefault("queryset", Prefix.objects.all())
71
- super().__init__(*args, **kwargs)
72
-
73
- def get_filter_predicate(self, v):
74
- # Null value filtering
75
- if v is None:
76
- return {f"{self.field_name}__isnull": True}
77
-
78
- # If value is a model instance, stringify it to a pk.
79
- if isinstance(v, Prefix):
80
- v = v.pk
81
-
82
- # Try to cast the value to a UUID to distinguish between PKs and prefix strings
83
- v = str(v)
84
- try:
85
- uuid.UUID(v)
86
- return {self.field_name: v}
87
- except (AttributeError, TypeError, ValueError):
88
- # It's a prefix string
89
- prefixes_queryset = Prefix.objects.net_equals(v)
90
- return {f"{self.field_name}__in": prefixes_queryset.values_list("pk", flat=True)}
91
-
92
-
93
- class NamespaceFilterSet(NautobotFilterSet):
63
+ class NamespaceFilterSet(NautobotFilterSet, TenancyModelFilterSetMixin):
94
64
  q = SearchFilter(
95
65
  filter_predicates={
96
66
  "name": "icontains",
@@ -263,6 +233,13 @@ class PrefixFilterSet(
263
233
  method="search_contains",
264
234
  label="Prefixes which contain this prefix or IP",
265
235
  )
236
+ ancestors = NaturalKeyOrPKMultipleChoiceFilter(
237
+ queryset=Prefix.objects.all(),
238
+ prefers_id=True,
239
+ to_field_name="network",
240
+ method="filter_ancestors",
241
+ label="Prefixes which are ancestors of this prefix (ID or host string)",
242
+ )
266
243
  vrfs = NaturalKeyOrPKMultipleChoiceFilter(
267
244
  queryset=VRF.objects.all(),
268
245
  to_field_name="rd",
@@ -320,13 +297,29 @@ class PrefixFilterSet(
320
297
  queryset=CloudNetwork.objects.all(),
321
298
  to_field_name="name",
322
299
  )
300
+ vpn_tunnel_endpoints = NaturalKeyOrPKMultipleChoiceFilter(
301
+ queryset=VPNTunnelEndpoint.objects.all(),
302
+ to_field_name="pk",
303
+ label="VPN Tunnel Endpoint ID",
304
+ )
305
+ vpn_tunnel_endpoints_name_contains = django_filters.CharFilter(
306
+ method="filter_vpntunnelendpoint_name_contains",
307
+ label="VPN Tunnel Endpoint Name Contains",
308
+ )
323
309
 
324
310
  class Meta:
325
311
  model = Prefix
326
312
  fields = ["date_allocated", "id", "prefix_length", "tags"]
327
313
 
328
314
  def _strip_values(self, values):
329
- return [value.strip() for value in values if value.strip()]
315
+ result = []
316
+ for value in values:
317
+ value = value.strip()
318
+ if is_uuid(value):
319
+ result.append(Prefix.objects.get(pk=value).prefix)
320
+ elif value:
321
+ result.append(value)
322
+ return result
330
323
 
331
324
  def filter_prefix(self, queryset, name, value):
332
325
  prefixes = self._strip_values(value)
@@ -363,6 +356,13 @@ class PrefixFilterSet(
363
356
  prefixes_queryset |= queryset.filter(query)
364
357
  return prefixes_queryset
365
358
 
359
+ def filter_ancestors(self, queryset, name, value):
360
+ if not value:
361
+ return queryset
362
+ prefixes = Prefix.objects.filter(pk__in=[v.id for v in value])
363
+ ancestor_ids = [ancestor.id for prefix in prefixes for ancestor in prefix.ancestors()]
364
+ return queryset.filter(pk__in=ancestor_ids)
365
+
366
366
  def generate_query_filter_present_in_vrf(self, value):
367
367
  if isinstance(value, (str, uuid.UUID)):
368
368
  value = VRF.objects.get(pk=value)
@@ -376,6 +376,9 @@ class PrefixFilterSet(
376
376
  params = self.generate_query_filter_present_in_vrf(value)
377
377
  return queryset.filter(params).distinct()
378
378
 
379
+ def filter_vpntunnelendpoint_name_contains(self, queryset, name, value):
380
+ return queryset.filter(vpn_tunnel_endpoints__name__contains=value)
381
+
379
382
 
380
383
  class PrefixLocationAssignmentFilterSet(NautobotFilterSet):
381
384
  q = SearchFilter(
@@ -486,6 +489,11 @@ class IPAddressFilterSet(
486
489
  label="Has NAT Inside",
487
490
  )
488
491
  ip_version = django_filters.NumberFilter()
492
+ services = NaturalKeyOrPKMultipleChoiceFilter(
493
+ queryset=Service.objects.all(),
494
+ to_field_name="name",
495
+ label="Services (name or ID)",
496
+ )
489
497
 
490
498
  class Meta:
491
499
  model = IPAddress
@@ -505,7 +513,14 @@ class IPAddressFilterSet(
505
513
  return queryset.filter(params)
506
514
 
507
515
  def search_by_prefix(self, queryset, name, value):
508
- prefixes = [prefix.strip() for prefix in value if prefix.strip()]
516
+ prefixes = []
517
+ for prefix in value:
518
+ prefix = prefix.strip()
519
+ if is_uuid(prefix):
520
+ prefixes.append(Prefix.objects.get(pk=prefix).prefix)
521
+ elif prefix:
522
+ prefixes.append(prefix)
523
+
509
524
  return queryset.net_host_contained(*prefixes)
510
525
 
511
526
  def filter_address(self, queryset, name, value):
@@ -67,7 +67,7 @@ class IPNetworkFormField(forms.Field):
67
67
  class PrefixFilterFormField(MultiMatchModelMultipleChoiceField):
68
68
  @property
69
69
  def filter(self):
70
- from nautobot.ipam.filters import PrefixFilter # avoid circular definition
70
+ from nautobot.ipam.filter_mixins import PrefixFilter # avoid circular definition
71
71
 
72
72
  return PrefixFilter
73
73