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
@@ -6,7 +6,7 @@ from django.contrib.contenttypes.models import ContentType
6
6
  from django.core.cache import cache
7
7
  from django.core.exceptions import ObjectDoesNotExist, ValidationError
8
8
  from django.core.validators import MaxValueValidator, MinValueValidator
9
- from django.db import models, transaction
9
+ from django.db import models
10
10
  from django.db.models import Sum
11
11
  from django.utils.functional import classproperty
12
12
 
@@ -596,6 +596,86 @@ class BaseInterface(RelationshipModel):
596
596
 
597
597
  return super().save(*args, **kwargs)
598
598
 
599
+ def add_ip_addresses(
600
+ self,
601
+ ip_addresses,
602
+ is_source=False,
603
+ is_destination=False,
604
+ is_default=False,
605
+ is_preferred=False,
606
+ is_primary=False,
607
+ is_secondary=False,
608
+ is_standby=False,
609
+ ):
610
+ """Add one or more IPAddress instances to this interface's `ip_addresses` many-to-many relationship.
611
+
612
+ Args:
613
+ ip_addresses (:obj:`list` or `IPAddress`): Instance of `nautobot.ipam.models.IPAddress` or list of `IPAddress` instances.
614
+ is_source (bool, optional): Is source address. Defaults to False.
615
+ is_destination (bool, optional): Is destination address. Defaults to False.
616
+ is_default (bool, optional): Is default address. Defaults to False.
617
+ is_preferred (bool, optional): Is preferred address. Defaults to False.
618
+ is_primary (bool, optional): Is primary address. Defaults to False.
619
+ is_secondary (bool, optional): Is secondary address. Defaults to False.
620
+ is_standby (bool, optional): Is standby address. Defaults to False.
621
+
622
+ Returns:
623
+ Number of instances added.
624
+ """
625
+ through_defaults = {
626
+ "is_source": is_source,
627
+ "is_destination": is_destination,
628
+ "is_default": is_default,
629
+ "is_preferred": is_preferred,
630
+ "is_primary": is_primary,
631
+ "is_secondary": is_secondary,
632
+ "is_standby": is_standby,
633
+ }
634
+
635
+ if not isinstance(ip_addresses, (tuple, list)):
636
+ ip_addresses = [ip_addresses]
637
+
638
+ # This ensures that ips_to_add only contains IPs which need to be added to the interface. This ensures
639
+ # that len(ips_to_add) accurately represents the results of the action.
640
+ ips_to_add = set(ip_addresses) - set(self.ip_addresses.all())
641
+
642
+ if ips_to_add:
643
+ self.ip_addresses.add(*ips_to_add, through_defaults=through_defaults) # pylint: disable=no-member # Intf/VMIntf both have ip_addresses
644
+
645
+ return len(ips_to_add)
646
+
647
+ add_ip_addresses.alters_data = True
648
+
649
+ def remove_ip_addresses(self, ip_addresses):
650
+ """Remove one or more IPAddress instances from this interface's `ip_addresses` many-to-many relationship.
651
+
652
+ Args:
653
+ ip_addresses (:obj:`list` or `IPAddress`): Instance of `nautobot.ipam.models.IPAddress` or list of `IPAddress` instances.
654
+
655
+ Returns:
656
+ Number of instances removed.
657
+ """
658
+ if not isinstance(ip_addresses, (tuple, list)):
659
+ ip_addresses = [ip_addresses]
660
+
661
+ # The delete() call used previously (ref: https://github.com/nautobot/nautobot/issues/3236)
662
+ # meant that if None was passed in, it was silently ignored. Rather that raise an exception,
663
+ # this comprehension maintains backwards compatibility.
664
+ ip_addresses = {ip for ip in ip_addresses if ip is not None}
665
+
666
+ # This checks that the IPs passed in are actually on the interface. By populating
667
+ # ips_to_remove correctly, we ensure that the only IPs passed to remove() are IPs known
668
+ # to be on the interface. This ensures that len(ips_to_remove) accurately represents
669
+ # the results of the action.
670
+ ips_to_remove = ip_addresses & set(self.ip_addresses.all())
671
+
672
+ if ips_to_remove:
673
+ self.ip_addresses.remove(*ips_to_remove) # pylint: disable=no-member # Intf/VMIntf both have ip_addresses
674
+
675
+ return len(ips_to_remove)
676
+
677
+ remove_ip_addresses.alters_data = True
678
+
599
679
 
600
680
  @extras_features(
601
681
  "cable_terminations",
@@ -798,73 +878,6 @@ class Interface(ModularComponentModel, CableTermination, PathEndpoint, BaseInter
798
878
  }
799
879
  )
800
880
 
801
- def add_ip_addresses(
802
- self,
803
- ip_addresses,
804
- is_source=False,
805
- is_destination=False,
806
- is_default=False,
807
- is_preferred=False,
808
- is_primary=False,
809
- is_secondary=False,
810
- is_standby=False,
811
- ):
812
- """Add one or more IPAddress instances to this interface's `ip_addresses` many-to-many relationship.
813
-
814
- Args:
815
- ip_addresses (:obj:`list` or `IPAddress`): Instance of `nautobot.ipam.models.IPAddress` or list of `IPAddress` instances.
816
- is_source (bool, optional): Is source address. Defaults to False.
817
- is_destination (bool, optional): Is destination address. Defaults to False.
818
- is_default (bool, optional): Is default address. Defaults to False.
819
- is_preferred (bool, optional): Is preferred address. Defaults to False.
820
- is_primary (bool, optional): Is primary address. Defaults to False.
821
- is_secondary (bool, optional): Is secondary address. Defaults to False.
822
- is_standby (bool, optional): Is standby address. Defaults to False.
823
-
824
- Returns:
825
- Number of instances added.
826
- """
827
- if not isinstance(ip_addresses, (tuple, list)):
828
- ip_addresses = [ip_addresses]
829
- with transaction.atomic():
830
- for ip in ip_addresses:
831
- instance = self.ip_addresses.through(
832
- ip_address=ip,
833
- interface=self,
834
- is_source=is_source,
835
- is_destination=is_destination,
836
- is_default=is_default,
837
- is_preferred=is_preferred,
838
- is_primary=is_primary,
839
- is_secondary=is_secondary,
840
- is_standby=is_standby,
841
- )
842
- instance.validated_save()
843
- return len(ip_addresses)
844
-
845
- add_ip_addresses.alters_data = True
846
-
847
- def remove_ip_addresses(self, ip_addresses):
848
- """Remove one or more IPAddress instances from this interface's `ip_addresses` many-to-many relationship.
849
-
850
- Args:
851
- ip_addresses (:obj:`list` or `IPAddress`): Instance of `nautobot.ipam.models.IPAddress` or list of `IPAddress` instances.
852
-
853
- Returns:
854
- Number of instances removed.
855
- """
856
- count = 0
857
- if not isinstance(ip_addresses, (tuple, list)):
858
- ip_addresses = [ip_addresses]
859
- with transaction.atomic():
860
- for ip in ip_addresses:
861
- qs = self.ip_addresses.through.objects.filter(ip_address=ip, interface=self)
862
- deleted_count, _ = qs.delete()
863
- count += deleted_count
864
- return count
865
-
866
- remove_ip_addresses.alters_data = True
867
-
868
881
  @property
869
882
  def is_connectable(self):
870
883
  return self.type not in NONCONNECTABLE_IFACE_TYPES
@@ -25,6 +25,7 @@ from nautobot.dcim.choices import (
25
25
  ControllerCapabilitiesChoices,
26
26
  DeviceFaceChoices,
27
27
  DeviceRedundancyGroupFailoverStrategyChoices,
28
+ DeviceUniquenessChoices,
28
29
  SoftwareImageFileHashingAlgorithmChoices,
29
30
  SubdeviceRoleChoices,
30
31
  )
@@ -682,19 +683,20 @@ class Device(PrimaryModel, ConfigContextModel):
682
683
  @classproperty # https://github.com/PyCQA/pylint-django/issues/240
683
684
  def natural_key_field_names(cls): # pylint: disable=no-self-argument
684
685
  """
685
- When DEVICE_NAME_AS_NATURAL_KEY is set in settings or Constance, we use just the `name` for simplicity.
686
+ Check DEVICE_UNIQUENESS from settings or Constance and return proper field.
686
687
  """
687
- if get_settings_or_config("DEVICE_NAME_AS_NATURAL_KEY"):
688
- # opt-in simplified "pseudo-natural-key"
688
+ if get_settings_or_config("DEVICE_UNIQUENESS") == DeviceUniquenessChoices.NAME:
689
+ # Simplified pseudo-natural key (opt-in for name-only uniqueness)
689
690
  return ["name"]
691
+ elif get_settings_or_config("DEVICE_UNIQUENESS") == DeviceUniquenessChoices.LOCATION_TENANT_NAME:
692
+ # Full natural key based on tenant, location, and name
693
+ return ["name", "tenant", "location"]
690
694
  else:
691
- # true natural-key given current uniqueness constraints
692
- return ["name", "tenant", "location"] # location should be last since it's potentially variadic
695
+ return ["pk"]
693
696
 
694
697
  class Meta:
695
698
  ordering = ("_name",) # Name may be null
696
699
  unique_together = (
697
- ("location", "tenant", "name"), # See validate_unique below
698
700
  ("rack", "position", "face"),
699
701
  ("virtual_chassis", "vc_position"),
700
702
  )
@@ -716,16 +718,6 @@ class Device(PrimaryModel, ConfigContextModel):
716
718
  else:
717
719
  self.clusters.set([cluster])
718
720
 
719
- def validate_unique(self, exclude=None):
720
- # Check for a duplicate name on a device assigned to the same Location and no Tenant. This is necessary
721
- # because Django does not consider two NULL fields to be equal, and thus will not trigger a violation
722
- # of the uniqueness constraint without manual intervention.
723
- if self.name and hasattr(self, "location") and self.tenant is None:
724
- if Device.objects.exclude(pk=self.pk).filter(name=self.name, location=self.location, tenant__isnull=True):
725
- raise ValidationError({"name": "A device with this name already exists."})
726
-
727
- super().validate_unique(exclude)
728
-
729
721
  def clean(self):
730
722
  from nautobot.ipam import models as ipam_models # circular import workaround
731
723
 
@@ -909,6 +901,11 @@ class Device(PrimaryModel, ConfigContextModel):
909
901
  def save(self, *args, **kwargs):
910
902
  is_new = not self.present_in_database
911
903
 
904
+ # to avoid circular import
905
+ from nautobot.dcim.custom_validators import DeviceUniquenessValidator
906
+
907
+ DeviceUniquenessValidator(self).clean()
908
+
912
909
  super().save(*args, **kwargs)
913
910
 
914
911
  # Apply any pending cluster assignment that was deferred during creation
@@ -4,12 +4,13 @@ from nautobot.core.apps import (
4
4
  NavMenuItem,
5
5
  NavMenuTab,
6
6
  )
7
+ from nautobot.core.ui.choices import NavigationIconChoices, NavigationWeightChoices
7
8
 
8
9
  menu_items = (
9
10
  NavMenuTab(
10
11
  name="Organization",
11
- icon="briefcase-2",
12
- weight=100,
12
+ icon=NavigationIconChoices.ORGANIZATION,
13
+ weight=NavigationWeightChoices.ORGANIZATION,
13
14
  groups=(
14
15
  NavMenuGroup(
15
16
  name="Locations",
@@ -116,8 +117,8 @@ menu_items = (
116
117
  ),
117
118
  NavMenuTab(
118
119
  name="Devices",
119
- icon="laptop",
120
- weight=200,
120
+ icon=NavigationIconChoices.DEVICES,
121
+ weight=NavigationWeightChoices.DEVICES,
121
122
  groups=(
122
123
  NavMenuGroup(
123
124
  name="Devices",
@@ -549,8 +550,8 @@ menu_items = (
549
550
  ),
550
551
  NavMenuTab(
551
552
  name="Power",
552
- icon="lightning",
553
- weight=600,
553
+ icon=NavigationIconChoices.POWER,
554
+ weight=NavigationWeightChoices.POWER,
554
555
  groups=(
555
556
  NavMenuGroup(
556
557
  name="Power",
@@ -295,6 +295,7 @@ class DeviceImportTable(StatusTableMixin, RoleTableMixin, BaseTable):
295
295
 
296
296
  class ModuleTable(StatusTableMixin, RoleTableMixin, BaseTable):
297
297
  pk = ToggleColumn()
298
+ id = tables.Column(linkify=True, verbose_name="ID")
298
299
  module_type = tables.Column(
299
300
  linkify=lambda record: record.module_type.get_absolute_url(),
300
301
  verbose_name="Type",
@@ -315,6 +316,7 @@ class ModuleTable(StatusTableMixin, RoleTableMixin, BaseTable):
315
316
  model = Module
316
317
  fields = (
317
318
  "pk",
319
+ "id",
318
320
  "module_type",
319
321
  "module_family",
320
322
  "parent_module_bay",
@@ -329,6 +331,7 @@ class ModuleTable(StatusTableMixin, RoleTableMixin, BaseTable):
329
331
  )
330
332
  default_columns = (
331
333
  "pk",
334
+ "id",
332
335
  "module_type",
333
336
  "module_family",
334
337
  "parent_module_bay",
@@ -137,8 +137,8 @@ CONSOLEPORT_BUTTONS = """
137
137
  <li><a href="{% url 'dcim:consoleport_trace' pk=record.pk %}" class="dropdown-item text-primary"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
138
138
  {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
139
139
  {% elif perms.dcim.add_cable %}
140
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
141
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
140
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
141
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
142
142
  <li>
143
143
  <a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='console-server-port' %}?return_url={{ request.path }}" class="dropdown-item text-success">
144
144
  <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
@@ -165,8 +165,8 @@ CONSOLESERVERPORT_BUTTONS = """
165
165
  <li><a href="{% url 'dcim:consoleserverport_trace' pk=record.pk %}" class="dropdown-item text-primary"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
166
166
  {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
167
167
  {% elif perms.dcim.add_cable %}
168
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
169
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
168
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
169
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
170
170
  <li>
171
171
  <a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='console-port' %}?return_url={{ request.path }}" class="dropdown-item text-success">
172
172
  <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
@@ -193,8 +193,8 @@ POWERPORT_BUTTONS = """
193
193
  <li><a href="{% url 'dcim:powerport_trace' pk=record.pk %}" class="dropdown-item text-primary"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
194
194
  {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
195
195
  {% elif perms.dcim.add_cable %}
196
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
197
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
196
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
197
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
198
198
  <li>
199
199
  <a href="{% url 'dcim:powerport_connect' termination_a_id=record.pk termination_b_type='power-outlet' %}?return_url={{ request.path }}" class="dropdown-item text-success">
200
200
  <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
@@ -215,8 +215,8 @@ POWEROUTLET_BUTTONS = """
215
215
  <li><a href="{% url 'dcim:poweroutlet_trace' pk=record.pk %}" class="dropdown-item text-primary"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
216
216
  {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
217
217
  {% elif perms.dcim.add_cable %}
218
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
219
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
218
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
219
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
220
220
  <li>
221
221
  <a href="{% url 'dcim:poweroutlet_connect' termination_a_id=record.pk termination_b_type='power-port' %}?return_url={{ request.path }}" class="dropdown-item text-success">
222
222
  <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
@@ -239,8 +239,8 @@ INTERFACE_BUTTONS = """
239
239
  <li><a href="{% url 'dcim:interface_trace' pk=record.pk %}" class="dropdown-item text-primary"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span><Trace/a></li>
240
240
  {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
241
241
  {% elif record.is_connectable and perms.dcim.add_cable %}
242
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
243
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
242
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
243
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
244
244
  <li>
245
245
  <a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={{ request.path }}" class="dropdown-item text-success">
246
246
  <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
@@ -273,8 +273,8 @@ FRONTPORT_BUTTONS = """
273
273
  <li><a href="{% url 'dcim:frontport_trace' pk=record.pk %}" class="dropdown-item text-primary"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
274
274
  {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
275
275
  {% elif perms.dcim.add_cable %}
276
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
277
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
276
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
277
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
278
278
  <li>
279
279
  <a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={{ request.path }}" class="dropdown-item text-success">
280
280
  <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
@@ -319,8 +319,8 @@ REARPORT_BUTTONS = """
319
319
  <li><a href="{% url 'dcim:rearport_trace' pk=record.pk %}" class="dropdown-item text-primary"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
320
320
  {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
321
321
  {% elif perms.dcim.add_cable %}
322
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
323
- <li><a href="#" class="dropdown-item disabled"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
322
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-transit-connection-variant" aria-hidden="true"></span>Trace</a></li>
323
+ <li><a class="dropdown-item disabled" aria-disabled="true"><span class="mdi mdi-lan-connect" aria-hidden="true"></span>Mark installed</a></li>
324
324
  <li>
325
325
  <a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={{ request.path }}" class="dropdown-item text-success">
326
326
  <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
@@ -1,61 +1,2 @@
1
- {% extends 'generic/object_retrieve.html' %}
2
- {% load helpers %}
3
-
4
- {% block content_left_page %}
5
- <div class="card">
6
- <div class="card-header">
7
- <strong>Cable</strong>
8
- </div>
9
- <table class="table table-hover card-body attr-table">
10
- <tr>
11
- <td>Type</td>
12
- <td>{{ object.get_type_display|placeholder }}</td>
13
- </tr>
14
- <tr>
15
- <td>Status</td>
16
- <td>
17
- {{ object.status| hyperlinked_object_with_color }}
18
- </td>
19
- </tr>
20
- <tr>
21
- <td>Label</td>
22
- <td>{{ object.label|placeholder }}</td>
23
- </tr>
24
- <tr>
25
- <td>Color</td>
26
- <td>
27
- {% if object.color %}
28
- <span class="label nb-color-block" style="background-color: #{{ object.color }}">&nbsp;</span>
29
- {% else %}
30
- <span class="text-secondary">&mdash;</span>
31
- {% endif %}
32
- </td>
33
- </tr>
34
- <tr>
35
- <td>Length</td>
36
- <td>
37
- {% if object.length %}
38
- {{ object.length }} {{ object.get_length_unit_display }}
39
- {% else %}
40
- <span class="text-secondary">&mdash;</span>
41
- {% endif %}
42
- </td>
43
- </tr>
44
- </table>
45
- </div>
46
- {% endblock content_left_page %}
47
-
48
- {% block content_right_page %}
49
- <div class="card">
50
- <div class="card-header">
51
- <strong>Termination A</strong>
52
- </div>
53
- {% include 'dcim/inc/cable_termination.html' with termination=object.termination_a %}
54
- </div>
55
- <div class="card">
56
- <div class="card-header">
57
- <strong>Termination B</strong>
58
- </div>
59
- {% include 'dcim/inc/cable_termination.html' with termination=object.termination_b %}
60
- </div>
61
- {% endblock content_right_page %}
1
+ {% extends 'dcim/cable_retrieve.html' %}
2
+ {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
@@ -3,6 +3,16 @@
3
3
  {% load helpers %}
4
4
  {% load form_helpers %}
5
5
 
6
+ {% block title %}
7
+ {% with termination_a=form.instance.termination_a %}
8
+ {% if termination_a.module %}
9
+ Connect {{ termination_a.module.display }} {{ termination_a }} to {{ termination_b_type|bettertitle }}
10
+ {% else %}
11
+ Connect {{ termination_a.device }} {{ termination_a }} to {{ termination_b_type|bettertitle }}
12
+ {% endif %}
13
+ {% endwith %}
14
+ {% endblock %}
15
+
6
16
  {% block content %}
7
17
  <form method="post" class="h-100 vstack">
8
18
  {% csrf_token %}
@@ -21,15 +31,6 @@
21
31
  </div>
22
32
  {% endif %}
23
33
  {% with termination_a=form.instance.termination_a %}
24
- <h3 class="mb-16">
25
- {% block title %}
26
- {% if termination_a.module %}
27
- Connect {{ termination_a.module.display }} {{ termination_a }} to {{ termination_b_type|bettertitle }}
28
- {% else %}
29
- Connect {{ termination_a.device }} {{ termination_a }} to {{ termination_b_type|bettertitle }}
30
- {% endif %}
31
- {% endblock %}
32
- </h3>
33
34
  <div class="col-lg-5">
34
35
  <div class="card">
35
36
  <div class="card-header">
@@ -38,36 +39,11 @@
38
39
  <div class="card-body">
39
40
  {% if termination_a.device %}
40
41
  {# Device component #}
41
- <div class="mb-10 d-flex justify-content-center">
42
- <label class="col-lg-3 col-form-label nb-required">Location</label>
43
- <div class="col-lg-9">
44
- <p class="form-control-plaintext">{{ termination_a.device.location }}</p>
45
- </div>
46
- </div>
47
- <div class="mb-10 d-flex justify-content-center">
48
- <label class="col-lg-3 col-form-label nb-required">Rack</label>
49
- <div class="col-lg-9">
50
- <p class="form-control-plaintext">{{ termination_a.device.rack|default:"None" }}</p>
51
- </div>
52
- </div>
53
- <div class="mb-10 d-flex justify-content-center">
54
- <label class="col-lg-3 col-form-label nb-required">Device</label>
55
- <div class="col-lg-9">
56
- <p class="form-control-plaintext">{{ termination_a.device }}</p>
57
- </div>
58
- </div>
59
- <div class="mb-10 d-flex justify-content-center">
60
- <label class="col-lg-3 col-form-label nb-required">Type</label>
61
- <div class="col-lg-9">
62
- <p class="form-control-plaintext">{{ termination_a|meta:"verbose_name"|bettertitle }}</p>
63
- </div>
64
- </div>
65
- <div class="mb-10 d-flex justify-content-center">
66
- <label class="col-lg-3 col-form-label nb-required">Name</label>
67
- <div class="col-lg-9">
68
- <p class="form-control-plaintext">{{ termination_a }}</p>
69
- </div>
70
- </div>
42
+ {% include "inc/form_static_field.html" with label="Location" value=termination_a.device.location required=True %}
43
+ {% include "inc/form_static_field.html" with label="Rack" value=termination_a.device.rack|default:"None" required=True %}
44
+ {% include "inc/form_static_field.html" with label="Device" value=termination_a.device required=True %}
45
+ {% include "inc/form_static_field.html" with label="Type" value=termination_a|meta:"verbose_name"|bettertitle required=True %}
46
+ {% include "inc/form_static_field.html" with label="Name" value=termination_a required=True %}
71
47
  {% elif termination_a.module %}
72
48
  {# Module component #}
73
49
  {% if not termination_a.module.device %}
@@ -75,76 +51,21 @@
75
51
  This {{ termination_a|meta:"verbose_name"|bettertitle }} belongs
76
52
  to a module not installed in any device.
77
53
  </div>
78
- <div class="mb-10 d-flex justify-content-center">
79
- <label class="col-lg-3 col-form-label nb-required">Location</label>
80
- <div class="col-lg-9">
81
- <p class="form-control-plaintext">{{ termination_a.module.location }}</p>
82
- </div>
83
- </div>
54
+ {% include "inc/form_static_field.html" with label="Location" value=termination_a.module.location required=True %}
84
55
  {% else %}
85
- <div class="mb-10 d-flex justify-content-center">
86
- <label class="col-lg-3 col-form-label nb-required">Location</label>
87
- <div class="col-lg-9">
88
- <p class="form-control-plaintext">{{ termination_a.module.device.location }}</p>
89
- </div>
90
- </div>
91
- <div class="mb-10 d-flex justify-content-center">
92
- <label class="col-lg-3 col-form-label nb-required">Rack</label>
93
- <div class="col-lg-9">
94
- <p class="form-control-plaintext">{{ termination_a.module.device.rack|default:"None" }}</p>
95
- </div>
96
- </div>
97
- <div class="mb-10 d-flex justify-content-center">
98
- <label class="col-lg-3 col-form-label nb-required">Device</label>
99
- <div class="col-lg-9">
100
- <p class="form-control-plaintext">{{ termination_a.module.device }}</p>
101
- </div>
102
- </div>
103
- <div class="mb-10 d-flex justify-content-center">
104
- <label class="col-lg-3 col-form-label nb-required">Module</label>
105
- <div class="col-lg-9">
106
- <p class="form-control-plaintext">{{ termination_a.module.display }}</p>
107
- </div>
108
- </div>
56
+ {% include "inc/form_static_field.html" with label="Location" value=termination_a.module.device.location required=True %}
57
+ {% include "inc/form_static_field.html" with label="Rack" value=termination_a.module.device.rack|default:"None" required=True %}
58
+ {% include "inc/form_static_field.html" with label="Device" value=termination_a.module.device required=True %}
59
+ {% include "inc/form_static_field.html" with label="Module" value=termination_a.module.display required=True %}
109
60
  {% endif %}
110
- <div class="mb-10 d-flex justify-content-center">
111
- <label class="col-lg-3 col-form-label nb-required">Type</label>
112
- <div class="col-lg-9">
113
- <p class="form-control-plaintext">{{ termination_a|meta:"verbose_name"|bettertitle }}</p>
114
- </div>
115
- </div>
116
- <div class="mb-10 d-flex justify-content-center">
117
- <label class="col-lg-3 col-form-label nb-required">Name</label>
118
- <div class="col-lg-9">
119
- <p class="form-control-plaintext">{{ termination_a }}</p>
120
- </div>
121
- </div>
61
+ {% include "inc/form_static_field.html" with label="Type" value=termination_a|meta:"verbose_name"|bettertitle required=True %}
62
+ {% include "inc/form_static_field.html" with label="Name" value=termination_a required=True %}
122
63
  {% else %}
123
64
  {# Circuit termination #}
124
- <div class="mb-10 d-flex justify-content-center">
125
- <label class="col-lg-3 col-form-label nb-required">Location</label>
126
- <div class="col-lg-9">
127
- <p class="form-control-plaintext">{{ termination_a.location }}</p>
128
- </div>
129
- </div>
130
- <div class="mb-10 d-flex justify-content-center">
131
- <label class="col-lg-3 col-form-label nb-required">Provider</label>
132
- <div class="col-lg-9">
133
- <p class="form-control-plaintext">{{ termination_a.circuit.provider }}</p>
134
- </div>
135
- </div>
136
- <div class="mb-10 d-flex justify-content-center">
137
- <label class="col-lg-3 col-form-label nb-required">Circuit</label>
138
- <div class="col-lg-9">
139
- <p class="form-control-plaintext">{{ termination_a.circuit.cid }}</p>
140
- </div>
141
- </div>
142
- <div class="mb-10 d-flex justify-content-center">
143
- <label class="col-lg-3 col-form-label nb-required">Side</label>
144
- <div class="col-lg-9">
145
- <p class="form-control-plaintext">{{ termination_a.term_side }}</p>
146
- </div>
147
- </div>
65
+ {% include "inc/form_static_field.html" with label="Location" value=termination_a.location required=True %}
66
+ {% include "inc/form_static_field.html" with label="Provider" value=termination_a.circuit.provider required=True %}
67
+ {% include "inc/form_static_field.html" with label="Circuit" value=termination_a.circuit.cid required=True %}
68
+ {% include "inc/form_static_field.html" with label="Side" value=termination_a.term_side required=True %}
148
69
  {% endif %}
149
70
  </div>
150
71
  </div>
@@ -192,17 +113,12 @@
192
113
  {% if 'termination_b_circuit' in form.fields %}
193
114
  {% render_field form.termination_b_circuit %}
194
115
  {% endif %}
195
- <div class="mb-10 d-flex justify-content-center">
196
- <label class="col-lg-3 col-form-label nb-required">Type</label>
197
- <div class="col-lg-9">
198
- <p class="form-control-plaintext">{{ termination_b_type|bettertitle }}</p>
199
- </div>
200
- </div>
116
+ {% include "inc/form_static_field.html" with label="Type" value=termination_b_type|bettertitle required=True %}
201
117
  {% render_field form.termination_b_id %}
202
118
  </div>
203
119
  </div>
204
120
  </div>
205
- <div class="col-lg-6 offset-md-3">
121
+ <div class="col-lg-6 offset-lg-3">
206
122
  {% include 'dcim/inc/cable_form.html' %}
207
123
  </div>
208
124
  {% endwith %}
@@ -1,5 +1,2 @@
1
- {% extends 'generic/object_create.html' %}
2
-
3
- {% block form %}
4
- {% include 'dcim/inc/cable_form.html' %}
5
- {% endblock %}
1
+ {% extends 'dcim/cable_update.html' %}
2
+ {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}