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
@@ -1,5 +1,354 @@
1
- # TODO: remove this entire file in 2.2
2
- # for backwards compatibility with Nautobot 1.4 - avoid importing this file if at all possible!
3
- from nautobot.dcim.filters.mixins import LocatableModelFilterSetMixin
1
+ from django.db.models import Q
2
+ import django_filters
4
3
 
5
- __all__ = ("LocatableModelFilterSetMixin",)
4
+ from nautobot.core.filters import (
5
+ MultiValueCharFilter,
6
+ MultiValueMACAddressFilter,
7
+ MultiValueUUIDFilter,
8
+ NameSearchFilterSet,
9
+ NaturalKeyOrPKMultipleChoiceFilter,
10
+ RelatedMembershipBooleanFilter,
11
+ SearchFilter,
12
+ TreeNodeMultipleChoiceFilter,
13
+ )
14
+ from nautobot.core.utils.data import is_uuid
15
+ from nautobot.dcim.constants import MODULE_RECURSION_DEPTH_LIMIT
16
+ from nautobot.dcim.models import (
17
+ Cable,
18
+ ConsolePort,
19
+ ConsolePortTemplate,
20
+ ConsoleServerPort,
21
+ ConsoleServerPortTemplate,
22
+ Device,
23
+ DeviceType,
24
+ FrontPort,
25
+ FrontPortTemplate,
26
+ Interface,
27
+ InterfaceTemplate,
28
+ Location,
29
+ Manufacturer,
30
+ Module,
31
+ ModuleBay,
32
+ ModuleBayTemplate,
33
+ ModuleType,
34
+ PowerOutlet,
35
+ PowerOutletTemplate,
36
+ PowerPort,
37
+ PowerPortTemplate,
38
+ RearPort,
39
+ RearPortTemplate,
40
+ )
41
+ from nautobot.extras.filter_mixins import CustomFieldModelFilterSetMixin, RelationshipModelFilterSetMixin
42
+
43
+
44
+ class CableTerminationModelFilterSetMixin(django_filters.FilterSet):
45
+ has_cable = RelatedMembershipBooleanFilter(
46
+ field_name="cable",
47
+ label="Has cable",
48
+ )
49
+ cable = django_filters.ModelMultipleChoiceFilter(
50
+ queryset=Cable.objects.all(),
51
+ label="Cable",
52
+ )
53
+
54
+
55
+ class DeviceComponentTemplateModelFilterSetMixin(NameSearchFilterSet, CustomFieldModelFilterSetMixin):
56
+ device_type = NaturalKeyOrPKMultipleChoiceFilter(
57
+ queryset=DeviceType.objects.all(),
58
+ to_field_name="model",
59
+ label="Device type (model or ID)",
60
+ )
61
+ label = MultiValueCharFilter(label="Label")
62
+ description = MultiValueCharFilter(label="Description")
63
+ id = MultiValueUUIDFilter(label="ID")
64
+ name = MultiValueCharFilter(label="Name")
65
+
66
+
67
+ class ModularDeviceComponentTemplateModelFilterSetMixin(DeviceComponentTemplateModelFilterSetMixin):
68
+ has_device_type = RelatedMembershipBooleanFilter(
69
+ field_name="device_type",
70
+ label="Has device type",
71
+ )
72
+ module_type = NaturalKeyOrPKMultipleChoiceFilter(
73
+ queryset=ModuleType.objects.all(),
74
+ to_field_name="model",
75
+ label="Module type (model or ID)",
76
+ )
77
+ has_module_type = RelatedMembershipBooleanFilter(
78
+ field_name="module_type",
79
+ label="Has module type",
80
+ )
81
+
82
+
83
+ class DeviceComponentModelFilterSetMixin(CustomFieldModelFilterSetMixin):
84
+ q = SearchFilter(
85
+ filter_predicates={
86
+ "name": "icontains",
87
+ "label": "icontains",
88
+ "description": "icontains",
89
+ },
90
+ )
91
+ location = NaturalKeyOrPKMultipleChoiceFilter(
92
+ field_name="device__location",
93
+ queryset=Location.objects.all(),
94
+ to_field_name="name",
95
+ label="Location (name or ID)",
96
+ )
97
+ device = NaturalKeyOrPKMultipleChoiceFilter(
98
+ queryset=Device.objects.all(),
99
+ to_field_name="name",
100
+ label="Device (name or ID)",
101
+ )
102
+
103
+
104
+ class ModularDeviceComponentModelFilterSetMixin(DeviceComponentModelFilterSetMixin, RelationshipModelFilterSetMixin):
105
+ module = NaturalKeyOrPKMultipleChoiceFilter(
106
+ queryset=Module.objects.all(),
107
+ to_field_name="module_type__model",
108
+ label="Module (model or ID)",
109
+ )
110
+ device = NaturalKeyOrPKMultipleChoiceFilter(
111
+ queryset=Device.objects.all(),
112
+ to_field_name="name",
113
+ label="Device (name or ID)",
114
+ method="filter_device",
115
+ )
116
+
117
+ def _construct_device_filter_recursively(self, field_name, value):
118
+ recursion_depth = MODULE_RECURSION_DEPTH_LIMIT - 1
119
+ query = Q(**{f"device__{field_name}__in": value})
120
+ for level in range(recursion_depth):
121
+ recursive_query = "module__parent_module_bay__" + "parent_module__parent_module_bay__" * level
122
+ query = query | Q(**{f"{recursive_query}parent_device__{field_name}__in": value})
123
+ return query
124
+
125
+ def generate_query_filter_device(self, value):
126
+ if not hasattr(value, "__iter__") or isinstance(value, str):
127
+ value = [value]
128
+
129
+ device_ids = set(str(item) for item in value if is_uuid(item))
130
+ device_names = set(str(item) for item in value if not is_uuid(item))
131
+ query = self._construct_device_filter_recursively("name", device_names)
132
+ query |= self._construct_device_filter_recursively("id", device_ids)
133
+ return query
134
+
135
+ def filter_device(self, queryset, name, value):
136
+ if not value:
137
+ return queryset
138
+ params = self.generate_query_filter_device(value)
139
+ return queryset.filter(params)
140
+
141
+
142
+ class LocatableModelFilterSetMixin(django_filters.FilterSet):
143
+ """Mixin to add `location` filter fields to a FilterSet.
144
+
145
+ The expectation is that the linked model has `location` FK fields.
146
+ """
147
+
148
+ location = TreeNodeMultipleChoiceFilter(
149
+ prefers_id=True,
150
+ queryset=Location.objects.all(),
151
+ to_field_name="name",
152
+ label="Location (name or ID)",
153
+ )
154
+
155
+
156
+ class PathEndpointModelFilterSetMixin(django_filters.FilterSet):
157
+ connected = django_filters.BooleanFilter(method="filter_connected", label="Connected status (bool)")
158
+
159
+ def filter_connected(self, queryset, name, value):
160
+ if value:
161
+ return queryset.filter(_path__is_active=True)
162
+ else:
163
+ return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False))
164
+
165
+
166
+ class DeviceModuleCommonFiltersMixin(django_filters.FilterSet):
167
+ mac_address = MultiValueMACAddressFilter(
168
+ field_name="interfaces__mac_address",
169
+ label="MAC address",
170
+ )
171
+ has_console_ports = RelatedMembershipBooleanFilter(
172
+ field_name="console_ports",
173
+ label="Has console ports",
174
+ )
175
+ console_ports = NaturalKeyOrPKMultipleChoiceFilter(
176
+ queryset=ConsolePort.objects.all(),
177
+ to_field_name="name",
178
+ label="Console Ports (name or ID)",
179
+ )
180
+ has_console_server_ports = RelatedMembershipBooleanFilter(
181
+ field_name="console_server_ports",
182
+ label="Has console server ports",
183
+ )
184
+ console_server_ports = NaturalKeyOrPKMultipleChoiceFilter(
185
+ queryset=ConsoleServerPort.objects.all(),
186
+ to_field_name="name",
187
+ label="Console Server Ports (name or ID)",
188
+ )
189
+ has_power_ports = RelatedMembershipBooleanFilter(
190
+ field_name="power_ports",
191
+ label="Has power ports",
192
+ )
193
+ power_ports = NaturalKeyOrPKMultipleChoiceFilter(
194
+ queryset=PowerPort.objects.all(),
195
+ to_field_name="name",
196
+ label="Power Ports (name or ID)",
197
+ )
198
+ has_power_outlets = RelatedMembershipBooleanFilter(
199
+ field_name="power_outlets",
200
+ label="Has power outlets",
201
+ )
202
+ power_outlets = NaturalKeyOrPKMultipleChoiceFilter(
203
+ queryset=PowerOutlet.objects.all(),
204
+ to_field_name="name",
205
+ label="Power Outlets (name or ID)",
206
+ )
207
+ has_interfaces = RelatedMembershipBooleanFilter(
208
+ field_name="interfaces",
209
+ label="Has interfaces",
210
+ )
211
+ interfaces = NaturalKeyOrPKMultipleChoiceFilter(
212
+ queryset=Interface.objects.all(),
213
+ to_field_name="name",
214
+ label="Interfaces (name or ID)",
215
+ )
216
+ has_front_ports = RelatedMembershipBooleanFilter(
217
+ field_name="front_ports",
218
+ label="Has front ports",
219
+ )
220
+ front_ports = NaturalKeyOrPKMultipleChoiceFilter(
221
+ queryset=FrontPort.objects.all(),
222
+ to_field_name="name",
223
+ label="Front Ports (name or ID)",
224
+ )
225
+ has_rear_ports = RelatedMembershipBooleanFilter(
226
+ field_name="rear_ports",
227
+ label="Has rear ports",
228
+ )
229
+ rear_ports = NaturalKeyOrPKMultipleChoiceFilter(
230
+ queryset=RearPort.objects.all(),
231
+ to_field_name="name",
232
+ label="Rear Ports (name or ID)",
233
+ )
234
+ has_module_bays = RelatedMembershipBooleanFilter(
235
+ field_name="module_bays",
236
+ label="Has module bays",
237
+ )
238
+ has_empty_module_bays = django_filters.BooleanFilter(
239
+ method="filter_has_empty_module_bays",
240
+ label="Has empty module bays",
241
+ )
242
+ module_bays = django_filters.ModelMultipleChoiceFilter(
243
+ queryset=ModuleBay.objects.all(),
244
+ label="Module Bays",
245
+ )
246
+ has_modules = RelatedMembershipBooleanFilter(
247
+ field_name="module_bays__installed_module",
248
+ label="Has modules",
249
+ )
250
+
251
+ def generate_query_filter_has_empty_module_bays(self, value):
252
+ if value is True:
253
+ query = Q(module_bays__isnull=False, module_bays__installed_module__isnull=True)
254
+ else:
255
+ query = Q(module_bays__isnull=True) | ~Q(module_bays__installed_module__isnull=True)
256
+ return query
257
+
258
+ def filter_has_empty_module_bays(self, queryset, name, value):
259
+ if value is None:
260
+ return queryset.none
261
+ params = self.generate_query_filter_has_empty_module_bays(value)
262
+ return queryset.filter(params)
263
+
264
+
265
+ class DeviceTypeModuleTypeCommonFiltersMixin(django_filters.FilterSet):
266
+ manufacturer = NaturalKeyOrPKMultipleChoiceFilter(
267
+ queryset=Manufacturer.objects.all(),
268
+ to_field_name="name",
269
+ label="Manufacturer (name or ID)",
270
+ )
271
+ # TODO: solve https://github.com/nautobot/nautobot/issues/2875 to use this filter correctly
272
+ console_port_templates = NaturalKeyOrPKMultipleChoiceFilter(
273
+ prefers_id=True,
274
+ to_field_name="name",
275
+ queryset=ConsolePortTemplate.objects.all(),
276
+ label="Console port templates (name or ID)",
277
+ )
278
+ has_console_port_templates = RelatedMembershipBooleanFilter(
279
+ field_name="console_port_templates",
280
+ label="Has console port templates",
281
+ )
282
+ # TODO: solve https://github.com/nautobot/nautobot/issues/2875 to use this filter correctly
283
+ console_server_port_templates = NaturalKeyOrPKMultipleChoiceFilter(
284
+ prefers_id=True,
285
+ to_field_name="name",
286
+ queryset=ConsoleServerPortTemplate.objects.all(),
287
+ label="Console server port templates (name or ID)",
288
+ )
289
+ has_console_server_port_templates = RelatedMembershipBooleanFilter(
290
+ field_name="console_server_port_templates",
291
+ label="Has console server port templates",
292
+ )
293
+ # TODO: solve https://github.com/nautobot/nautobot/issues/2875 to use this filter correctly
294
+ power_port_templates = NaturalKeyOrPKMultipleChoiceFilter(
295
+ prefers_id=True,
296
+ to_field_name="name",
297
+ queryset=PowerPortTemplate.objects.all(),
298
+ label="Power port templates (name or ID)",
299
+ )
300
+ has_power_port_templates = RelatedMembershipBooleanFilter(
301
+ field_name="power_port_templates",
302
+ label="Has power port templates",
303
+ )
304
+ # TODO: solve https://github.com/nautobot/nautobot/issues/2875 to use this filter correctly
305
+ power_outlet_templates = NaturalKeyOrPKMultipleChoiceFilter(
306
+ prefers_id=True,
307
+ to_field_name="name",
308
+ queryset=PowerOutletTemplate.objects.all(),
309
+ label="Power outlet templates (name or ID)",
310
+ )
311
+ has_power_outlet_templates = RelatedMembershipBooleanFilter(
312
+ field_name="power_outlet_templates",
313
+ label="Has power outlet templates",
314
+ )
315
+ # TODO: solve https://github.com/nautobot/nautobot/issues/2875 to use this filter correctly
316
+ interface_templates = NaturalKeyOrPKMultipleChoiceFilter(
317
+ prefers_id=True,
318
+ to_field_name="name",
319
+ queryset=InterfaceTemplate.objects.all(),
320
+ label="Interface templates (name or ID)",
321
+ )
322
+ has_interface_templates = RelatedMembershipBooleanFilter(
323
+ field_name="interface_templates",
324
+ label="Has interface templates",
325
+ )
326
+ # TODO: solve https://github.com/nautobot/nautobot/issues/2875 to use this filter correctly
327
+ front_port_templates = NaturalKeyOrPKMultipleChoiceFilter(
328
+ prefers_id=True,
329
+ to_field_name="name",
330
+ queryset=FrontPortTemplate.objects.all(),
331
+ label="Front port templates (name or ID)",
332
+ )
333
+ has_front_port_templates = RelatedMembershipBooleanFilter(
334
+ field_name="front_port_templates",
335
+ label="Has front port templates",
336
+ )
337
+ # TODO: solve https://github.com/nautobot/nautobot/issues/2875 to use this filter correctly
338
+ rear_port_templates = NaturalKeyOrPKMultipleChoiceFilter(
339
+ prefers_id=True,
340
+ to_field_name="name",
341
+ queryset=RearPortTemplate.objects.all(),
342
+ label="Rear port templates (name or ID)",
343
+ )
344
+ has_rear_port_templates = RelatedMembershipBooleanFilter(
345
+ field_name="rear_port_templates",
346
+ label="Has rear port templates",
347
+ )
348
+ module_bay_templates = django_filters.ModelMultipleChoiceFilter(
349
+ queryset=ModuleBayTemplate.objects.all(),
350
+ )
351
+ has_module_bay_templates = RelatedMembershipBooleanFilter(
352
+ field_name="module_bay_templates",
353
+ label="Has module bay templates",
354
+ )
@@ -18,7 +18,6 @@ from nautobot.core.filters import (
18
18
  TreeNodeMultipleChoiceFilter,
19
19
  )
20
20
  from nautobot.core.utils.data import is_uuid
21
- from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
22
21
  from nautobot.dcim.choices import (
23
22
  CableTypeChoices,
24
23
  ConsolePortTypeChoices,
@@ -35,7 +34,7 @@ from nautobot.dcim.constants import (
35
34
  VIRTUAL_IFACE_TYPES,
36
35
  WIRELESS_IFACE_TYPES,
37
36
  )
38
- from nautobot.dcim.filters.mixins import (
37
+ from nautobot.dcim.filter_mixins import (
39
38
  CableTerminationModelFilterSetMixin,
40
39
  DeviceComponentModelFilterSetMixin,
41
40
  DeviceComponentTemplateModelFilterSetMixin,
@@ -104,14 +103,13 @@ from nautobot.extras.filters import (
104
103
  from nautobot.extras.models import ExternalIntegration, SecretsGroup
105
104
  from nautobot.extras.utils import FeatureQuery
106
105
  from nautobot.ipam.models import IPAddress, VLAN, VLANGroup
107
- from nautobot.tenancy.filters.mixins import TenancyModelFilterSetMixin
106
+ from nautobot.tenancy.filter_mixins import TenancyModelFilterSetMixin
108
107
  from nautobot.tenancy.models import Tenant
109
108
  from nautobot.virtualization.models import Cluster, VirtualMachine
110
109
  from nautobot.wireless.models import RadioProfile, WirelessNetwork
111
110
 
112
111
  __all__ = (
113
112
  "CableFilterSet",
114
- "CableTerminationFilterSet",
115
113
  "CableTerminationModelFilterSetMixin",
116
114
  "ConsoleConnectionFilterSet",
117
115
  "ConsolePortFilterSet",
@@ -145,7 +143,6 @@ __all__ = (
145
143
  "ModuleFamilyFilterSet",
146
144
  "ModuleFilterSet",
147
145
  "ModuleTypeFilterSet",
148
- "PathEndpointFilterSet",
149
146
  "PathEndpointModelFilterSetMixin",
150
147
  "PlatformFilterSet",
151
148
  "PowerConnectionFilterSet",
@@ -667,12 +664,6 @@ class DeviceTypeFilterSet(DeviceTypeModuleTypeCommonFiltersMixin, NautobotFilter
667
664
  return queryset.exclude(device_bay_templates__isnull=value)
668
665
 
669
666
 
670
- # TODO: remove in 2.2
671
- @class_deprecated_in_favor_of(DeviceComponentTemplateModelFilterSetMixin)
672
- class DeviceTypeComponentFilterSet(DeviceComponentTemplateModelFilterSetMixin):
673
- pass
674
-
675
-
676
667
  class ConsolePortTemplateFilterSet(ModularDeviceComponentTemplateModelFilterSetMixin, BaseFilterSet):
677
668
  class Meta:
678
669
  model = ConsolePortTemplate
@@ -978,24 +969,6 @@ class DeviceFilterSet(
978
969
  return queryset.filter(params)
979
970
 
980
971
 
981
- # TODO: remove in 2.2
982
- @class_deprecated_in_favor_of(DeviceComponentModelFilterSetMixin)
983
- class DeviceComponentFilterSet(DeviceComponentModelFilterSetMixin):
984
- pass
985
-
986
-
987
- # TODO: remove in 2.2
988
- @class_deprecated_in_favor_of(CableTerminationModelFilterSetMixin)
989
- class CableTerminationFilterSet(CableTerminationModelFilterSetMixin):
990
- pass
991
-
992
-
993
- # TODO: remove in 2.2
994
- @class_deprecated_in_favor_of(PathEndpointModelFilterSetMixin)
995
- class PathEndpointFilterSet(PathEndpointModelFilterSetMixin):
996
- pass
997
-
998
-
999
972
  class ConsolePortFilterSet(
1000
973
  ModularDeviceComponentModelFilterSetMixin,
1001
974
  CableTerminationModelFilterSetMixin,
@@ -1524,12 +1497,6 @@ class ConnectionFilterSetMixin:
1524
1497
  return queryset.filter(**{f"{name}__in": value})
1525
1498
 
1526
1499
 
1527
- # TODO: remove in 2.2
1528
- @class_deprecated_in_favor_of(ConnectionFilterSetMixin)
1529
- class ConnectionFilterSet(ConnectionFilterSetMixin):
1530
- pass
1531
-
1532
-
1533
1500
  class ConsoleConnectionFilterSet(ConnectionFilterSetMixin, BaseFilterSet):
1534
1501
  location = django_filters.CharFilter(
1535
1502
  method="filter_location",
nautobot/dcim/forms.py CHANGED
@@ -4577,7 +4577,7 @@ class BaseVCMemberFormSet(forms.BaseModelFormSet):
4577
4577
  vc_position_list.append(vc_position)
4578
4578
 
4579
4579
 
4580
- class DeviceVCMembershipForm(forms.ModelForm):
4580
+ class DeviceVCMembershipForm(BootstrapMixin, forms.ModelForm):
4581
4581
  class Meta:
4582
4582
  model = Device
4583
4583
  fields = [
@@ -0,0 +1,16 @@
1
+ # Generated by Django 4.2.24 on 2025-10-07 07:18
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("dcim", "0077_remove_device_cluster"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AlterUniqueTogether(
13
+ name="device",
14
+ unique_together={("rack", "position", "face"), ("virtual_chassis", "vc_position")},
15
+ ),
16
+ ]
@@ -0,0 +1,59 @@
1
+ # Generated by Django 4.2.24 on 2025-10-07 07:18
2
+
3
+ from constance.models import Constance
4
+ from django.db import migrations
5
+
6
+ from nautobot.core.utils.config import get_settings_or_config
7
+ from nautobot.dcim.choices import DeviceUniquenessChoices
8
+
9
+
10
+ def migrate_device_uniqueness_setting(apps, schema_editor):
11
+ """
12
+ Migrate deprecated DEVICE_NAME_AS_NATURAL_KEY setting to DEVICE_UNIQUENESS and DEVICE_NAME_REQUIRED.
13
+ """
14
+ try:
15
+ old_value = get_settings_or_config("DEVICE_NAME_AS_NATURAL_KEY")
16
+ except AttributeError:
17
+ old_value = False
18
+ new_value = DeviceUniquenessChoices.NAME if old_value else DeviceUniquenessChoices.LOCATION_TENANT_NAME
19
+
20
+ # Update or create DEVICE_UNIQUENESS in Constance
21
+ Constance.objects.update_or_create(
22
+ key="DEVICE_UNIQUENESS",
23
+ defaults={"value": new_value},
24
+ )
25
+
26
+ # cleanup: remove deprecated setting entry if it exists
27
+ Constance.objects.filter(key="DEVICE_NAME_AS_NATURAL_KEY").delete()
28
+
29
+
30
+ def reverse_migrate_device_uniqueness_setting(apps, schema_editor):
31
+ """
32
+ Reverse migration set DEVICE_NAME_AS_NATURAL_KEY based on DEVICE_UNIQUENESS.
33
+ """
34
+ try:
35
+ device_uniqueness = Constance.objects.get(key="DEVICE_UNIQUENESS").value
36
+ except Constance.DoesNotExist:
37
+ return
38
+
39
+ if device_uniqueness == DeviceUniquenessChoices.NAME:
40
+ Constance.objects.update_or_create(key="DEVICE_NAME_AS_NATURAL_KEY", defaults={"value": True})
41
+ else:
42
+ Constance.objects.update_or_create(key="DEVICE_NAME_AS_NATURAL_KEY", defaults={"value": False})
43
+
44
+ # cleanup: remove DEVICE_UNIQUENESS setting entry if it exists
45
+ Constance.objects.filter(key="DEVICE_UNIQUENESS").delete()
46
+
47
+
48
+ class Migration(migrations.Migration):
49
+ dependencies = [
50
+ ("dcim", "0078_remove_device_location_tenant_name_uniqueness"),
51
+ ("constance", "0003_drop_pickle"),
52
+ ]
53
+
54
+ operations = [
55
+ migrations.RunPython(
56
+ migrate_device_uniqueness_setting,
57
+ reverse_migrate_device_uniqueness_setting,
58
+ ),
59
+ ]