nautobot 3.0.0a3__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 (388) hide show
  1. nautobot/apps/choices.py +4 -0
  2. nautobot/apps/ui.py +4 -0
  3. nautobot/apps/utils.py +8 -0
  4. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
  5. nautobot/circuits/views.py +6 -2
  6. nautobot/core/api/serializers.py +1 -1
  7. nautobot/core/api/urls.py +1 -0
  8. nautobot/core/api/views.py +4 -0
  9. nautobot/core/choices.py +1 -1
  10. nautobot/core/cli/bootstrap_v3_to_v5.py +36 -13
  11. nautobot/core/cli/migrate_deprecated_templates.py +36 -9
  12. nautobot/core/filters.py +4 -0
  13. nautobot/core/forms/__init__.py +2 -0
  14. nautobot/core/forms/widgets.py +21 -2
  15. nautobot/core/jobs/__init__.py +56 -0
  16. nautobot/core/management/commands/generate_test_data.py +3 -3
  17. nautobot/core/models/__init__.py +11 -0
  18. nautobot/core/models/utils.py +1 -1
  19. nautobot/core/settings.py +17 -7
  20. nautobot/core/settings.yaml +4 -26
  21. nautobot/core/templates/admin/base.html +1 -2
  22. nautobot/core/templates/admin/change_list.html +9 -12
  23. nautobot/core/templates/base_django.html +1 -2
  24. nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
  25. nautobot/core/templates/components/tab/content_wrapper.html +4 -4
  26. nautobot/core/templates/echarts/echarts.html +21 -8
  27. nautobot/core/templates/generic/object_bulk_create.html +2 -2
  28. nautobot/core/templates/generic/object_bulk_delete.html +1 -1
  29. nautobot/core/templates/generic/object_bulk_edit.html +1 -1
  30. nautobot/core/templates/generic/object_bulk_import.html +1 -1
  31. nautobot/core/templates/generic/object_delete.html +1 -1
  32. nautobot/core/templates/generic/object_detail.html +1 -1
  33. nautobot/core/templates/generic/object_edit.html +1 -1
  34. nautobot/core/templates/generic/object_retrieve.html +2 -2
  35. nautobot/core/templates/graphene/graphiql.html +0 -1
  36. nautobot/core/templates/inc/footer.html +3 -1
  37. nautobot/core/templates/inc/header.html +10 -0
  38. nautobot/core/templates/inc/media.html +14 -0
  39. nautobot/core/templates/inc/nav_menu.html +1 -8
  40. nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
  41. nautobot/core/templates/nautobot_config.py.j2 +0 -6
  42. nautobot/core/templates/rest_framework/api.html +103 -2
  43. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +33 -0
  44. nautobot/core/templates/utilities/theme_preview.html +3 -0
  45. nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
  46. nautobot/core/templatetags/helpers.py +24 -12
  47. nautobot/core/testing/integration.py +24 -13
  48. nautobot/core/testing/utils.py +18 -4
  49. nautobot/core/testing/views.py +104 -17
  50. nautobot/core/tests/integration/test_filters.py +48 -11
  51. nautobot/core/tests/integration/test_theme.py +22 -21
  52. nautobot/core/tests/nautobot_config.py +3 -0
  53. nautobot/core/tests/runner.py +1 -2
  54. nautobot/core/tests/test_breadcrumbs.py +21 -21
  55. nautobot/core/tests/test_jobs.py +73 -6
  56. nautobot/core/tests/test_renderers.py +59 -0
  57. nautobot/core/tests/test_settings_schema.py +1 -0
  58. nautobot/core/tests/test_templatetags_helpers.py +9 -0
  59. nautobot/core/tests/test_titles.py +0 -16
  60. nautobot/core/tests/test_ui.py +122 -3
  61. nautobot/core/tests/test_utils.py +41 -1
  62. nautobot/core/ui/breadcrumbs.py +68 -17
  63. nautobot/core/ui/bulk_buttons.py +1 -1
  64. nautobot/core/ui/choices.py +49 -65
  65. nautobot/core/ui/echarts.py +15 -20
  66. nautobot/core/ui/object_detail.py +54 -46
  67. nautobot/core/ui/titles.py +3 -6
  68. nautobot/core/urls.py +8 -8
  69. nautobot/core/utils/filtering.py +11 -1
  70. nautobot/core/utils/lookup.py +46 -0
  71. nautobot/core/views/mixins.py +31 -20
  72. nautobot/core/views/renderers.py +2 -3
  73. nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -2
  74. nautobot/dcim/api/serializers.py +3 -0
  75. nautobot/dcim/choices.py +49 -0
  76. nautobot/dcim/constants.py +7 -0
  77. nautobot/dcim/factory.py +1 -1
  78. nautobot/dcim/filters.py +13 -1
  79. nautobot/dcim/forms.py +89 -3
  80. nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
  81. nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
  82. nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
  83. nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
  84. nautobot/dcim/migrations/{0078_remove_device_location_tenant_name_uniqueness.py → 0079_remove_device_location_tenant_name_uniqueness.py} +1 -1
  85. nautobot/dcim/migrations/{0079_device_name_data_migration.py → 0080_device_name_data_migration.py} +1 -1
  86. nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
  87. nautobot/dcim/models/device_component_templates.py +33 -1
  88. nautobot/dcim/models/device_components.py +22 -1
  89. nautobot/dcim/models/devices.py +17 -4
  90. nautobot/dcim/tables/devices.py +15 -0
  91. nautobot/dcim/tables/devicetypes.py +8 -1
  92. nautobot/dcim/tables/racks.py +0 -2
  93. nautobot/dcim/tables/template_code.py +1 -1
  94. nautobot/dcim/templates/dcim/cable_trace.html +0 -2
  95. nautobot/dcim/templates/dcim/consoleport.html +1 -1
  96. nautobot/dcim/templates/dcim/consoleserverport.html +1 -1
  97. nautobot/dcim/templates/dcim/devicebay.html +1 -1
  98. nautobot/dcim/templates/dcim/frontport.html +1 -1
  99. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  100. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
  101. nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
  102. nautobot/dcim/templates/dcim/interface.html +9 -1
  103. nautobot/dcim/templates/dcim/interface_edit.html +2 -0
  104. nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
  105. nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
  106. nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
  107. nautobot/dcim/templates/dcim/module_frontports.html +1 -1
  108. nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
  109. nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
  110. nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
  111. nautobot/dcim/templates/dcim/module_powerports.html +1 -1
  112. nautobot/dcim/templates/dcim/module_rearports.html +1 -1
  113. nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
  114. nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
  115. nautobot/dcim/templates/dcim/powerport.html +1 -1
  116. nautobot/dcim/templates/dcim/rack_elevation_list.html +1 -1
  117. nautobot/dcim/templates/dcim/rack_retrieve.html +0 -11
  118. nautobot/dcim/templates/dcim/rearport.html +1 -1
  119. nautobot/dcim/templates/dcim/trace/cable.html +1 -1
  120. nautobot/dcim/templates/dcim/virtualchassis_update.html +1 -1
  121. nautobot/dcim/tests/integration/test_controller.py +3 -6
  122. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
  123. nautobot/dcim/tests/integration/test_create_device.py +0 -2
  124. nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
  125. nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
  126. nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
  127. nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
  128. nautobot/dcim/tests/test_api.py +186 -6
  129. nautobot/dcim/tests/test_filters.py +43 -1
  130. nautobot/dcim/tests/test_forms.py +110 -8
  131. nautobot/dcim/tests/test_graphql.py +44 -1
  132. nautobot/dcim/tests/test_models.py +265 -0
  133. nautobot/dcim/tests/test_tables.py +160 -0
  134. nautobot/dcim/tests/test_views.py +69 -7
  135. nautobot/dcim/views.py +232 -126
  136. nautobot/extras/api/views.py +51 -44
  137. nautobot/extras/datasources/git.py +3 -1
  138. nautobot/extras/filters.py +19 -2
  139. nautobot/extras/forms/forms.py +9 -2
  140. nautobot/extras/jobs.py +2 -0
  141. nautobot/extras/jobs_ui.py +4 -3
  142. nautobot/extras/management/__init__.py +2 -0
  143. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
  144. nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
  145. nautobot/extras/models/approvals.py +11 -1
  146. nautobot/extras/models/change_logging.py +4 -0
  147. nautobot/extras/models/jobs.py +1 -3
  148. nautobot/extras/models/models.py +10 -2
  149. nautobot/extras/plugins/marketplace_manifest.yml +49 -1
  150. nautobot/extras/plugins/views.py +0 -5
  151. nautobot/extras/querysets.py +8 -0
  152. nautobot/extras/tables.py +12 -0
  153. nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
  154. nautobot/extras/templates/extras/configcontext_update.html +1 -0
  155. nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
  156. nautobot/extras/templates/extras/objectchange_retrieve.html +0 -2
  157. nautobot/extras/templates/extras/plugin_detail.html +3 -3
  158. nautobot/extras/templates/extras/secret_create.html +1 -1
  159. nautobot/extras/tests/integration/test_computedfields.py +8 -9
  160. nautobot/extras/tests/integration/test_customfields.py +1 -3
  161. nautobot/extras/tests/integration/test_dynamicgroups.py +7 -8
  162. nautobot/extras/tests/integration/test_relationships.py +0 -2
  163. nautobot/extras/tests/test_api.py +63 -0
  164. nautobot/extras/tests/test_changelog.py +24 -2
  165. nautobot/extras/tests/test_filters.py +36 -3
  166. nautobot/extras/tests/test_models.py +38 -2
  167. nautobot/extras/tests/test_utils.py +3 -4
  168. nautobot/extras/tests/test_views.py +22 -83
  169. nautobot/extras/urls.py +0 -14
  170. nautobot/extras/views.py +83 -52
  171. nautobot/ipam/filters.py +26 -0
  172. nautobot/ipam/tables.py +6 -0
  173. nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
  174. nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
  175. nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
  176. nautobot/ipam/tests/test_filters.py +26 -1
  177. nautobot/ipam/tests/test_models.py +1 -1
  178. nautobot/ipam/views.py +9 -7
  179. nautobot/load_balancers/__init__.py +0 -0
  180. nautobot/load_balancers/api/__init__.py +1 -0
  181. nautobot/load_balancers/api/serializers.py +75 -0
  182. nautobot/load_balancers/api/urls.py +23 -0
  183. nautobot/load_balancers/api/views.py +61 -0
  184. nautobot/load_balancers/apps.py +17 -0
  185. nautobot/load_balancers/choices.py +167 -0
  186. nautobot/load_balancers/filters.py +225 -0
  187. nautobot/load_balancers/forms.py +532 -0
  188. nautobot/load_balancers/management/commands/__init__.py +0 -0
  189. nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
  190. nautobot/load_balancers/migrations/0001_initial.py +465 -0
  191. nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
  192. nautobot/load_balancers/migrations/__init__.py +0 -0
  193. nautobot/load_balancers/models.py +423 -0
  194. nautobot/load_balancers/navigation.py +80 -0
  195. nautobot/load_balancers/tables.py +255 -0
  196. nautobot/load_balancers/tests/__init__.py +474 -0
  197. nautobot/load_balancers/tests/test_api.py +353 -0
  198. nautobot/load_balancers/tests/test_filters.py +134 -0
  199. nautobot/load_balancers/tests/test_forms.py +266 -0
  200. nautobot/load_balancers/tests/test_models.py +195 -0
  201. nautobot/load_balancers/tests/test_views.py +229 -0
  202. nautobot/load_balancers/urls.py +17 -0
  203. nautobot/load_balancers/views.py +248 -0
  204. nautobot/project-static/dist/css/github-dark.min.css +10 -0
  205. nautobot/project-static/dist/css/github.min.css +10 -0
  206. nautobot/project-static/dist/css/nautobot.css +1 -11
  207. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  208. nautobot/project-static/dist/js/libraries.js +1 -1
  209. nautobot/project-static/dist/js/libraries.js.map +1 -1
  210. nautobot/project-static/dist/js/nautobot.js +1 -1
  211. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  212. nautobot/project-static/js/forms.js +13 -0
  213. nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
  214. nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
  215. nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
  216. nautobot/ui/package-lock.json +87 -4
  217. nautobot/ui/package.json +2 -1
  218. nautobot/ui/src/js/nautobot.js +0 -1
  219. nautobot/ui/src/js/select2.js +53 -2
  220. nautobot/ui/src/scss/nautobot.scss +51 -2
  221. nautobot/ui/webpack.config.js +13 -0
  222. nautobot/users/templates/users/preferences.html +11 -2
  223. nautobot/virtualization/filters.py +6 -1
  224. nautobot/virtualization/tests/test_filters.py +10 -1
  225. nautobot/virtualization/tests/test_models.py +1 -0
  226. nautobot/virtualization/views.py +4 -1
  227. nautobot/vpn/factory.py +25 -15
  228. nautobot/vpn/filters.py +1 -0
  229. nautobot/vpn/forms.py +1 -0
  230. nautobot/vpn/migrations/0001_initial.py +1 -1
  231. nautobot/vpn/models.py +16 -8
  232. nautobot/vpn/tables.py +5 -2
  233. nautobot/vpn/tests/test_api.py +0 -5
  234. nautobot/vpn/tests/test_forms.py +1 -2
  235. nautobot/vpn/tests/test_models.py +57 -7
  236. nautobot/vpn/tests/test_views.py +22 -3
  237. nautobot/vpn/views.py +78 -20
  238. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +4 -4
  239. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +243 -352
  240. nautobot/circuits/templates/circuits/circuit.html +0 -2
  241. nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
  242. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
  243. nautobot/circuits/templates/circuits/circuit_update.html +0 -1
  244. nautobot/circuits/templates/circuits/circuittermination.html +0 -2
  245. nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
  246. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
  247. nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
  248. nautobot/circuits/templates/circuits/circuittype.html +0 -2
  249. nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
  250. nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
  251. nautobot/circuits/templates/circuits/provider.html +0 -2
  252. nautobot/circuits/templates/circuits/provider_edit.html +0 -2
  253. nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
  254. nautobot/circuits/templates/circuits/provider_update.html +0 -1
  255. nautobot/circuits/templates/circuits/providernetwork.html +0 -2
  256. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
  257. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
  258. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
  259. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
  260. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
  261. nautobot/core/templates/buttons/import.html +0 -9
  262. nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
  263. nautobot/dcim/templates/dcim/cable.html +0 -2
  264. nautobot/dcim/templates/dcim/cable_edit.html +0 -2
  265. nautobot/dcim/templates/dcim/controller/base.html +0 -2
  266. nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
  267. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
  268. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
  269. nautobot/dcim/templates/dcim/device/base.html +0 -2
  270. nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
  271. nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
  272. nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
  273. nautobot/dcim/templates/dcim/device/frontports.html +0 -2
  274. nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
  275. nautobot/dcim/templates/dcim/device/inventory.html +0 -2
  276. nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
  277. nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
  278. nautobot/dcim/templates/dcim/device/powerports.html +0 -2
  279. nautobot/dcim/templates/dcim/device/rearports.html +0 -2
  280. nautobot/dcim/templates/dcim/device/wireless.html +0 -2
  281. nautobot/dcim/templates/dcim/device_component.html +0 -2
  282. nautobot/dcim/templates/dcim/device_edit.html +0 -2
  283. nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
  284. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
  285. nautobot/dcim/templates/dcim/devicetype.html +0 -2
  286. nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
  287. nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
  288. nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
  289. nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
  290. nautobot/dcim/templates/dcim/location.html +0 -2
  291. nautobot/dcim/templates/dcim/location_edit.html +0 -2
  292. nautobot/dcim/templates/dcim/location_retrieve.html +0 -2
  293. nautobot/dcim/templates/dcim/locationtype.html +0 -2
  294. nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
  295. nautobot/dcim/templates/dcim/manufacturer.html +0 -2
  296. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
  297. nautobot/dcim/templates/dcim/platform.html +0 -2
  298. nautobot/dcim/templates/dcim/powerfeed.html +0 -2
  299. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
  300. nautobot/dcim/templates/dcim/powerpanel.html +0 -2
  301. nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
  302. nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
  303. nautobot/dcim/templates/dcim/rack.html +0 -2
  304. nautobot/dcim/templates/dcim/rack_edit.html +0 -2
  305. nautobot/dcim/templates/dcim/rackgroup.html +0 -2
  306. nautobot/dcim/templates/dcim/rackreservation.html +0 -2
  307. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
  308. nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
  309. nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
  310. nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
  311. nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
  312. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
  313. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
  314. nautobot/dcim/ui.py +0 -29
  315. nautobot/extras/templates/extras/computedfield.html +0 -2
  316. nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
  317. nautobot/extras/templates/extras/configcontext.html +0 -2
  318. nautobot/extras/templates/extras/configcontext_edit.html +0 -2
  319. nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
  320. nautobot/extras/templates/extras/configcontextschema.html +0 -2
  321. nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
  322. nautobot/extras/templates/extras/contact_retrieve.html +0 -2
  323. nautobot/extras/templates/extras/customfield.html +0 -2
  324. nautobot/extras/templates/extras/customfield_edit.html +0 -2
  325. nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
  326. nautobot/extras/templates/extras/customlink.html +0 -2
  327. nautobot/extras/templates/extras/dynamicgroup.html +0 -2
  328. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
  329. nautobot/extras/templates/extras/exporttemplate.html +0 -2
  330. nautobot/extras/templates/extras/gitrepository.html +0 -2
  331. nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
  332. nautobot/extras/templates/extras/graphqlquery.html +0 -2
  333. nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
  334. nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -2
  335. nautobot/extras/templates/extras/job_detail.html +0 -2
  336. nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
  337. nautobot/extras/templates/extras/jobhook.html +0 -2
  338. nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
  339. nautobot/extras/templates/extras/jobresult.html +0 -2
  340. nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
  341. nautobot/extras/templates/extras/note.html +0 -2
  342. nautobot/extras/templates/extras/note_retrieve.html +0 -1
  343. nautobot/extras/templates/extras/object_changelog.html +0 -2
  344. nautobot/extras/templates/extras/object_notes.html +0 -2
  345. nautobot/extras/templates/extras/objectchange.html +0 -2
  346. nautobot/extras/templates/extras/objectchange_list.html +0 -3
  347. nautobot/extras/templates/extras/relationship.html +0 -1
  348. nautobot/extras/templates/extras/secret.html +0 -1
  349. nautobot/extras/templates/extras/secret_edit.html +0 -1
  350. nautobot/extras/templates/extras/secretsgroup.html +0 -2
  351. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
  352. nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
  353. nautobot/extras/templates/extras/status.html +0 -2
  354. nautobot/extras/templates/extras/tag.html +0 -2
  355. nautobot/extras/templates/extras/tag_edit.html +0 -2
  356. nautobot/extras/templates/extras/tag_retrieve.html +0 -2
  357. nautobot/extras/templates/extras/team_retrieve.html +0 -2
  358. nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
  359. nautobot/ipam/templates/ipam/prefix.html +0 -2
  360. nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
  361. nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
  362. nautobot/ipam/templates/ipam/rir.html +0 -2
  363. nautobot/ipam/templates/ipam/routetarget.html +0 -1
  364. nautobot/ipam/templates/ipam/service.html +0 -2
  365. nautobot/ipam/templates/ipam/service_edit.html +0 -2
  366. nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
  367. nautobot/ipam/templates/ipam/vlan.html +0 -2
  368. nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
  369. nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
  370. nautobot/ipam/templates/ipam/vlangroup.html +0 -2
  371. nautobot/ipam/templates/ipam/vrf.html +0 -1
  372. nautobot/tenancy/templates/tenancy/tenant.html +0 -2
  373. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
  374. nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
  375. nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
  376. nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
  377. nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
  378. nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
  379. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
  380. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
  381. nautobot/vpn/templates/vpn/vpnprofile.html +0 -2
  382. nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
  383. nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
  384. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
  385. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
  386. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
  387. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
  388. {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +0 -0
nautobot/apps/choices.py CHANGED
@@ -14,8 +14,10 @@ from nautobot.dcim.choices import (
14
14
  ConsolePortTypeChoices,
15
15
  DeviceFaceChoices,
16
16
  DeviceRedundancyGroupFailoverStrategyChoices,
17
+ InterfaceDuplexChoices,
17
18
  InterfaceModeChoices,
18
19
  InterfaceRedundancyGroupProtocolChoices,
20
+ InterfaceSpeedChoices,
19
21
  InterfaceTypeChoices,
20
22
  PortTypeChoices,
21
23
  PowerFeedBreakerPoleChoices,
@@ -84,8 +86,10 @@ __all__ = (
84
86
  "IPAddressRoleChoices",
85
87
  "IPAddressTypeChoices",
86
88
  "IPAddressVersionChoices",
89
+ "InterfaceDuplexChoices",
87
90
  "InterfaceModeChoices",
88
91
  "InterfaceRedundancyGroupProtocolChoices",
92
+ "InterfaceSpeedChoices",
89
93
  "InterfaceTypeChoices",
90
94
  "JobExecutionType",
91
95
  "JobResultStatusChoices",
nautobot/apps/ui.py CHANGED
@@ -4,6 +4,7 @@ from nautobot.core.choices import ButtonColorChoices
4
4
  from nautobot.core.ui.base import PermissionsMixin
5
5
  from nautobot.core.ui.breadcrumbs import (
6
6
  AncestorsBreadcrumbs,
7
+ AncestorsInstanceBreadcrumbItem,
7
8
  BaseBreadcrumbItem,
8
9
  Breadcrumbs,
9
10
  context_object_attr,
@@ -13,6 +14,7 @@ from nautobot.core.ui.breadcrumbs import (
13
14
  ViewNameBreadcrumbItem,
14
15
  )
15
16
  from nautobot.core.ui.choices import (
17
+ EChartsThemeColors,
16
18
  EChartsTypeChoices,
17
19
  LayoutChoices,
18
20
  NavigationIconChoices,
@@ -65,6 +67,7 @@ from nautobot.extras.plugins import Banner, TemplateExtension
65
67
 
66
68
  __all__ = (
67
69
  "AncestorsBreadcrumbs",
70
+ "AncestorsInstanceBreadcrumbItem",
68
71
  "Banner",
69
72
  "BannerClassChoices",
70
73
  "BaseBreadcrumbItem",
@@ -78,6 +81,7 @@ __all__ = (
78
81
  "DropdownButton",
79
82
  "EChartsBase",
80
83
  "EChartsPanel",
84
+ "EChartsThemeColors",
81
85
  "EChartsTypeChoices",
82
86
  "GroupedKeyValueTablePanel",
83
87
  "HomePageBase",
nautobot/apps/utils.py CHANGED
@@ -27,10 +27,13 @@ from nautobot.core.utils.filtering import (
27
27
  from nautobot.core.utils.git import BranchDoesNotExist, convert_git_diff_log_to_list, GitRepo, swap_status_initials
28
28
  from nautobot.core.utils.logging import sanitize
29
29
  from nautobot.core.utils.lookup import (
30
+ get_breadcrumbs_for_model,
30
31
  get_changes_for_model,
32
+ get_detail_view_components_context_for_model,
31
33
  get_filterset_for_model,
32
34
  get_form_for_model,
33
35
  get_model_from_name,
36
+ get_object_detail_content_for_model,
34
37
  get_related_class_for_model,
35
38
  get_related_field_for_models,
36
39
  get_route_for_model,
@@ -38,6 +41,7 @@ from nautobot.core.utils.lookup import (
38
41
  get_url_for_url_pattern,
39
42
  get_url_patterns,
40
43
  get_view_for_model,
44
+ get_view_titles_for_model,
41
45
  )
42
46
  from nautobot.core.utils.migrations import migrate_content_type_references_to_new_model
43
47
  from nautobot.core.utils.permissions import (
@@ -97,8 +101,10 @@ __all__ = (
97
101
  "generate_signature",
98
102
  "get_all_lookup_expr_for_field",
99
103
  "get_base_template",
104
+ "get_breadcrumbs_for_model",
100
105
  "get_celery_queues",
101
106
  "get_changes_for_model",
107
+ "get_detail_view_components_context_for_model",
102
108
  "get_filter_field_label",
103
109
  "get_filterable_params_from_filter_params",
104
110
  "get_filterset_field",
@@ -107,6 +113,7 @@ __all__ = (
107
113
  "get_form_for_model",
108
114
  "get_latest_release",
109
115
  "get_model_from_name",
116
+ "get_object_detail_content_for_model",
110
117
  "get_permission_for_model",
111
118
  "get_related_class_for_model",
112
119
  "get_related_field_for_models",
@@ -116,6 +123,7 @@ __all__ = (
116
123
  "get_url_for_url_pattern",
117
124
  "get_url_patterns",
118
125
  "get_view_for_model",
126
+ "get_view_titles_for_model",
119
127
  "get_worker_count",
120
128
  "hex_to_rgb",
121
129
  "image_upload",
@@ -1,7 +1,5 @@
1
1
  import uuid
2
2
 
3
- from django.test import tag
4
-
5
3
  from nautobot.circuits.models import Circuit, CircuitType, Provider
6
4
  from nautobot.core.testing.integration import (
7
5
  BulkOperationsTestCases,
@@ -9,7 +7,6 @@ from nautobot.core.testing.integration import (
9
7
  from nautobot.extras.models import Status
10
8
 
11
9
 
12
- @tag("fix_in_v3")
13
10
  class CircuitBulkOperationsTestCase(BulkOperationsTestCases.BulkOperationsTestCase):
14
11
  """
15
12
  Test circuits bulk edit / delete operations.
@@ -60,7 +60,7 @@ class CircuitTypeUIViewSet(NautobotUIViewSet):
60
60
 
61
61
  class CircuitTerminationObjectFieldsPanel(ObjectFieldsPanel):
62
62
  def get_extra_context(self, context):
63
- return {"termination": context["object"]}
63
+ return {"termination": context["object"], **super().get_extra_context(context)}
64
64
 
65
65
  def render_key(self, key, value, context):
66
66
  if key == "connected_endpoint":
@@ -226,7 +226,11 @@ class CircuitUIViewSet(NautobotUIViewSet):
226
226
  return True
227
227
 
228
228
  def get_extra_context(self, context):
229
- return {"termination": context[self.context_object_key], "side": self.side}
229
+ return {
230
+ "termination": context[self.context_object_key],
231
+ "side": self.side,
232
+ **super().get_extra_context(context),
233
+ }
230
234
 
231
235
  def get_data(self, context):
232
236
  """
@@ -104,7 +104,7 @@ class OptInFieldsMixin:
104
104
  # If exclude_m2m is present and truthy, mark any many-to-many fields as write-only so they
105
105
  # don't get included in the response.
106
106
  # If exclude_m2m is not present, we include a subset of many-to-many fields by default.
107
- exclude_m2m = params.get("exclude_m2m")
107
+ exclude_m2m = params.get("exclude_m2m", self.context.get("exclude_m2m", None))
108
108
  if exclude_m2m is None or is_truthy(exclude_m2m):
109
109
  for field_instance in fields.values():
110
110
  if isinstance(field_instance, (serializers.ManyRelatedField, serializers.ListSerializer)):
nautobot/core/api/urls.py CHANGED
@@ -48,6 +48,7 @@ urlpatterns = [
48
48
  path("dcim/", include("nautobot.dcim.api.urls")),
49
49
  path("extras/", include("nautobot.extras.api.urls")),
50
50
  path("ipam/", include("nautobot.ipam.api.urls")),
51
+ path("load-balancers/", include("nautobot.load_balancers.api.urls")),
51
52
  path("tenancy/", include("nautobot.tenancy.api.urls")),
52
53
  path("users/", include("nautobot.users.api.urls")),
53
54
  path("virtualization/", include("nautobot.virtualization.api.urls")),
@@ -458,6 +458,10 @@ class APIRootView(AuthenticatedAPIRootView):
458
458
  "ipam",
459
459
  reverse("ipam-api:api-root", request=request, format=format),
460
460
  ),
461
+ (
462
+ "load-balancers",
463
+ reverse("load_balancers-api:api-root", request=request, format=format),
464
+ ),
461
465
  (
462
466
  "plugins",
463
467
  reverse("plugins-api:api-root", request=request, format=format),
nautobot/core/choices.py CHANGED
@@ -238,7 +238,7 @@ class ButtonActionIconChoices(ChoiceSet):
238
238
  LOCK = "mdi-lock"
239
239
  MAGNIFY = "mdi-magnify"
240
240
  NOTE = "mdi-note-text"
241
- RENAME = "mdi-pencil"
241
+ RENAME = "mdi-rename"
242
242
  SWAP = "mdi-swap-vertical"
243
243
  TRASH = "mdi-trash-can-outline"
244
244
 
@@ -3,6 +3,8 @@ import logging
3
3
  import os
4
4
  import re
5
5
 
6
+ from .migrate_deprecated_templates import replace_deprecated_templates
7
+
6
8
  logger = logging.getLogger(__name__)
7
9
 
8
10
 
@@ -647,7 +649,7 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
647
649
  # --- File Processing ---
648
650
 
649
651
 
650
- def fix_html_files_in_directory(directory: str, resize=False) -> None:
652
+ def fix_html_files_in_directory(directory: str, resize=False, dry_run=False, skip_templates=False) -> None:
651
653
  """
652
654
  Recursively finds all .html files in the given directory, applies convert_bootstrap_classes,
653
655
  and overwrites each file with the fixed content. If resize is True, it will only change the
@@ -669,6 +671,9 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
669
671
  # Breakpoints that are not xs do not count as failures in djlint, so we keep a separate counter
670
672
  resizing_other = 0
671
673
 
674
+ if not os.path.exists(directory):
675
+ raise FileNotFoundError(directory)
676
+
672
677
  if os.path.isfile(directory):
673
678
  only_filename = os.path.basename(directory)
674
679
  directory = os.path.dirname(directory)
@@ -715,9 +720,12 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
715
720
 
716
721
  fixed_content, stats = convert_bootstrap_classes(content, file_path=file_path)
717
722
 
718
- with open(file_path, "w", encoding="utf-8") as f:
719
- f.write(fixed_content)
720
- logger.info("Fixed: %s", file_path)
723
+ if dry_run:
724
+ logger.info("Would fix: %s", file_path)
725
+ else:
726
+ with open(file_path, "w", encoding="utf-8") as f:
727
+ f.write(fixed_content)
728
+ logger.info("Fixed: %s", file_path)
721
729
 
722
730
  if any(stats.values()):
723
731
  print(f"→ {os.path.relpath(file_path, directory)}: ", end="")
@@ -743,19 +751,23 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
743
751
  if k in totals:
744
752
  totals[k] += v
745
753
 
754
+ templates_replaced = replace_deprecated_templates(directory, dry_run=dry_run) if not skip_templates else 0
755
+
746
756
  # Global summary
747
757
  total_issues = sum(totals.values())
748
758
  print("=== Global Summary ===")
749
759
  print(f"Total issues fixed: {total_issues}")
750
- print(f"- Class replacements: {totals['replacements']}")
751
- print(f"- Extra-breadcrumb fixes: {totals['extra_breadcrumbs']}")
752
- print(f"- <li> in <ol.breadcrumb>: {totals['breadcrumb_items']}")
753
- print(f"- <li> in <ul.nav-tabs>: {totals['nav_items']}")
754
- print(f"- <a> in <ul.dropdown-menu>: {totals['dropdown_items']}")
755
- print(f"- Panel class replacements: {totals['panel_classes']}")
756
- print(f"- Resizing breakpoint xs: {totals['resizing_xs']}")
760
+ print(f"- Class replacements: {totals['replacements']}")
761
+ print(f"- Extra-breadcrumb fixes: {totals['extra_breadcrumbs']}")
762
+ print(f"- <li> in <ol.breadcrumb>: {totals['breadcrumb_items']}")
763
+ print(f"- <li> in <ul.nav-tabs>: {totals['nav_items']}")
764
+ print(f"- <a> in <ul.dropdown-menu>: {totals['dropdown_items']}")
765
+ print(f"- Panel class replacements: {totals['panel_classes']}")
766
+ print(f"- Resizing breakpoint xs: {totals['resizing_xs']}")
767
+ print("-------------------------------------")
768
+ print(f"- Resizing other breakpoints: {resizing_other}")
757
769
  print("-------------------------------------")
758
- print(f"- Resizing other breakpoints: {resizing_other}")
770
+ print(f"- Deprecated templates replaced: {templates_replaced}")
759
771
 
760
772
 
761
773
  def main():
@@ -766,10 +778,21 @@ def main():
766
778
  action="store_true",
767
779
  help="Change column breakpoints to be one level higher, such as 'col-xs-*' to 'col-sm-*'",
768
780
  )
781
+ parser.add_argument(
782
+ "-d",
783
+ "--dry-run",
784
+ action="store_true",
785
+ help="Show which files would be modified without making any changes.",
786
+ )
769
787
  parser.add_argument("path", type=str, help="Path to directory in which to recursively fix all .html files.")
788
+ parser.add_argument(
789
+ "-st", "--skip-template-replacement", action="store_true", help="Skip replacing deprecated templates."
790
+ )
770
791
  args = parser.parse_args()
771
792
 
772
- fix_html_files_in_directory(args.path, resize=args.resize)
793
+ fix_html_files_in_directory(
794
+ args.path, resize=args.resize, dry_run=args.dry_run, skip_templates=args.skip_template_replacement
795
+ )
773
796
 
774
797
 
775
798
  if __name__ == "__main__":
@@ -4,15 +4,16 @@ import re
4
4
 
5
5
  TEMPLATE_REPLACEMENTS = {
6
6
  # Format: new_template: [old_template1, old_template2, ...]
7
- "circuits/circuit_create.html": ["circuits/circuit_edit.html"],
8
- "circuits/circuittermination_create.html": ["circuits/circuittermination_edit.html"],
9
- "circuits/provider_create.html": ["circuits/provider_edit.html"],
10
- "circuits/provider_retrieve.html": ["circuits/provider.html"],
7
+ "circuits/circuit_create.html": ["circuits/circuit_edit.html", "circuits/circuit_update.html"],
8
+ "circuits/circuittermination_create.html": [
9
+ "circuits/circuittermination_edit.html",
10
+ "circuits/circuittermination_update.html",
11
+ ],
12
+ "circuits/provider_create.html": ["circuits/provider_edit.html", "circuits/provider_update.html"],
11
13
  "dcim/cable_retrieve.html": ["dcim/cable.html"],
12
14
  "dcim/cable_update.html": ["dcim/cable_edit.html"],
13
15
  "dcim/device_create.html": ["dcim/device_edit.html"],
14
16
  "dcim/devicetype_update.html": ["dcim/devicetype_edit.html"],
15
- "dcim/location_retrieve.html": ["dcim/location.html"],
16
17
  "dcim/location_update.html": ["dcim/location_edit.html"],
17
18
  "dcim/rack_retrieve.html": ["dcim/rack.html"],
18
19
  "dcim/rack_update.html": ["dcim/rack_edit.html"],
@@ -26,10 +27,9 @@ TEMPLATE_REPLACEMENTS = {
26
27
  "extras/dynamicgroup_update.html": ["extras/dynamicgroup_edit.html"],
27
28
  "extras/gitrepository_retrieve.html": ["extras/gitrepository.html"],
28
29
  "extras/gitrepository_update.html": ["extras/gitrepository_object_edit.html"],
29
- "extras/graphqlquery_retrieve.html": ["extras/graphqlquery.html"],
30
30
  "extras/jobresult_retrieve.html": ["extras/jobresult.html"],
31
- "extras/note_retrieve.html": ["extras/note.html"],
32
31
  "extras/objectchange_retrieve.html": ["extras/objectchange.html"],
32
+ "extras/secret_create.html": ["extras/secret_edit.html"],
33
33
  "extras/secretsgroup_update.html": ["extras/secretsgroup_edit.html"],
34
34
  "extras/tag_update.html": ["extras/tag_edit.html"],
35
35
  "generic/object_bulk_create.html": ["generic/object_bulk_import.html"],
@@ -38,6 +38,7 @@ TEMPLATE_REPLACEMENTS = {
38
38
  "generic/object_changelog.html": ["extras/object_changelog.html"],
39
39
  "generic/object_create.html": ["dcim/powerpanel_edit.html", "generic/object_edit.html", "ipam/service_edit.html"],
40
40
  "generic/object_destroy.html": ["generic/object_delete.html"],
41
+ "generic/object_list.html": ["extras/graphqlquery_list.html", "extras/objectchange_list.html"],
41
42
  "generic/object_notes.html": ["extras/object_notes.html"],
42
43
  "generic/object_retrieve.html": [
43
44
  "circuits/circuit.html",
@@ -46,6 +47,8 @@ TEMPLATE_REPLACEMENTS = {
46
47
  "circuits/circuittermination_retrieve.html",
47
48
  "circuits/circuittype.html",
48
49
  "circuits/circuittype_retrieve.html",
50
+ "circuits/provider.html",
51
+ "circuits/provider_retrieve.html",
49
52
  "circuits/providernetwork.html",
50
53
  "circuits/providernetwork_retrieve.html",
51
54
  "cloud/cloudaccount_retrieve.html",
@@ -68,14 +71,18 @@ TEMPLATE_REPLACEMENTS = {
68
71
  "dcim/device/powerports.html",
69
72
  "dcim/device/rearports.html",
70
73
  "dcim/device/wireless.html",
74
+ "dcim/device_component.html",
71
75
  "dcim/devicefamily_retrieve.html",
72
76
  "dcim/deviceredundancygroup_retrieve.html",
73
77
  "dcim/devicetype.html",
74
78
  "dcim/devicetype_retrieve.html",
75
79
  "dcim/interfaceredundancygroup_retrieve.html",
80
+ "dcim/location.html",
81
+ "dcim/location_retrieve.html",
76
82
  "dcim/locationtype.html",
77
83
  "dcim/locationtype_retrieve.html",
78
84
  "dcim/manufacturer.html",
85
+ "dcim/modulebay_retrieve.html",
79
86
  "dcim/platform.html",
80
87
  "dcim/powerfeed.html",
81
88
  "dcim/powerfeed_retrieve.html",
@@ -96,11 +103,17 @@ TEMPLATE_REPLACEMENTS = {
96
103
  "extras/customfield_retrieve.html",
97
104
  "extras/customlink.html",
98
105
  "extras/exporttemplate.html",
106
+ "extras/graphqlquery.html",
107
+ "extras/graphqlquery_retrieve.html",
99
108
  "extras/job_detail.html",
100
109
  "extras/jobbutton_retrieve.html",
101
110
  "extras/jobhook.html",
102
111
  "extras/jobqueue_retrieve.html",
103
112
  "extras/metadatatype_retrieve.html",
113
+ "extras/note.html",
114
+ "extras/note_retrieve.html",
115
+ "extras/relationship.html",
116
+ "extras/secret.html",
104
117
  "extras/secretsgroup.html",
105
118
  "extras/secretsgroup_retrieve.html",
106
119
  "extras/status.html",
@@ -108,13 +121,20 @@ TEMPLATE_REPLACEMENTS = {
108
121
  "extras/tag_retrieve.html",
109
122
  "extras/team_retrieve.html",
110
123
  "generic/object_detail.html",
124
+ "ipam/namespace_retrieve.html",
125
+ "ipam/prefix.html",
126
+ "ipam/prefix_retrieve.html",
111
127
  "ipam/rir.html",
128
+ "ipam/routetarget.html",
112
129
  "ipam/service.html",
113
130
  "ipam/service_retrieve.html",
114
131
  "ipam/vlan.html",
115
132
  "ipam/vlan_retrieve.html",
116
133
  "ipam/vlangroup.html",
134
+ "ipam/vrf.html",
117
135
  "tenancy/tenant.html",
136
+ "tenancy/tenantgroup.html",
137
+ "tenancy/tenantgroup_retrieve.html",
118
138
  "virtualization/clustergroup.html",
119
139
  "virtualization/clustertype.html",
120
140
  "virtualization/virtualmachine.html",
@@ -123,10 +143,9 @@ TEMPLATE_REPLACEMENTS = {
123
143
  "wireless/supporteddatarate_retrieve.html",
124
144
  "wireless/wirelessnetwork_retrieve.html",
125
145
  ],
126
- "ipam/prefix_retrieve.html": ["ipam/prefix.html"],
146
+ "ipam/prefix_create.html": ["ipam/prefix_edit.html"],
127
147
  "ipam/vlan_update.html": ["ipam/vlan_edit.html"],
128
148
  "tenancy/tenant_create.html": ["tenancy/tenant_edit.html"],
129
- "tenancy/tenantgroup_retrieve.html": ["tenancy/tenantgroup.html"],
130
149
  "virtualchassis_update.html": ["dcim/virtualchassis_edit.html"],
131
150
  "virtualization/virtualmachine_update.html": ["virtualization/virtualmachine_edit.html"],
132
151
  }
@@ -164,6 +183,8 @@ def replace_deprecated_templates(path: str, dry_run: bool = False):
164
183
  path = os.path.dirname(path)
165
184
  else:
166
185
  only_filename = None
186
+ print("Finding deprecated templates to replace...")
187
+ count = 0
167
188
 
168
189
  for root, _, files in os.walk(path):
169
190
  for filename in files:
@@ -179,12 +200,18 @@ def replace_deprecated_templates(path: str, dry_run: bool = False):
179
200
  fixed_content, was_updated = replace_template_references(content)
180
201
 
181
202
  if was_updated:
203
+ count += 1
182
204
  if dry_run:
183
205
  print(f"Detected deprecated template reference in {file_path}")
184
206
  continue
185
207
  with open(file_path, "w", encoding="utf-8") as f:
186
208
  f.write(fixed_content)
187
209
  print(f"Updated: {file_path}")
210
+ if not count:
211
+ print("No deprecated templates found.")
212
+ else:
213
+ print(f"Found {count} deprecated templates.")
214
+ return count
188
215
 
189
216
 
190
217
  def main():
nautobot/core/filters.py CHANGED
@@ -101,6 +101,10 @@ class MultiValueDateTimeFilter(django_filters.DateTimeFilter, django_filters.Mul
101
101
  class MultiValueNumberFilter(django_filters.NumberFilter, django_filters.MultipleChoiceFilter):
102
102
  field_class = multivalue_field_factory(django_forms.IntegerField)
103
103
 
104
+ def __init__(self, *args, choices=None, **kwargs):
105
+ super().__init__(*args, **kwargs)
106
+ self.choices = list(choices) if choices is not None else None
107
+
104
108
 
105
109
  class MultiValueBigNumberFilter(MultiValueNumberFilter):
106
110
  """Subclass of MultiValueNumberFilter used for BigInteger model fields."""
@@ -69,6 +69,7 @@ from nautobot.core.forms.widgets import (
69
69
  DatePicker,
70
70
  DateTimePicker,
71
71
  MultiValueCharInput,
72
+ NumberWithSelect,
72
73
  SelectWithDisabled,
73
74
  SelectWithPK,
74
75
  SlugWidget,
@@ -126,6 +127,7 @@ __all__ = (
126
127
  "MultiValueCharInput",
127
128
  "MultipleContentTypeField",
128
129
  "NullableDateField",
130
+ "NumberWithSelect",
129
131
  "NumericArrayField",
130
132
  "PrefixFieldMixin",
131
133
  "ReturnURLForm",
@@ -6,7 +6,7 @@ from django import forms
6
6
  from django.forms.models import ModelChoiceIterator
7
7
  from django.urls import get_script_prefix
8
8
 
9
- from nautobot.core import choices
9
+ from nautobot.core import choices as core_choices
10
10
  from nautobot.core.forms import utils
11
11
 
12
12
  __all__ = (
@@ -19,6 +19,7 @@ __all__ = (
19
19
  "ContentTypeSelect",
20
20
  "DatePicker",
21
21
  "DateTimePicker",
22
+ "NumberWithSelect",
22
23
  "SelectWithDisabled",
23
24
  "SelectWithPK",
24
25
  "SlugWidget",
@@ -68,7 +69,7 @@ class ColorSelect(forms.Select):
68
69
  option_template_name = "widgets/colorselect_option.html"
69
70
 
70
71
  def __init__(self, *args, **kwargs):
71
- kwargs["choices"] = utils.add_blank_choice(choices.ColorChoices)
72
+ kwargs["choices"] = utils.add_blank_choice(core_choices.ColorChoices)
72
73
  super().__init__(*args, **kwargs)
73
74
  self.attrs["class"] = "nautobot-select2-color-picker"
74
75
 
@@ -283,3 +284,21 @@ class ClearableFileInput(forms.ClearableFileInput):
283
284
 
284
285
  class Media:
285
286
  js = ["bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js"]
287
+
288
+
289
+ class NumberWithSelect(forms.NumberInput):
290
+ template_name = "widgets/number_input_with_choices.html"
291
+
292
+ def __init__(self, choices=None, *args, **kwargs):
293
+ super().__init__(*args, **kwargs)
294
+ if choices is None:
295
+ self.choices = []
296
+ elif hasattr(choices, "CHOICES"):
297
+ self.choices = core_choices.unpack_grouped_choices(choices.CHOICES)
298
+ else:
299
+ self.choices = core_choices.unpack_grouped_choices(choices)
300
+
301
+ def get_context(self, name, value, attrs):
302
+ context = super().get_context(name, value, attrs)
303
+ context["widget"]["choices"] = self.choices
304
+ return context
@@ -43,6 +43,7 @@ from nautobot.extras.jobs import (
43
43
  FileVar,
44
44
  Job,
45
45
  MultiChoiceVar,
46
+ MultiObjectVar,
46
47
  ObjectVar,
47
48
  RunJobTaskFailed,
48
49
  StringVar,
@@ -529,6 +530,60 @@ class RunRegisteredDataComplianceRules(Job):
529
530
  clean_compliance_rules_results_for_instance(instance=validated_object, excluded_pks=[result.pk])
530
531
 
531
532
 
533
+ class ValidateModelData(Job):
534
+ """Clean and validate data in all records of a given content type(s)."""
535
+
536
+ class Meta:
537
+ name = "Validate Model Data"
538
+ description = "Run `full_clean()` against all records of a given type(s) to check for data validity."
539
+ has_sensitive_variables = False
540
+ read_only = True
541
+ # Validating large amounts of data may take substantial processing time
542
+ soft_time_limit = 1800
543
+ time_limit = 2000
544
+
545
+ content_types = MultiObjectVar(
546
+ model=ContentType,
547
+ description="Type(s) of objects to validate.",
548
+ label="Content Types",
549
+ query_params={"can_view": True},
550
+ required=True,
551
+ )
552
+ verbose = BooleanVar(default=False, label="Verbose output?")
553
+
554
+ def run(self, *, content_types, verbose=False): # pylint:disable=arguments-differ
555
+ for content_type in content_types:
556
+ model = content_type.model_class()
557
+ if model is None:
558
+ self.fail(
559
+ "Couldn't locate Python model for content-type %s.%s",
560
+ content_type.app_label,
561
+ content_type.model,
562
+ )
563
+ continue
564
+
565
+ try:
566
+ records = model.objects.restrict(self.user, "view")
567
+ except AttributeError: # Not a RestrictedQuerySet?
568
+ if self.user.is_superuser: # i.e., permissions exempt
569
+ records = model.objects.all()
570
+ else:
571
+ self.fail("Unable to apply access permissions to %s.%s", content_type.app_label, content_type.model)
572
+
573
+ if not records.exists():
574
+ self.logger.warning("No %s found", model._meta.verbose_name_plural)
575
+ continue
576
+
577
+ self.logger.info("Validating %d %s", records.count(), model._meta.verbose_name_plural)
578
+ for record in records.iterator():
579
+ try:
580
+ record.full_clean()
581
+ if verbose:
582
+ self.logger.success("Validated successfully", extra={"object": record})
583
+ except ValidationError as err:
584
+ self.fail("Validation error: `%s`", err, extra={"object": record})
585
+
586
+
532
587
  jobs = [
533
588
  BulkDeleteObjects,
534
589
  BulkEditObjects,
@@ -540,5 +595,6 @@ jobs = [
540
595
  RefreshDynamicGroupCaches,
541
596
  RefreshDynamicGroupCacheJobButtonReceiver,
542
597
  RunRegisteredDataComplianceRules,
598
+ ValidateModelData,
543
599
  ]
544
600
  register_jobs(*jobs)
@@ -338,9 +338,9 @@ class Command(BaseCommand):
338
338
  _create_batch(WirelessNetworksWithMembersFactory, 5, description="with members")
339
339
  # make sure we have some supported data rates that have null relationships to make filter tests happy
340
340
  _create_batch(SupportedDataRateFactory, 10, description="without any associated objects")
341
- _create_batch(VPNPhase1PolicyFactory, 10)
342
- _create_batch(VPNPhase2PolicyFactory, 10)
343
- _create_batch(VPNProfileFactory, 20)
341
+ _create_batch(VPNPhase1PolicyFactory, 20)
342
+ _create_batch(VPNPhase2PolicyFactory, 20)
343
+ _create_batch(VPNProfileFactory, 30)
344
344
  _create_batch(VPNFactory, 10)
345
345
  _create_batch(VPNTunnelEndpointFactory, 20)
346
346
  _create_batch(VPNTunnelFactory, 10)
@@ -102,6 +102,17 @@ class BaseModel(models.Model):
102
102
 
103
103
  raise AttributeError(f"Cannot find a URL for {self} ({self._meta.app_label}.{self._meta.model_name})")
104
104
 
105
+ @property
106
+ def page_title(self):
107
+ """
108
+ Property used by Title and Breadcrumbs to display link to the object or title at detail page.
109
+ """
110
+ if hasattr(self, "name"):
111
+ return self.name
112
+ if hasattr(self, "display"):
113
+ return self.display
114
+ return str(self)
115
+
105
116
  @property
106
117
  def present_in_database(self):
107
118
  """
@@ -147,7 +147,7 @@ def serialize_object_v2(obj):
147
147
  # Try serializing obj(model instance) using its API Serializer
148
148
  try:
149
149
  serializer_class = get_serializer_for_model(obj.__class__)
150
- data = serializer_class(obj, context={"request": None, "depth": 1}).data
150
+ data = serializer_class(obj, context={"request": None, "depth": 1, "exclude_m2m": False}).data
151
151
  except SerializerNotFound:
152
152
  # Fall back to generic JSON representation of obj
153
153
  data = serialize_object(obj)