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
@@ -0,0 +1,160 @@
1
+ from django.test import TestCase
2
+
3
+ from nautobot.dcim.choices import InterfaceDuplexChoices, InterfaceSpeedChoices, InterfaceTypeChoices
4
+ from nautobot.dcim.models import Device, DeviceType, Interface, InterfaceTemplate, Location, LocationType, Manufacturer
5
+ from nautobot.dcim.tables.devices import DeviceModuleInterfaceTable, InterfaceTable
6
+ from nautobot.dcim.tables.devicetypes import InterfaceTemplateTable
7
+ from nautobot.extras.models import Role, Status
8
+
9
+
10
+ class InterfaceTableRenderMixin:
11
+ """Mixin for testing render_speed methods on interface tables."""
12
+
13
+ table_class = None
14
+
15
+ @classmethod
16
+ def setUpTestData(cls):
17
+ manufacturer = Manufacturer.objects.create(name="Test Manufacturer")
18
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model="Test Device Type")
19
+ device_role = Role.objects.get_for_model(Device).first()
20
+ location_type = LocationType.objects.get(name="Campus")
21
+ location = Location.objects.filter(location_type=location_type).first()
22
+ device_status = Status.objects.get_for_model(Device).first()
23
+ cls.interface_status = Status.objects.get_for_model(Interface).first()
24
+
25
+ cls.device = Device.objects.create(
26
+ name="Test Device",
27
+ device_type=device_type,
28
+ role=device_role,
29
+ location=location,
30
+ status=device_status,
31
+ )
32
+
33
+ def test_render_speed_duplex_with_value(self):
34
+ """Test that the table renders humanized speed values."""
35
+ interface = Interface.objects.create(
36
+ device=self.device,
37
+ name="eth0",
38
+ type=InterfaceTypeChoices.TYPE_1GE_FIXED,
39
+ status=self.interface_status,
40
+ speed=InterfaceSpeedChoices.SPEED_1G,
41
+ duplex=InterfaceDuplexChoices.DUPLEX_FULL,
42
+ )
43
+
44
+ queryset = Interface.objects.filter(pk=interface.pk)
45
+ table = self.table_class(queryset) # pylint: disable=not-callable
46
+ bound_row = table.rows[0]
47
+ rendered_speed = bound_row.get_cell("speed")
48
+ rendered_duplex = bound_row.get_cell("duplex")
49
+
50
+ self.assertEqual(rendered_speed, "1 Gbps")
51
+ self.assertEqual(rendered_duplex, "Full")
52
+
53
+ def test_render_speed_duplex_with_none(self):
54
+ """Test that the table handles None speed value and renders an emdash."""
55
+ emdash = "\u2014"
56
+ interface = Interface.objects.create(
57
+ device=self.device,
58
+ name="eth1",
59
+ type=InterfaceTypeChoices.TYPE_1GE_FIXED,
60
+ status=self.interface_status,
61
+ speed=None,
62
+ )
63
+
64
+ queryset = Interface.objects.filter(pk=interface.pk)
65
+ table = self.table_class(queryset) # pylint: disable=not-callable
66
+ bound_row = table.rows[0]
67
+ rendered_speed = bound_row.get_cell("speed")
68
+ rendered_duplex = bound_row.get_cell("duplex")
69
+
70
+ self.assertEqual(rendered_speed, emdash)
71
+ self.assertEqual(rendered_duplex, emdash)
72
+
73
+ def test_render_speed_various(self):
74
+ """Test that the table correctly humanizes various speed values."""
75
+ # Test all speed choices defined in InterfaceSpeedChoices
76
+ for speed_value, expected_output in InterfaceSpeedChoices.CHOICES:
77
+ with self.subTest(speed_value=speed_value, expected=expected_output):
78
+ interface = Interface.objects.create(
79
+ device=self.device,
80
+ name=f"eth-{speed_value}",
81
+ type=InterfaceTypeChoices.TYPE_1GE_FIXED,
82
+ status=self.interface_status,
83
+ speed=speed_value,
84
+ )
85
+
86
+ queryset = Interface.objects.filter(pk=interface.pk)
87
+ table = self.table_class(queryset) # pylint: disable=not-callable
88
+ bound_row = table.rows[0]
89
+ rendered_speed = bound_row.get_cell("speed")
90
+
91
+ self.assertEqual(rendered_speed, expected_output)
92
+
93
+ def test_render_duplex_various(self):
94
+ """Test that the table correctly renders various duplex values."""
95
+ for duplex_value, expected_output in InterfaceDuplexChoices.CHOICES:
96
+ with self.subTest(duplex_value=duplex_value, expected=expected_output):
97
+ interface = Interface.objects.create(
98
+ device=self.device,
99
+ name=f"eth-{duplex_value}",
100
+ type=InterfaceTypeChoices.TYPE_1GE_FIXED,
101
+ status=self.interface_status,
102
+ duplex=duplex_value,
103
+ )
104
+
105
+ queryset = Interface.objects.filter(pk=interface.pk)
106
+ table = self.table_class(queryset) # pylint: disable=not-callable
107
+ bound_row = table.rows[0]
108
+ rendered_duplex = bound_row.get_cell("duplex")
109
+
110
+ self.assertEqual(rendered_duplex, expected_output)
111
+
112
+
113
+ class InterfaceTableTestCase(InterfaceTableRenderMixin, TestCase):
114
+ """Test cases for InterfaceTable."""
115
+
116
+ table_class = InterfaceTable
117
+
118
+
119
+ class DeviceModuleInterfaceTableTestCase(InterfaceTableRenderMixin, TestCase):
120
+ """Test cases for DeviceModuleInterfaceTable."""
121
+
122
+ table_class = DeviceModuleInterfaceTable
123
+
124
+
125
+ class InterfaceTemplateTableTestCase(TestCase):
126
+ """Render tests for InterfaceTemplateTable speed/duplex columns."""
127
+
128
+ @classmethod
129
+ def setUpTestData(cls):
130
+ manufacturer = Manufacturer.objects.create(name="Test Manuf Tmpl")
131
+ cls.device_type = DeviceType.objects.create(manufacturer=manufacturer, model="DT-Tmpl")
132
+
133
+ def test_render_speed_duplex_with_value(self):
134
+ interface_template = InterfaceTemplate.objects.create(
135
+ device_type=self.device_type,
136
+ name="tmpl-eth0",
137
+ type=InterfaceTypeChoices.TYPE_1GE_FIXED,
138
+ speed=InterfaceSpeedChoices.SPEED_1G,
139
+ duplex=InterfaceDuplexChoices.DUPLEX_FULL,
140
+ )
141
+ table = InterfaceTemplateTable(InterfaceTemplate.objects.filter(pk=interface_template.pk))
142
+ bound_row = table.rows[0]
143
+ rendered_speed = bound_row.get_cell("speed") # pylint: disable=no-member
144
+ rendered_duplex = bound_row.get_cell("duplex") # pylint: disable=no-member
145
+ self.assertEqual(rendered_speed, "1 Gbps")
146
+ self.assertEqual(rendered_duplex, "Full")
147
+
148
+ def test_render_speed_duplex_with_none(self):
149
+ emdash = "\u2014"
150
+ interface_template = InterfaceTemplate.objects.create(
151
+ device_type=self.device_type,
152
+ name="tmpl-eth1",
153
+ type=InterfaceTypeChoices.TYPE_1GE_FIXED,
154
+ )
155
+ table = InterfaceTemplateTable(InterfaceTemplate.objects.filter(pk=interface_template.pk))
156
+ bound_row = table.rows[0]
157
+ rendered_speed = bound_row.get_cell("speed") # pylint: disable=no-member
158
+ rendered_duplex = bound_row.get_cell("duplex") # pylint: disable=no-member
159
+ self.assertEqual(rendered_speed, emdash)
160
+ self.assertEqual(rendered_duplex, emdash)
@@ -6,8 +6,7 @@ import zoneinfo
6
6
  from django.contrib.auth import get_user_model
7
7
  from django.contrib.contenttypes.models import ContentType
8
8
  from django.db.models import Q
9
- from django.test import override_settings, tag
10
- from django.test.client import RequestFactory
9
+ from django.test import override_settings
11
10
  from django.urls import reverse
12
11
  from django.utils.html import strip_spaces_between_tags
13
12
  from netaddr import EUI
@@ -31,6 +30,7 @@ from nautobot.dcim.choices import (
31
30
  ConsolePortTypeChoices,
32
31
  DeviceFaceChoices,
33
32
  DeviceRedundancyGroupFailoverStrategyChoices,
33
+ InterfaceDuplexChoices,
34
34
  InterfaceModeChoices,
35
35
  InterfaceRedundancyGroupProtocolChoices,
36
36
  InterfaceTypeChoices,
@@ -109,7 +109,6 @@ from nautobot.dcim.models import (
109
109
  from nautobot.dcim.views import (
110
110
  ConsoleConnectionsListView,
111
111
  InterfaceConnectionsListView,
112
- LocationUIViewSet,
113
112
  PowerConnectionsListView,
114
113
  )
115
114
  from nautobot.extras.choices import CustomFieldTypeChoices, RelationshipTypeChoices
@@ -211,8 +210,10 @@ class LocationTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase, ViewT
211
210
 
212
211
  class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
213
212
  model = Location
214
- # One query for the natural slug, one for `LocationViewSet.get_extra_context`
215
- allowed_number_of_tree_queries_per_view_type = {"retrieve": 2}
213
+ # One query for the natural slug, one for `LocationViewSet.get_extra_context`, and additional for distinct (which may be fixable?)
214
+ # See: https://github.com/nautobot/nautobot/pull/7530#discussion_r2432836062
215
+ # and https://github.com/nautobot/nautobot/pull/7530#discussion_r2432620239 for additional context
216
+ allowed_number_of_tree_queries_per_view_type = {"retrieve": 3}
216
217
 
217
218
  @classmethod
218
219
  def setUpTestData(cls):
@@ -442,7 +443,6 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
442
443
  self.assertEqual(location.contact_email, "")
443
444
 
444
445
  def test_get_extra_context(self):
445
- view_set = LocationUIViewSet()
446
446
  child_1, child_2 = Location.objects.filter(name__startswith="Leaf ")
447
447
  parent_location = child_1.parent
448
448
  child_1.location_type.content_types.add(ContentType.objects.get_for_model(Prefix))
@@ -451,12 +451,24 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
451
451
  prefix_2 = Prefix.objects.create(network="192.0.2.128", prefix_length=25, status=status)
452
452
  prefix_1.locations.set([child_1, child_2])
453
453
  prefix_2.locations.set([child_1, child_2])
454
- request = RequestFactory().get(parent_location.get_absolute_url())
455
- request.user = self.user
454
+
456
455
  self.add_permissions("dcim.view_location")
457
456
  self.add_permissions("ipam.view_prefix")
458
- context = view_set.get_extra_context(request=request, instance=parent_location)
459
- self.assertEqual(context["stats"]["prefix_count"], 2)
457
+
458
+ url = parent_location.get_absolute_url()
459
+ context = self.client.get(url).context
460
+ prefix_count = (
461
+ Prefix.objects.filter(location__in=parent_location.descendants(include_self=True)).distinct().count()
462
+ )
463
+
464
+ # Ensure that the context contains "stats" and the expected stat for prefix_list
465
+ self.assertIn("stats", context)
466
+ found = False
467
+ for stat in context["stats"].values():
468
+ if stat[0] == "ipam:prefix_list":
469
+ self.assertEqual(stat[1], prefix_count)
470
+ found = True
471
+ self.assertTrue(found, "ipam:prefix_list stat not found in context['stats']")
460
472
 
461
473
 
462
474
  class RackGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase, ViewTestCases.BulkEditObjectsViewTestCase):
@@ -1282,7 +1294,6 @@ class ModuleTypeTestCase(
1282
1294
  "comments": "changed comment",
1283
1295
  }
1284
1296
 
1285
- @tag("fix_in_v3")
1286
1297
  def test_list_has_correct_links(self):
1287
1298
  """Assert that the ModuleType list view has import/export buttons for both CSV and YAML/JSON formats."""
1288
1299
  self.add_permissions("dcim.add_moduletype", "dcim.view_moduletype")
@@ -1294,11 +1305,11 @@ class ModuleTypeTestCase(
1294
1305
  csv_import_url = job_import_url(ContentType.objects.get_for_model(ModuleType))
1295
1306
  # Dropdown provides both YAML/JSON and CSV import as options
1296
1307
  self.assertInHTML(
1297
- f'<a href="{yaml_import_url}"><span class="mdi mdi-database-import text-secondary" aria-hidden="true"></span> Import from JSON/YAML (single record)</a>',
1308
+ f'<a class="dropdown-item" href="{yaml_import_url}"><span class="mdi mdi-database-import text-secondary" aria-hidden="true"></span> Import from JSON/YAML (single record)</a>',
1298
1309
  content,
1299
1310
  )
1300
1311
  self.assertInHTML(
1301
- f'<a href="{csv_import_url}"><span class="mdi mdi-database-import text-secondary" aria-hidden="true"></span> Import from CSV (multiple records)</a>',
1312
+ f'<a class="dropdown-item" href="{csv_import_url}"><span class="mdi mdi-database-import text-secondary" aria-hidden="true"></span> Import from CSV (multiple records)</a>',
1302
1313
  content,
1303
1314
  )
1304
1315
 
@@ -1311,11 +1322,11 @@ class ModuleTypeTestCase(
1311
1322
  )
1312
1323
  self.assertInHTML('<input type="hidden" name="export_format" value="yaml">', content)
1313
1324
  self.assertInHTML(
1314
- '<button type="submit"><span class="mdi mdi-database-export text-secondary" aria-hidden="true"></span> Export as YAML</button>',
1325
+ '<button class="dropdown-item" type="submit"><span class="mdi mdi-database-export text-secondary" aria-hidden="true"></span> Export as YAML</button>',
1315
1326
  content,
1316
1327
  )
1317
1328
  self.assertInHTML(
1318
- '<button type="submit"><span class="mdi mdi-database-export text-secondary" aria-hidden="true"></span> Export as CSV</button>',
1329
+ '<button class="dropdown-item" type="submit"><span class="mdi mdi-database-export text-secondary" aria-hidden="true"></span> Export as CSV</button>',
1319
1330
  content,
1320
1331
  )
1321
1332
 
@@ -1827,6 +1838,68 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
1827
1838
  "description": "new test description",
1828
1839
  }
1829
1840
 
1841
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1842
+ def test_create_base_t_with_speed_and_duplex(self):
1843
+ self.add_permissions("dcim.add_interfacetemplate", "dcim.view_devicetype")
1844
+ url = reverse("dcim:interfacetemplate_add")
1845
+ dt = DeviceType.objects.first()
1846
+ data = {
1847
+ "device_type": dt.pk,
1848
+ "name_pattern": "Eth-View-1",
1849
+ "type": InterfaceTypeChoices.TYPE_1GE_FIXED,
1850
+ "mgmt_only": False,
1851
+ "speed": 1_000_000,
1852
+ "duplex": InterfaceDuplexChoices.DUPLEX_FULL,
1853
+ "_create": True,
1854
+ }
1855
+ response = self.client.post(url, data)
1856
+
1857
+ # Successful create redirects (JobResult or object list)
1858
+ self.assertIn(response.status_code, (302, 303))
1859
+ interface_template = InterfaceTemplate.objects.get(name="Eth-View-1")
1860
+ self.assertEqual(interface_template.speed, 1_000_000)
1861
+ self.assertEqual(interface_template.duplex, InterfaceDuplexChoices.DUPLEX_FULL)
1862
+
1863
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1864
+ def test_create_sfp_with_duplex_rejected(self):
1865
+ self.add_permissions("dcim.add_interfacetemplate", "dcim.view_devicetype")
1866
+ url = reverse("dcim:interfacetemplate_add")
1867
+ dt = DeviceType.objects.first()
1868
+ data = {
1869
+ "device_type": dt.pk,
1870
+ "name_pattern": "SFP-View-1",
1871
+ "type": InterfaceTypeChoices.TYPE_1GE_SFP,
1872
+ "duplex": InterfaceDuplexChoices.DUPLEX_FULL,
1873
+ "_create": True,
1874
+ }
1875
+ response = self.client.post(url, data)
1876
+ # Form error returns 200 with field error displayed
1877
+ self.assertEqual(response.status_code, 200)
1878
+ content = response.content.decode(response.charset)
1879
+ self.assertIn("Duplex is only applicable to copper twisted-pair interfaces.", content)
1880
+
1881
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1882
+ def test_bulk_create_with_speed_and_duplex(self):
1883
+ self.add_permissions("dcim.add_interfacetemplate", "dcim.view_devicetype")
1884
+ url = reverse("dcim:interfacetemplate_add")
1885
+ dt = DeviceType.objects.first()
1886
+ data = {
1887
+ "device_type": dt.pk,
1888
+ "name_pattern": "Et[1-2]",
1889
+ "type": InterfaceTypeChoices.TYPE_1GE_FIXED,
1890
+ "mgmt_only": False,
1891
+ "speed": 1_000_000,
1892
+ "duplex": InterfaceDuplexChoices.DUPLEX_FULL,
1893
+ "_apply": True,
1894
+ }
1895
+ response = self.client.post(url, data)
1896
+ self.assertIn(response.status_code, (302, 303))
1897
+ objs = InterfaceTemplate.objects.filter(name__in=["Et1", "Et2"]).order_by("name")
1898
+ self.assertEqual(objs.count(), 2)
1899
+ for obj in objs:
1900
+ self.assertEqual(obj.speed, 1_000_000)
1901
+ self.assertEqual(obj.duplex, InterfaceDuplexChoices.DUPLEX_FULL)
1902
+
1830
1903
 
1831
1904
  class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
1832
1905
  model = FrontPortTemplate
@@ -2186,7 +2259,7 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2186
2259
  )
2187
2260
  cls.custom_fields[0].content_types.set([ContentType.objects.get_for_model(Device)])
2188
2261
 
2189
- devices = (
2262
+ cls.devices = (
2190
2263
  Device.objects.create(
2191
2264
  name="Device 1",
2192
2265
  location=locations[0],
@@ -2251,16 +2324,40 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2251
2324
  intf_status = Status.objects.get_for_model(Interface).first()
2252
2325
  intf_role = Role.objects.get_for_model(Interface).first()
2253
2326
  cls.interfaces = (
2254
- Interface.objects.create(device=devices[0], name="Interface A1", status=intf_status, role=intf_role),
2255
- Interface.objects.create(device=devices[0], name="Interface A2", status=intf_status),
2256
- Interface.objects.create(device=devices[0], name="Interface A3", status=intf_status, role=intf_role),
2327
+ Interface.objects.create(device=cls.devices[0], name="Interface A1", status=intf_status, role=intf_role),
2328
+ Interface.objects.create(device=cls.devices[0], name="Interface A2", status=intf_status),
2329
+ Interface.objects.create(device=cls.devices[0], name="Interface A3", status=intf_status, role=intf_role),
2257
2330
  )
2258
2331
 
2259
- for device, ipaddress in zip(devices, ipaddresses):
2332
+ for device, ipaddress in zip(cls.devices, ipaddresses):
2260
2333
  RelationshipAssociation(
2261
2334
  relationship=cls.relationships[0], source=device, destination=ipaddress
2262
2335
  ).validated_save()
2263
2336
 
2337
+ powerports = (
2338
+ PowerPort.objects.create(device=cls.devices[0], name="Power Port 1"),
2339
+ PowerPort.objects.create(device=cls.devices[0], name="Power Port 2"),
2340
+ PowerPort.objects.create(device=cls.devices[0], name="Power Port 3"),
2341
+ )
2342
+ PowerOutlet.objects.create(device=cls.devices[0], name="Power Outlet A")
2343
+
2344
+ powerpanel = PowerPanel.objects.create(location=locations[0], name="Power Panel 1")
2345
+ pf_status = Status.objects.get_for_model(PowerFeed).first()
2346
+ powerfeed = PowerFeed.objects.create(
2347
+ power_panel=powerpanel,
2348
+ name="Power Feed 1",
2349
+ status=pf_status,
2350
+ available_power=1000,
2351
+ phase=PowerFeedPhaseChoices.PHASE_3PHASE,
2352
+ )
2353
+
2354
+ status_connected = Status.objects.get(name="Connected")
2355
+
2356
+ Cable.objects.create(termination_a=powerports[0], termination_b=powerfeed, status=status_connected)
2357
+
2358
+ poweroutlet = PowerOutlet.objects.create(device=cls.devices[1], name="Power Outlet 1")
2359
+ Cable.objects.create(termination_a=powerports[1], termination_b=poweroutlet, status=status_connected)
2360
+
2264
2361
  cls.form_data = {
2265
2362
  "device_type": devicetypes[1].pk,
2266
2363
  "role": deviceroles[1].pk,
@@ -2408,11 +2505,7 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2408
2505
 
2409
2506
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
2410
2507
  def test_device_powerports(self):
2411
- device = Device.objects.first()
2412
-
2413
- PowerPort.objects.create(device=device, name="Power Port 1")
2414
- PowerPort.objects.create(device=device, name="Power Port 2")
2415
- PowerPort.objects.create(device=device, name="Power Port 3")
2508
+ device = self.devices[0]
2416
2509
 
2417
2510
  url = reverse("dcim:device_powerports", kwargs={"pk": device.pk})
2418
2511
  self.assertHttpStatus(self.client.get(url), 200)
@@ -2596,6 +2689,15 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2596
2689
  response = self.client.get(url)
2597
2690
  self.assertHttpStatus(response, 200)
2598
2691
 
2692
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
2693
+ def test_device_vpnendpoints(self):
2694
+ self.add_permissions("vpn.view_vpntunnelendpoint")
2695
+ device = Device.objects.first()
2696
+
2697
+ url = reverse("dcim:device_vpnendpoints", kwargs={"pk": device.pk})
2698
+ response = self.client.get(url)
2699
+ self.assertHttpStatus(response, 200)
2700
+
2599
2701
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
2600
2702
  def test_device_napalm_tabs(self):
2601
2703
  self.add_permissions("dcim.napalm_read_device")
@@ -4076,8 +4178,8 @@ class PowerConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
4076
4178
  )
4077
4179
 
4078
4180
  poweroutlets = (
4079
- PowerOutlet.objects.create(device=device_2, name="Power Outlet 1", power_port=powerports[0]),
4080
- PowerOutlet.objects.create(device=device_2, name="Power Outlet 2", power_port=powerports[1]),
4181
+ PowerOutlet.objects.create(device=device_2, name="Power Outlet 1"),
4182
+ PowerOutlet.objects.create(device=device_2, name="Power Outlet 2"),
4081
4183
  )
4082
4184
 
4083
4185
  powerpanel = PowerPanel.objects.create(location=location, name="Power Panel 1")
@@ -4552,7 +4654,8 @@ class PathTraceViewTestCase(ModelViewTestCase):
4552
4654
  url = reverse("dcim:rearport_trace", args=[obj.pk])
4553
4655
  cablepath_id = CablePath.objects.first().id
4554
4656
  response = self.client.get(url + f"?cablepath_id={cablepath_id}")
4555
- self.assertBodyContains(response, "<h1>Cable Trace for Rear Port Rear Port 1</h1>", html=True)
4657
+ self.assertBodyContains(response, "Rear Port 1")
4658
+ self.assertBodyContains(response, "eth0")
4556
4659
 
4557
4660
 
4558
4661
  class DeviceRedundancyGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
@@ -4781,7 +4884,7 @@ class ControllerTestCase(ViewTestCases.PrimaryObjectViewTestCase):
4781
4884
  model = Controller
4782
4885
  filterset = ControllerFilterSet
4783
4886
  custom_action_required_permissions = {
4784
- "dcim:controller_wirelessnetworks": [
4887
+ "dcim:controller_wireless_networks": [
4785
4888
  "dcim.view_controller",
4786
4889
  "wireless.view_controllermanageddevicegroupwirelessnetworkassignment",
4787
4890
  ],
nautobot/dcim/urls.py CHANGED
@@ -7,7 +7,6 @@ from nautobot.ipam.views import ServiceEditView
7
7
 
8
8
  from . import views
9
9
  from .models import (
10
- Cable,
11
10
  ConsolePort,
12
11
  ConsoleServerPort,
13
12
  Device,
@@ -26,6 +25,7 @@ from .models import (
26
25
  app_name = "dcim"
27
26
 
28
27
  router = NautobotUIViewSetRouter()
28
+ router.register("cables", views.CableUIViewSet)
29
29
  router.register("controller-managed-device-groups", views.ControllerManagedDeviceGroupUIViewSet)
30
30
  router.register("controllers", views.ControllerUIViewSet)
31
31
  router.register("device-families", views.DeviceFamilyUIViewSet)
@@ -84,6 +84,69 @@ urlpatterns = [
84
84
  views.DeviceTypeImportView.as_view(),
85
85
  name="devicetype_import",
86
86
  ),
87
+ path(
88
+ "device-types/<uuid:pk>/console-port-templates/add/",
89
+ RedirectView.as_view(
90
+ url="/dcim/console-port-templates/add/?device_type=%(pk)s&return_url=/dcim/device-types/%(pk)s/console-ports/"
91
+ ),
92
+ name="devicetype_consoleporttemplate_add",
93
+ ),
94
+ path(
95
+ "devices-types/<uuid:pk>/console-server-port-templates/add/",
96
+ RedirectView.as_view(
97
+ url="/dcim/console-server-port-templates/add/?device_type=%(pk)s&return_url=/dcim/device-types/%(pk)s/console-server-ports/"
98
+ ),
99
+ name="devicetype_consoleserverporttemplate_add",
100
+ ),
101
+ path(
102
+ "device-types/<uuid:pk>/power-port-templates/add/",
103
+ RedirectView.as_view(
104
+ url="/dcim/power-port-templates/add/?device_type=%(pk)s&return_url=/dcim/device-types/%(pk)s/power-ports/"
105
+ ),
106
+ name="devicetype_powerporttemplate_add",
107
+ ),
108
+ path(
109
+ "device-types/<uuid:pk>/power-outlet-templates/add/",
110
+ RedirectView.as_view(
111
+ url="/dcim/power-outlet-templates/add/?device_type=%(pk)s&return_url=/dcim/device-types/%(pk)s/power-outlets/"
112
+ ),
113
+ name="devicetype_poweroutlettemplate_add",
114
+ ),
115
+ path(
116
+ "device-types/<uuid:pk>/interface-templates/add/",
117
+ RedirectView.as_view(
118
+ url="/dcim/interface-templates/add/?device_type=%(pk)s&return_url=/dcim/device-types/%(pk)s/interfaces/"
119
+ ),
120
+ name="devicetype_interfacetemplate_add",
121
+ ),
122
+ path(
123
+ "device-types/<uuid:pk>/front-port-templates/add/",
124
+ RedirectView.as_view(
125
+ url="/dcim/front-port-templates/add/?device_type=%(pk)s&return_url=/dcim/device-types/%(pk)s/front-ports/"
126
+ ),
127
+ name="devicetype_frontporttemplate_add",
128
+ ),
129
+ path(
130
+ "device-types/<uuid:pk>/rear-port-templates/add/",
131
+ RedirectView.as_view(
132
+ url="/dcim/rear-port-templates/add/?device_type=%(pk)s&return_url=/dcim/device-types/%(pk)s/rear-ports/"
133
+ ),
134
+ name="devicetype_rearporttemplate_add",
135
+ ),
136
+ path(
137
+ "device-types/<uuid:pk>/device-bay-templates/add/",
138
+ RedirectView.as_view(
139
+ url="/dcim/device-bay-templates/add/?device_type=%(pk)s&return_url=/dcim/device-types/%(pk)s/device-bays/"
140
+ ),
141
+ name="devicetype_devicebaytemplate_add",
142
+ ),
143
+ path(
144
+ "device-types/<uuid:pk>/module-bay-templates/add/",
145
+ RedirectView.as_view(
146
+ url="/dcim/module-bay-templates/add/?device_type=%(pk)s&return_url=/dcim/device-types/%(pk)s/module-bays/"
147
+ ),
148
+ name="devicetype_modulebaytemplate_add",
149
+ ),
87
150
  # Console port templates
88
151
  path(
89
152
  "console-port-templates/add/",
@@ -1024,26 +1087,6 @@ urlpatterns = [
1024
1087
  RedirectView.as_view(url="/dcim/inventory-items/add/?device=%(pk)s&return_url=/dcim/devices/%(pk)s/inventory/"),
1025
1088
  name="device_inventoryitems_add",
1026
1089
  ),
1027
- # Cables
1028
- path("cables/", views.CableListView.as_view(), name="cable_list"),
1029
- path("cables/import/", views.CableBulkImportView.as_view(), name="cable_import"), # 3.0 TODO: remove, unused
1030
- path("cables/edit/", views.CableBulkEditView.as_view(), name="cable_bulk_edit"),
1031
- path("cables/delete/", views.CableBulkDeleteView.as_view(), name="cable_bulk_delete"),
1032
- path("cables/<uuid:pk>/", views.CableView.as_view(), name="cable"),
1033
- path("cables/<uuid:pk>/edit/", views.CableEditView.as_view(), name="cable_edit"),
1034
- path("cables/<uuid:pk>/delete/", views.CableDeleteView.as_view(), name="cable_delete"),
1035
- path(
1036
- "cables/<uuid:pk>/changelog/",
1037
- ObjectChangeLogView.as_view(),
1038
- name="cable_changelog",
1039
- kwargs={"model": Cable},
1040
- ),
1041
- path(
1042
- "cables/<uuid:pk>/notes/",
1043
- ObjectNotesView.as_view(),
1044
- name="cable_notes",
1045
- kwargs={"model": Cable},
1046
- ),
1047
1090
  # Console/power/interface connections (read-only)
1048
1091
  path(
1049
1092
  "console-connections/",
nautobot/dcim/utils.py CHANGED
@@ -49,9 +49,9 @@ def cable_status_color_css(record):
49
49
  return ""
50
50
  else:
51
51
  CABLE_STATUS_TO_CSS_CLASS = {
52
- ColorChoices.COLOR_GREEN: "success",
53
- ColorChoices.COLOR_AMBER: "warning",
54
- ColorChoices.COLOR_CYAN: "info",
52
+ ColorChoices.COLOR_GREEN: "table-success",
53
+ ColorChoices.COLOR_AMBER: "table-warning",
54
+ ColorChoices.COLOR_CYAN: "table-info",
55
55
  }
56
56
  status_color = record.cable.get_status_color().strip("#")
57
57
  return CABLE_STATUS_TO_CSS_CLASS.get(status_color, "")