nautobot 3.0.0a2__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (659) hide show
  1. nautobot/apps/choices.py +4 -2
  2. nautobot/apps/filters.py +7 -9
  3. nautobot/apps/models.py +2 -2
  4. nautobot/apps/ui.py +13 -1
  5. nautobot/apps/utils.py +8 -0
  6. nautobot/circuits/filters.py +3 -2
  7. nautobot/circuits/navigation.py +3 -2
  8. nautobot/circuits/templates/circuits/circuit_create.html +3 -3
  9. nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
  10. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
  11. nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
  12. nautobot/circuits/tests/integration/test_circuit.py +10 -13
  13. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
  14. nautobot/circuits/views.py +6 -2
  15. nautobot/cloud/filters.py +1 -1
  16. nautobot/cloud/navigation.py +3 -2
  17. nautobot/core/api/schema.py +1 -1
  18. nautobot/core/api/serializers.py +6 -1
  19. nautobot/core/api/urls.py +2 -0
  20. nautobot/core/api/views.py +12 -0
  21. nautobot/core/apps/__init__.py +11 -10
  22. nautobot/core/celery/__init__.py +3 -5
  23. nautobot/core/checks.py +46 -0
  24. nautobot/core/choices.py +1 -1
  25. nautobot/core/cli/bootstrap_v3_to_v5.py +105 -13
  26. nautobot/core/cli/migrate_deprecated_templates.py +227 -0
  27. nautobot/core/constants.py +3 -0
  28. nautobot/core/context_processors.py +9 -1
  29. nautobot/core/filters.py +4 -0
  30. nautobot/core/forms/__init__.py +2 -0
  31. nautobot/core/forms/forms.py +1 -1
  32. nautobot/core/forms/widgets.py +21 -2
  33. nautobot/core/jobs/__init__.py +62 -3
  34. nautobot/core/jobs/groups.py +31 -1
  35. nautobot/core/management/commands/generate_test_data.py +28 -9
  36. nautobot/core/models/__init__.py +11 -0
  37. nautobot/core/models/generics.py +9 -1
  38. nautobot/core/models/tree_queries.py +10 -5
  39. nautobot/core/models/utils.py +1 -1
  40. nautobot/core/settings.py +35 -19
  41. nautobot/core/settings.yaml +17 -33
  42. nautobot/core/signals.py +12 -1
  43. nautobot/core/tables.py +13 -6
  44. nautobot/core/templates/40x.html +1 -1
  45. nautobot/core/templates/500.html +2 -2
  46. nautobot/core/templates/admin/base.html +1 -2
  47. nautobot/core/templates/admin/change_list.html +9 -12
  48. nautobot/core/templates/admin/config/config.html +12 -12
  49. nautobot/core/templates/admin/index.html +3 -3
  50. nautobot/core/templates/base_django.html +1 -2
  51. nautobot/core/templates/buttons/export.html +1 -1
  52. nautobot/core/templates/components/button/dropdown.html +5 -3
  53. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  54. nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
  55. nautobot/core/templates/components/panel/panel.html +3 -3
  56. nautobot/core/templates/components/tab/content_wrapper.html +6 -7
  57. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
  58. nautobot/core/templates/echarts/echarts.html +22 -9
  59. nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
  60. nautobot/core/templates/generic/object_bulk_create.html +6 -5
  61. nautobot/core/templates/generic/object_bulk_delete.html +1 -1
  62. nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
  63. nautobot/core/templates/generic/object_bulk_edit.html +1 -1
  64. nautobot/core/templates/generic/object_bulk_import.html +1 -1
  65. nautobot/core/templates/generic/object_bulk_remove.html +2 -2
  66. nautobot/core/templates/generic/object_bulk_update.html +5 -4
  67. nautobot/core/templates/generic/object_create.html +5 -4
  68. nautobot/core/templates/generic/object_delete.html +1 -1
  69. nautobot/core/templates/generic/object_detail.html +1 -1
  70. nautobot/core/templates/generic/object_edit.html +1 -1
  71. nautobot/core/templates/generic/object_import.html +2 -1
  72. nautobot/core/templates/generic/object_list.html +12 -4
  73. nautobot/core/templates/generic/object_notes.html +5 -3
  74. nautobot/core/templates/generic/object_retrieve.html +4 -5
  75. nautobot/core/templates/graphene/graphiql.html +7 -8
  76. nautobot/core/templates/home.html +1 -1
  77. nautobot/core/templates/import_success.html +2 -1
  78. nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
  79. nautobot/core/templates/inc/created_updated.html +7 -3
  80. nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
  81. nautobot/core/templates/inc/footer.html +3 -1
  82. nautobot/core/templates/inc/form_static_field.html +6 -0
  83. nautobot/core/templates/inc/header.html +11 -1
  84. nautobot/core/templates/inc/image_attachments.html +2 -1
  85. nautobot/core/templates/inc/media.html +14 -0
  86. nautobot/core/templates/inc/nav_menu.html +3 -9
  87. nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
  88. nautobot/core/templates/inc/search_panel.html +4 -4
  89. nautobot/core/templates/login.html +4 -2
  90. nautobot/core/templates/nautobot_config.py.j2 +6 -11
  91. nautobot/core/templates/redoc_ui.html +7 -0
  92. nautobot/core/templates/rest_framework/api.html +103 -2
  93. nautobot/core/templates/search.html +1 -1
  94. nautobot/core/templates/swagger_ui.html +17 -3
  95. nautobot/core/templates/system_jobs/import_objects.html +1 -2
  96. nautobot/core/templates/utilities/confirmation_form.html +2 -2
  97. nautobot/core/templates/utilities/obj_table.html +10 -2
  98. nautobot/core/templates/utilities/render_field.html +7 -7
  99. nautobot/core/templates/utilities/render_jinja2.html +2 -2
  100. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +37 -4
  101. nautobot/core/templates/utilities/theme_preview.html +19 -3
  102. nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
  103. nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
  104. nautobot/core/templatetags/helpers.py +76 -18
  105. nautobot/core/testing/api.py +68 -9
  106. nautobot/core/testing/filters.py +0 -23
  107. nautobot/core/testing/integration.py +41 -17
  108. nautobot/core/testing/mixins.py +2 -0
  109. nautobot/core/testing/utils.py +18 -4
  110. nautobot/core/testing/views.py +104 -13
  111. nautobot/core/tests/integration/test_app_home.py +34 -30
  112. nautobot/core/tests/integration/test_app_navbar.py +3 -0
  113. nautobot/core/tests/integration/test_filters.py +48 -11
  114. nautobot/core/tests/integration/test_theme.py +22 -21
  115. nautobot/core/tests/nautobot_config.py +3 -0
  116. nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
  117. nautobot/core/tests/runner.py +8 -1
  118. nautobot/core/tests/test_api.py +5 -3
  119. nautobot/core/tests/test_breadcrumbs.py +27 -28
  120. nautobot/core/tests/test_checks.py +28 -0
  121. nautobot/core/tests/test_cli.py +40 -0
  122. nautobot/core/tests/test_config.py +2 -1
  123. nautobot/core/tests/test_forms.py +55 -13
  124. nautobot/core/tests/test_jobs.py +144 -3
  125. nautobot/core/tests/test_nautobot_server.py +2 -0
  126. nautobot/core/tests/test_navigations.py +76 -1
  127. nautobot/core/tests/test_patch_social_django.py +42 -0
  128. nautobot/core/tests/test_renderers.py +59 -0
  129. nautobot/core/tests/test_settings_schema.py +1 -0
  130. nautobot/core/tests/test_tables.py +3 -1
  131. nautobot/core/tests/test_templatetags_helpers.py +62 -13
  132. nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
  133. nautobot/core/tests/test_titles.py +0 -16
  134. nautobot/core/tests/test_tree_queries.py +14 -1
  135. nautobot/core/tests/test_ui.py +123 -4
  136. nautobot/core/tests/test_utils.py +72 -5
  137. nautobot/core/tests/test_views.py +159 -31
  138. nautobot/core/ui/breadcrumbs.py +70 -29
  139. nautobot/core/ui/bulk_buttons.py +1 -1
  140. nautobot/core/ui/choices.py +143 -27
  141. nautobot/core/ui/constants.py +76 -12
  142. nautobot/core/ui/echarts.py +15 -20
  143. nautobot/core/ui/object_detail.py +143 -55
  144. nautobot/core/ui/titles.py +3 -6
  145. nautobot/core/urls.py +20 -9
  146. nautobot/core/utils/cache.py +2 -1
  147. nautobot/core/utils/filtering.py +28 -18
  148. nautobot/core/utils/lookup.py +49 -8
  149. nautobot/core/utils/module_loading.py +21 -0
  150. nautobot/core/utils/patch_social_django.py +128 -0
  151. nautobot/core/views/__init__.py +38 -1
  152. nautobot/core/views/generic.py +3 -3
  153. nautobot/core/views/mixins.py +45 -22
  154. nautobot/core/views/renderers.py +4 -3
  155. nautobot/core/views/viewsets.py +2 -1
  156. nautobot/data_validation/apps.py +1 -5
  157. nautobot/data_validation/custom_validators.py +4 -4
  158. nautobot/data_validation/filters.py +1 -1
  159. nautobot/data_validation/forms.py +40 -0
  160. nautobot/data_validation/migrations/0001_initial.py +0 -7
  161. nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -14
  162. nautobot/data_validation/models.py +16 -7
  163. nautobot/data_validation/navigation.py +8 -1
  164. nautobot/data_validation/tables.py +12 -5
  165. nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
  166. nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
  167. nautobot/data_validation/tests/__init__.py +2 -2
  168. nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
  169. nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
  170. nautobot/data_validation/tests/test_filters.py +8 -6
  171. nautobot/data_validation/tests/test_models.py +15 -0
  172. nautobot/data_validation/tests/test_views.py +190 -32
  173. nautobot/data_validation/urls.py +2 -5
  174. nautobot/data_validation/views.py +73 -40
  175. nautobot/dcim/api/serializers.py +3 -13
  176. nautobot/dcim/apps.py +4 -0
  177. nautobot/dcim/choices.py +65 -0
  178. nautobot/dcim/constants.py +7 -0
  179. nautobot/dcim/custom_validators.py +84 -0
  180. nautobot/dcim/factory.py +1 -1
  181. nautobot/dcim/filter_mixins.py +353 -4
  182. nautobot/dcim/{filters/__init__.py → filters.py} +15 -36
  183. nautobot/dcim/forms.py +90 -4
  184. nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
  185. nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
  186. nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
  187. nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
  188. nautobot/dcim/migrations/0079_remove_device_location_tenant_name_uniqueness.py +16 -0
  189. nautobot/dcim/migrations/0080_device_name_data_migration.py +59 -0
  190. nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
  191. nautobot/dcim/models/device_component_templates.py +33 -1
  192. nautobot/dcim/models/device_components.py +98 -64
  193. nautobot/dcim/models/devices.py +30 -20
  194. nautobot/dcim/navigation.py +7 -6
  195. nautobot/dcim/tables/devices.py +18 -0
  196. nautobot/dcim/tables/devicetypes.py +8 -1
  197. nautobot/dcim/tables/racks.py +0 -2
  198. nautobot/dcim/tables/template_code.py +15 -15
  199. nautobot/dcim/templates/dcim/cable_connect.html +28 -112
  200. nautobot/dcim/templates/dcim/cable_trace.html +0 -4
  201. nautobot/dcim/templates/dcim/{cable_edit.html → cable_update.html} +1 -1
  202. nautobot/dcim/templates/dcim/consoleport.html +7 -6
  203. nautobot/dcim/templates/dcim/consoleserverport.html +7 -6
  204. nautobot/dcim/templates/dcim/device/config.html +2 -2
  205. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
  206. nautobot/dcim/templates/dcim/device/status.html +8 -8
  207. nautobot/dcim/templates/dcim/device.html +1 -1
  208. nautobot/dcim/templates/dcim/device_component_add.html +2 -2
  209. nautobot/dcim/templates/dcim/device_create.html +5 -3
  210. nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
  211. nautobot/dcim/templates/dcim/device_list.html +73 -10
  212. nautobot/dcim/templates/dcim/devicebay.html +1 -1
  213. nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
  214. nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
  215. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
  216. nautobot/dcim/templates/dcim/frontport.html +10 -9
  217. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  218. nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
  219. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
  220. nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
  221. nautobot/dcim/templates/dcim/interface.html +35 -7
  222. nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
  223. nautobot/dcim/templates/dcim/interface_edit.html +2 -0
  224. nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
  225. nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
  226. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  227. nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
  228. nautobot/dcim/templates/dcim/module/base.html +49 -9
  229. nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
  230. nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
  231. nautobot/dcim/templates/dcim/module_frontports.html +1 -1
  232. nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
  233. nautobot/dcim/templates/dcim/module_list.html +57 -8
  234. nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
  235. nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
  236. nautobot/dcim/templates/dcim/module_powerports.html +1 -1
  237. nautobot/dcim/templates/dcim/module_rearports.html +1 -1
  238. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  239. nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
  240. nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
  241. nautobot/dcim/templates/dcim/platform_create.html +1 -1
  242. nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
  243. nautobot/dcim/templates/dcim/powerport.html +6 -5
  244. nautobot/dcim/templates/dcim/rack_elevation_list.html +17 -5
  245. nautobot/dcim/templates/dcim/rack_retrieve.html +22 -15
  246. nautobot/dcim/templates/dcim/rearport.html +8 -7
  247. nautobot/dcim/templates/dcim/trace/cable.html +1 -1
  248. nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
  249. nautobot/dcim/templates/dcim/virtualchassis_update.html +15 -7
  250. nautobot/dcim/tests/integration/test_controller.py +4 -6
  251. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
  252. nautobot/dcim/tests/integration/test_create_device.py +0 -2
  253. nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
  254. nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
  255. nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
  256. nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
  257. nautobot/dcim/tests/test_api.py +194 -6
  258. nautobot/dcim/tests/test_custom_validators.py +229 -0
  259. nautobot/dcim/tests/test_filters.py +55 -7
  260. nautobot/dcim/tests/test_forms.py +110 -8
  261. nautobot/dcim/tests/test_graphql.py +44 -1
  262. nautobot/dcim/tests/test_models.py +328 -4
  263. nautobot/dcim/tests/test_tables.py +160 -0
  264. nautobot/dcim/tests/test_views.py +132 -29
  265. nautobot/dcim/urls.py +64 -21
  266. nautobot/dcim/utils.py +3 -3
  267. nautobot/dcim/views.py +777 -397
  268. nautobot/extras/api/views.py +60 -45
  269. nautobot/extras/choices.py +2 -13
  270. nautobot/extras/datasources/git.py +3 -1
  271. nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
  272. nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
  273. nautobot/extras/{filters/__init__.py → filters.py} +33 -48
  274. nautobot/extras/forms/forms.py +14 -15
  275. nautobot/extras/forms/mixins.py +0 -41
  276. nautobot/extras/jobs.py +2 -0
  277. nautobot/extras/jobs_ui.py +4 -3
  278. nautobot/extras/management/__init__.py +11 -0
  279. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
  280. nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
  281. nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
  282. nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
  283. nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
  284. nautobot/extras/models/__init__.py +1 -2
  285. nautobot/extras/models/approvals.py +33 -14
  286. nautobot/extras/models/change_logging.py +4 -0
  287. nautobot/extras/models/contacts.py +2 -0
  288. nautobot/extras/models/groups.py +44 -5
  289. nautobot/extras/models/jobs.py +60 -4
  290. nautobot/extras/models/mixins.py +28 -0
  291. nautobot/extras/models/models.py +23 -2
  292. nautobot/extras/models/secrets.py +1 -0
  293. nautobot/extras/models/statuses.py +0 -15
  294. nautobot/extras/navigation.py +13 -9
  295. nautobot/extras/plugins/__init__.py +33 -55
  296. nautobot/extras/plugins/marketplace_manifest.yml +49 -1
  297. nautobot/extras/plugins/tables.py +3 -3
  298. nautobot/extras/plugins/urls.py +2 -21
  299. nautobot/extras/plugins/utils.py +1 -33
  300. nautobot/extras/plugins/views.py +0 -9
  301. nautobot/extras/querysets.py +8 -0
  302. nautobot/extras/signals.py +20 -19
  303. nautobot/extras/tables.py +64 -68
  304. nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
  305. nautobot/extras/templates/extras/approval_dashboard.html +7 -5
  306. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
  307. nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
  308. nautobot/extras/templates/extras/configcontext_update.html +1 -0
  309. nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
  310. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
  311. nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
  312. nautobot/extras/templates/extras/gitrepository_result.html +0 -2
  313. nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
  314. nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
  315. nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
  316. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
  317. nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
  318. nautobot/extras/templates/extras/inc/json_format.html +10 -3
  319. nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
  320. nautobot/extras/templates/extras/job.html +29 -12
  321. nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
  322. nautobot/extras/templates/extras/job_edit.html +52 -46
  323. nautobot/extras/templates/extras/job_list.html +29 -25
  324. nautobot/extras/templates/extras/marketplace.html +5 -9
  325. nautobot/extras/templates/extras/object_configcontext.html +1 -1
  326. nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
  327. nautobot/extras/templates/extras/objectchange_retrieve.html +19 -39
  328. nautobot/extras/templates/extras/plugin_detail.html +29 -24
  329. nautobot/extras/templates/extras/plugins_list.html +16 -26
  330. nautobot/extras/templates/extras/role_retrieve.html +64 -0
  331. nautobot/extras/templates/extras/scheduledjob.html +4 -2
  332. nautobot/extras/templates/extras/secret_create.html +1 -1
  333. nautobot/extras/templatetags/custom_links.py +12 -12
  334. nautobot/extras/templatetags/job_buttons.py +14 -12
  335. nautobot/extras/test_jobs/invalid_import.py +9 -0
  336. nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
  337. nautobot/extras/test_jobs/missing_import.py +11 -0
  338. nautobot/extras/tests/integration/test_computedfields.py +8 -9
  339. nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
  340. nautobot/extras/tests/integration/test_customfields.py +9 -10
  341. nautobot/extras/tests/integration/test_dynamicgroups.py +12 -9
  342. nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
  343. nautobot/extras/tests/integration/test_plugins.py +18 -6
  344. nautobot/extras/tests/integration/test_relationships.py +0 -2
  345. nautobot/extras/tests/test_api.py +90 -18
  346. nautobot/extras/tests/test_approvals.py +38 -38
  347. nautobot/extras/tests/test_changelog.py +59 -5
  348. nautobot/extras/tests/test_customfields.py +22 -13
  349. nautobot/extras/tests/test_customfields_filters.py +479 -0
  350. nautobot/extras/tests/test_dynamicgroups.py +39 -1
  351. nautobot/extras/tests/test_filters.py +57 -22
  352. nautobot/extras/tests/test_forms.py +18 -21
  353. nautobot/extras/tests/test_jobs.py +25 -4
  354. nautobot/extras/tests/test_migrations.py +1 -0
  355. nautobot/extras/tests/test_models.py +51 -33
  356. nautobot/extras/tests/test_plugins.py +36 -10
  357. nautobot/extras/tests/test_utils.py +3 -4
  358. nautobot/extras/tests/test_views.py +52 -112
  359. nautobot/extras/urls.py +0 -14
  360. nautobot/extras/views.py +164 -71
  361. nautobot/ipam/factory.py +7 -0
  362. nautobot/ipam/filter_mixins.py +38 -0
  363. nautobot/ipam/filters.py +53 -38
  364. nautobot/ipam/formfields.py +1 -1
  365. nautobot/ipam/forms.py +6 -3
  366. nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
  367. nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
  368. nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
  369. nautobot/ipam/models.py +29 -2
  370. nautobot/ipam/navigation.py +3 -2
  371. nautobot/ipam/signals.py +71 -0
  372. nautobot/ipam/tables.py +19 -6
  373. nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
  374. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
  375. nautobot/ipam/templates/ipam/ipaddress.html +14 -0
  376. nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
  377. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
  378. nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
  379. nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
  380. nautobot/ipam/templates/ipam/namespace_update.html +15 -0
  381. nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
  382. nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
  383. nautobot/ipam/templates/ipam/prefix_list.html +14 -13
  384. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  385. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  386. nautobot/ipam/tests/migration/test_migrations.py +89 -0
  387. nautobot/ipam/tests/test_api.py +13 -6
  388. nautobot/ipam/tests/test_filters.py +36 -1
  389. nautobot/ipam/tests/test_forms.py +1 -1
  390. nautobot/ipam/tests/test_models.py +44 -2
  391. nautobot/ipam/tests/test_tables.py +1 -2
  392. nautobot/ipam/tests/test_utils.py +1 -1
  393. nautobot/ipam/tests/test_views.py +13 -14
  394. nautobot/ipam/ui.py +0 -17
  395. nautobot/ipam/utils/migrations.py +16 -2
  396. nautobot/ipam/utils/testing.py +9 -3
  397. nautobot/ipam/views.py +53 -11
  398. nautobot/load_balancers/__init__.py +0 -0
  399. nautobot/load_balancers/api/__init__.py +1 -0
  400. nautobot/load_balancers/api/serializers.py +75 -0
  401. nautobot/load_balancers/api/urls.py +23 -0
  402. nautobot/load_balancers/api/views.py +61 -0
  403. nautobot/load_balancers/apps.py +17 -0
  404. nautobot/load_balancers/choices.py +167 -0
  405. nautobot/load_balancers/filters.py +225 -0
  406. nautobot/load_balancers/forms.py +532 -0
  407. nautobot/load_balancers/management/commands/__init__.py +0 -0
  408. nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
  409. nautobot/load_balancers/migrations/0001_initial.py +465 -0
  410. nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
  411. nautobot/load_balancers/migrations/__init__.py +0 -0
  412. nautobot/load_balancers/models.py +423 -0
  413. nautobot/load_balancers/navigation.py +80 -0
  414. nautobot/load_balancers/tables.py +255 -0
  415. nautobot/load_balancers/tests/__init__.py +474 -0
  416. nautobot/load_balancers/tests/test_api.py +353 -0
  417. nautobot/load_balancers/tests/test_filters.py +134 -0
  418. nautobot/load_balancers/tests/test_forms.py +266 -0
  419. nautobot/load_balancers/tests/test_models.py +195 -0
  420. nautobot/load_balancers/tests/test_views.py +229 -0
  421. nautobot/load_balancers/urls.py +17 -0
  422. nautobot/load_balancers/views.py +248 -0
  423. nautobot/project-static/dist/css/github-dark.min.css +10 -0
  424. nautobot/project-static/dist/css/github.min.css +10 -0
  425. nautobot/project-static/dist/css/nautobot.css +1 -11
  426. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  427. nautobot/project-static/dist/js/libraries.js +1 -1
  428. nautobot/project-static/dist/js/libraries.js.map +1 -1
  429. nautobot/project-static/dist/js/nautobot.js +1 -1
  430. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  431. nautobot/project-static/js/cabletrace.js +1 -1
  432. nautobot/project-static/js/forms.js +13 -0
  433. nautobot/project-static/js/interface_filtering.js +20 -16
  434. nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
  435. nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
  436. nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
  437. nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
  438. nautobot/project-static/nautobot-icons/cloud.svg +1 -1
  439. nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
  440. nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
  441. nautobot/project-static/nautobot-icons/elements.svg +1 -1
  442. nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
  443. nautobot/project-static/nautobot-icons/hammer.svg +1 -1
  444. nautobot/project-static/nautobot-icons/organization.svg +3 -0
  445. nautobot/project-static/nautobot-icons/secrets.svg +1 -1
  446. nautobot/project-static/nautobot-icons/security.svg +3 -0
  447. nautobot/project-static/nautobot-icons/server.svg +1 -1
  448. nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
  449. nautobot/project-static/nautobot-icons/star.svg +1 -1
  450. nautobot/tenancy/api/serializers.py +1 -0
  451. nautobot/tenancy/api/views.py +2 -1
  452. nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
  453. nautobot/tenancy/navigation.py +3 -1
  454. nautobot/tenancy/tests/test_filters.py +0 -2
  455. nautobot/tenancy/views.py +2 -1
  456. nautobot/ui/package-lock.json +87 -4
  457. nautobot/ui/package.json +2 -1
  458. nautobot/ui/src/js/collapse.js +3 -3
  459. nautobot/ui/src/js/nautobot.js +16 -1
  460. nautobot/ui/src/js/select2.js +53 -2
  461. nautobot/ui/src/scss/colors.scss +1 -1
  462. nautobot/ui/src/scss/nautobot.scss +112 -30
  463. nautobot/ui/webpack.config.js +13 -0
  464. nautobot/users/templates/users/preferences.html +11 -2
  465. nautobot/users/templates/users/profile.html +45 -12
  466. nautobot/users/templates/users/sessionkey_delete.html +1 -1
  467. nautobot/users/tests/test_api.py +4 -0
  468. nautobot/users/views.py +4 -2
  469. nautobot/virtualization/filters.py +6 -1
  470. nautobot/virtualization/models.py +1 -68
  471. nautobot/virtualization/navigation.py +3 -2
  472. nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
  473. nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
  474. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
  475. nautobot/virtualization/tests/test_api.py +3 -0
  476. nautobot/virtualization/tests/test_filters.py +10 -1
  477. nautobot/virtualization/tests/test_models.py +45 -4
  478. nautobot/virtualization/views.py +4 -1
  479. nautobot/vpn/__init__.py +0 -0
  480. nautobot/vpn/api/serializers.py +113 -0
  481. nautobot/vpn/api/urls.py +19 -0
  482. nautobot/vpn/api/views.py +70 -0
  483. nautobot/vpn/apps.py +8 -0
  484. nautobot/vpn/choices.py +171 -0
  485. nautobot/vpn/factory.py +219 -0
  486. nautobot/vpn/filters.py +234 -0
  487. nautobot/vpn/forms.py +487 -0
  488. nautobot/vpn/homepage.py +19 -0
  489. nautobot/vpn/migrations/0001_initial.py +541 -0
  490. nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
  491. nautobot/vpn/migrations/__init__.py +0 -0
  492. nautobot/vpn/models.py +535 -0
  493. nautobot/vpn/navigation.py +98 -0
  494. nautobot/vpn/tables.py +383 -0
  495. nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
  496. nautobot/vpn/tests/__init__.py +0 -0
  497. nautobot/vpn/tests/test_api.py +336 -0
  498. nautobot/vpn/tests/test_filters.py +139 -0
  499. nautobot/vpn/tests/test_forms.py +293 -0
  500. nautobot/vpn/tests/test_models.py +147 -0
  501. nautobot/vpn/tests/test_views.py +300 -0
  502. nautobot/vpn/urls.py +16 -0
  503. nautobot/vpn/views.py +495 -0
  504. nautobot/wireless/navigation.py +3 -2
  505. nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
  506. nautobot/wireless/tests/test_api.py +1 -1
  507. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +15 -15
  508. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +514 -572
  509. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +1 -0
  510. nautobot/circuits/templates/circuits/circuit.html +0 -2
  511. nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
  512. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
  513. nautobot/circuits/templates/circuits/circuit_update.html +0 -1
  514. nautobot/circuits/templates/circuits/circuittermination.html +0 -2
  515. nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
  516. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
  517. nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
  518. nautobot/circuits/templates/circuits/circuittype.html +0 -2
  519. nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
  520. nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
  521. nautobot/circuits/templates/circuits/provider.html +0 -2
  522. nautobot/circuits/templates/circuits/provider_edit.html +0 -2
  523. nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
  524. nautobot/circuits/templates/circuits/provider_update.html +0 -1
  525. nautobot/circuits/templates/circuits/providernetwork.html +0 -2
  526. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
  527. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
  528. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
  529. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
  530. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
  531. nautobot/core/templates/buttons/import.html +0 -9
  532. nautobot/data_validation/template_content.py +0 -42
  533. nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
  534. nautobot/dcim/filters/mixins.py +0 -354
  535. nautobot/dcim/templates/dcim/controller/base.html +0 -2
  536. nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
  537. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
  538. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
  539. nautobot/dcim/templates/dcim/device/base.html +0 -2
  540. nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
  541. nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
  542. nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
  543. nautobot/dcim/templates/dcim/device/frontports.html +0 -2
  544. nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
  545. nautobot/dcim/templates/dcim/device/inventory.html +0 -2
  546. nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
  547. nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
  548. nautobot/dcim/templates/dcim/device/powerports.html +0 -2
  549. nautobot/dcim/templates/dcim/device/rearports.html +0 -2
  550. nautobot/dcim/templates/dcim/device/wireless.html +0 -2
  551. nautobot/dcim/templates/dcim/device_component.html +0 -2
  552. nautobot/dcim/templates/dcim/device_edit.html +0 -2
  553. nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
  554. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
  555. nautobot/dcim/templates/dcim/devicetype.html +0 -2
  556. nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
  557. nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
  558. nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
  559. nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
  560. nautobot/dcim/templates/dcim/location.html +0 -2
  561. nautobot/dcim/templates/dcim/location_edit.html +0 -2
  562. nautobot/dcim/templates/dcim/location_retrieve.html +0 -243
  563. nautobot/dcim/templates/dcim/locationtype.html +0 -2
  564. nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
  565. nautobot/dcim/templates/dcim/manufacturer.html +0 -2
  566. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
  567. nautobot/dcim/templates/dcim/platform.html +0 -2
  568. nautobot/dcim/templates/dcim/powerfeed.html +0 -2
  569. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
  570. nautobot/dcim/templates/dcim/powerpanel.html +0 -2
  571. nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
  572. nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
  573. nautobot/dcim/templates/dcim/rack.html +0 -2
  574. nautobot/dcim/templates/dcim/rack_edit.html +0 -2
  575. nautobot/dcim/templates/dcim/rackgroup.html +0 -2
  576. nautobot/dcim/templates/dcim/rackreservation.html +0 -2
  577. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
  578. nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
  579. nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
  580. nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
  581. nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
  582. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
  583. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
  584. nautobot/dcim/ui.py +0 -29
  585. nautobot/extras/templates/extras/computedfield.html +0 -2
  586. nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
  587. nautobot/extras/templates/extras/configcontext.html +0 -2
  588. nautobot/extras/templates/extras/configcontext_edit.html +0 -2
  589. nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
  590. nautobot/extras/templates/extras/configcontextschema.html +0 -2
  591. nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
  592. nautobot/extras/templates/extras/contact_retrieve.html +0 -2
  593. nautobot/extras/templates/extras/customfield.html +0 -2
  594. nautobot/extras/templates/extras/customfield_edit.html +0 -2
  595. nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
  596. nautobot/extras/templates/extras/customlink.html +0 -2
  597. nautobot/extras/templates/extras/dynamicgroup.html +0 -2
  598. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
  599. nautobot/extras/templates/extras/exporttemplate.html +0 -2
  600. nautobot/extras/templates/extras/gitrepository.html +0 -2
  601. nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
  602. nautobot/extras/templates/extras/graphqlquery.html +0 -2
  603. nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
  604. nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -97
  605. nautobot/extras/templates/extras/job_detail.html +0 -2
  606. nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
  607. nautobot/extras/templates/extras/jobhook.html +0 -2
  608. nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
  609. nautobot/extras/templates/extras/jobresult.html +0 -2
  610. nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
  611. nautobot/extras/templates/extras/note.html +0 -2
  612. nautobot/extras/templates/extras/note_retrieve.html +0 -1
  613. nautobot/extras/templates/extras/object_changelog.html +0 -2
  614. nautobot/extras/templates/extras/object_notes.html +0 -2
  615. nautobot/extras/templates/extras/objectchange.html +0 -2
  616. nautobot/extras/templates/extras/objectchange_list.html +0 -3
  617. nautobot/extras/templates/extras/relationship.html +0 -1
  618. nautobot/extras/templates/extras/secret.html +0 -1
  619. nautobot/extras/templates/extras/secret_edit.html +0 -1
  620. nautobot/extras/templates/extras/secretsgroup.html +0 -2
  621. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
  622. nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
  623. nautobot/extras/templates/extras/status.html +0 -2
  624. nautobot/extras/templates/extras/tag.html +0 -2
  625. nautobot/extras/templates/extras/tag_edit.html +0 -2
  626. nautobot/extras/templates/extras/tag_retrieve.html +0 -2
  627. nautobot/extras/templates/extras/team_retrieve.html +0 -2
  628. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
  629. nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
  630. nautobot/ipam/templates/ipam/prefix.html +0 -2
  631. nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
  632. nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
  633. nautobot/ipam/templates/ipam/rir.html +0 -2
  634. nautobot/ipam/templates/ipam/routetarget.html +0 -1
  635. nautobot/ipam/templates/ipam/service.html +0 -2
  636. nautobot/ipam/templates/ipam/service_edit.html +0 -2
  637. nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
  638. nautobot/ipam/templates/ipam/vlan.html +0 -2
  639. nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
  640. nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
  641. nautobot/ipam/templates/ipam/vlangroup.html +0 -2
  642. nautobot/ipam/templates/ipam/vrf.html +0 -1
  643. nautobot/tenancy/templates/tenancy/tenant.html +0 -2
  644. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
  645. nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
  646. nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
  647. nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
  648. nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
  649. nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
  650. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
  651. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
  652. nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
  653. nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
  654. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
  655. /nautobot/dcim/templates/dcim/{cable.html → cable_retrieve.html} +0 -0
  656. /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
  657. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
  658. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
  659. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
@@ -1,7 +1,8 @@
1
1
  from dataclasses import dataclass
2
+ from itertools import chain
2
3
  import logging
3
4
  from operator import attrgetter
4
- from typing import Any, Callable, Literal, Optional, Protocol, Type, Union
5
+ from typing import Any, Callable, Iterator, Literal, Optional, Protocol, Type, Union
5
6
  from urllib.parse import urlencode
6
7
 
7
8
  from django.db.models import Model
@@ -11,7 +12,7 @@ from django.urls import NoReverseMatch, reverse
11
12
  from nautobot.core.models.tree_queries import TreeModel
12
13
  from nautobot.core.templatetags import helpers
13
14
  from nautobot.core.ui.utils import get_absolute_url, render_component_template
14
- from nautobot.core.utils import lookup
15
+ from nautobot.core.utils import deprecation, lookup
15
16
  from nautobot.core.utils.lookup import get_model_for_view_name, get_model_from_name
16
17
  from nautobot.core.views.utils import get_obj_from_context
17
18
 
@@ -153,7 +154,7 @@ class BaseBreadcrumbItem:
153
154
  return params
154
155
  return {}
155
156
 
156
- def as_pair(self, context: Context) -> tuple[str, str]:
157
+ def as_pair(self, context: Context) -> Iterator[tuple[str, str]]:
157
158
  """
158
159
  Construct the (URL, label) pair for the breadcrumb.
159
160
 
@@ -163,12 +164,12 @@ class BaseBreadcrumbItem:
163
164
  context (Context): Context object used to resolve the breadcrumb parts.
164
165
 
165
166
  Returns:
166
- tuple[str, Optional[str]]: A tuple of (URL, label), where URL may be an empty string
167
+ Iterator[tuple[str, Optional[str]]]: A tuple of (URL, label), where URL may be an empty string
167
168
  if unresolved, and label.
168
169
  """
169
170
  url = self.get_url(context) or ""
170
171
  label = self.get_label(context)
171
- return url, label
172
+ yield url, label
172
173
 
173
174
 
174
175
  @dataclass
@@ -387,7 +388,7 @@ class InstanceBreadcrumbItem(BaseBreadcrumbItem):
387
388
  instance = self.get_instance(context)
388
389
  if not instance:
389
390
  return ""
390
- return getattr(instance, "display", str(instance))
391
+ return getattr(instance, "page_title", str(instance))
391
392
 
392
393
  def get_instance(self, context: Context) -> Optional[Model]:
393
394
  """
@@ -419,6 +420,7 @@ class InstanceParentBreadcrumbItem(InstanceBreadcrumbItem):
419
420
 
420
421
  Attributes:
421
422
  parent_key (str): Instance attribute to get the parent instance. Default: "parent".
423
+ parent (Optional[Model]): Instance or related object (parent).
422
424
  parent_query_param (Optional[str]): Query param name under which parent lookup key will be added into breadcrumb url. If None, will be the same as `parent_key`.
423
425
  parent_lookup_key (Optional[str]): Parent attribute which will be used to build the url and filter the instance list url. Can be set to None to use parent as key.
424
426
 
@@ -430,6 +432,7 @@ class InstanceParentBreadcrumbItem(InstanceBreadcrumbItem):
430
432
  """
431
433
 
432
434
  parent_key: str = "parent"
435
+ parent: Optional[Model] = None
433
436
  parent_query_param: Optional[str] = None
434
437
  parent_lookup_key: Optional[str] = "pk"
435
438
 
@@ -451,12 +454,14 @@ class InstanceParentBreadcrumbItem(InstanceBreadcrumbItem):
451
454
  Optional[str]: The URL as a string, or None.
452
455
  """
453
456
  instance = self.get_instance(context)
454
- parent = self.get_parent_attr(instance)
455
- if not instance or not parent:
457
+ related_object = self.get_related_object(instance)
458
+ if not instance or not related_object:
456
459
  return None
457
460
 
458
461
  view_name = lookup.get_route_for_model(instance, "list")
459
- return self.reverse_view_name(view_name, context, reverse_query_params=self.get_reverse_query_params(parent))
462
+ return self.reverse_view_name(
463
+ view_name, context, reverse_query_params=self.get_reverse_query_params(related_object)
464
+ )
460
465
 
461
466
  def get_label(self, context: Context) -> str:
462
467
  """
@@ -471,10 +476,10 @@ class InstanceParentBreadcrumbItem(InstanceBreadcrumbItem):
471
476
  if self.label or self.label_key:
472
477
  return super().get_label(context)
473
478
  instance = self.get_instance(context)
474
- parent = self.get_parent_attr(instance)
475
- if not instance or not parent:
479
+ related_object = self.get_related_object(instance)
480
+ if not instance or not related_object:
476
481
  return ""
477
- return getattr(parent, "display", str(parent))
482
+ return getattr(related_object, "page_title", str(related_object))
478
483
 
479
484
  def get_reverse_query_params(self, parent: Model) -> Optional[dict]:
480
485
  if self.parent_lookup_key is None:
@@ -485,12 +490,58 @@ class InstanceParentBreadcrumbItem(InstanceBreadcrumbItem):
485
490
  return {self.parent_query_param: query_param}
486
491
  return {}
487
492
 
488
- def get_parent_attr(self, instance: Model) -> Optional[Model]:
493
+ def get_related_object(self, instance: Model) -> Optional[Model]:
494
+ if self.parent:
495
+ return self.parent
496
+
489
497
  if hasattr(instance, self.parent_key):
490
498
  return getattr(instance, self.parent_key) or None
499
+
491
500
  return None
492
501
 
493
502
 
503
+ @dataclass
504
+ class AncestorsInstanceBreadcrumbItem(InstanceBreadcrumbItem):
505
+ """
506
+ Item class which can render list of ancestors of given instance.
507
+
508
+ By default, it won't include itself.
509
+
510
+ Attributes:
511
+ include_self (bool): Whether to include self as last item of ancestors.
512
+
513
+ Examples:
514
+ >>> AncestorsInstanceBreadcrumbItem(instance=location)
515
+ ("/dcim/locations/<pk>", "Parent 2"), ("/dcim/locations/<pk>", "Parent 1")
516
+ >>> AncestorsInstanceBreadcrumbItem(instance=location, include_self=True)
517
+ ("/dcim/locations/<pk>", "Parent 2"), ("/dcim/locations/<pk>", "Parent 1"), ("/dcim/locations/<pk>", "Location")
518
+ """
519
+
520
+ ancestor_item: Callable[[Model], BaseBreadcrumbItem] = lambda instance: InstanceBreadcrumbItem(instance=instance)
521
+ include_self: bool = False
522
+
523
+ def as_pair(self, context: Context) -> Iterator[tuple[str, str]]:
524
+ """
525
+ Construct the (URL, label) pair for the breadcrumb with all the instance ancestors.
526
+
527
+ Creates separate `InstanceBreadcrumbItem` with copied data from self.
528
+
529
+ Args:
530
+ context (Context): Context object used to resolve the breadcrumb parts.
531
+
532
+ Returns:
533
+ Iterator[tuple[str, Optional[str]]]: A tuple of (URL, label), where URL may be an empty string
534
+ if unresolved, and label.
535
+ """
536
+ instance = self.get_instance(context)
537
+
538
+ for ancestor in instance.ancestors():
539
+ yield from self.ancestor_item(ancestor).as_pair(context)
540
+
541
+ if self.include_self:
542
+ yield from self.ancestor_item(instance).as_pair(context)
543
+
544
+
494
545
  class Breadcrumbs:
495
546
  """
496
547
  Base class responsible for generating and rendering breadcrumbs for a page.
@@ -507,14 +558,7 @@ class Breadcrumbs:
507
558
  and there is `context['detail'] = True` set in context
508
559
 
509
560
  !!! important
510
- This class automatically adds the:
511
- - `InstanceBreadcrumbItem` at the end of `detail` breadcrumbs
512
- - `ModelBreadcrumbItem` at the beginning of `list` and `detail` breadcrumbs
513
-
514
- You can override this behavior by subclassing this class and updating
515
- the `list_breadcrumb_item` or `detail_breadcrumb_item` attributes.
516
-
517
- If you're using custom action other than `list` / `detail` you need to remember to add above breadcrumbs
561
+ If you're using custom action other than `list` / `detail` you need to remember to add list view breadcrumb manually
518
562
  if you need them in your custom action.
519
563
 
520
564
  Attributes:
@@ -553,7 +597,7 @@ class Breadcrumbs:
553
597
 
554
598
  # Set the default breadcrumbs
555
599
  self.items = {
556
- "list": [*self.breadcrumb_items],
600
+ "list": [],
557
601
  "detail": [*self.breadcrumb_items],
558
602
  }
559
603
 
@@ -561,9 +605,6 @@ class Breadcrumbs:
561
605
  if items:
562
606
  self.items = {**self.items, **items}
563
607
 
564
- # Built-in feature: always add the instance details at the end of breadcrumbs path
565
- self.items["detail"].append(InstanceBreadcrumbItem(label=detail_item_label))
566
-
567
608
  def get_breadcrumbs_items(self, context: Context) -> list[tuple[str, str]]:
568
609
  """
569
610
  Compute the list of breadcrumb items for the given context.
@@ -579,7 +620,8 @@ class Breadcrumbs:
579
620
  action = context.get("view_action", "")
580
621
  detail = context.get("detail", False)
581
622
  items = self.get_items_for_action(self.items, action, detail, context)
582
- return [item.as_pair(context) for item in items if item.should_render(context)]
623
+ items_pairs = [item.as_pair(context) for item in items if item.should_render(context)]
624
+ return list(chain.from_iterable(items_pairs))
583
625
 
584
626
  def filter_breadcrumbs_items(self, items: list[tuple[str, str]], context: Context) -> list[tuple[str, str]]:
585
627
  """
@@ -657,7 +699,7 @@ class Breadcrumbs:
657
699
  Provide additional data to include in the rendering context, based on the configuration of this component.
658
700
 
659
701
  Context updated here will be applied to resolving url and labels.
660
- Please ote that you can't override `breadcrumb_items` here because items are generated after this method call.
702
+ Please note that you can't override `breadcrumb_items` here because items are generated after this method call.
661
703
 
662
704
  Args:
663
705
  context (Context): The current context passed to `render()`.
@@ -668,6 +710,7 @@ class Breadcrumbs:
668
710
  return {}
669
711
 
670
712
 
713
+ @deprecation.class_deprecated("Functionality of this class was moved to the AncestorsInstanceBreadcrumbItem.")
671
714
  class AncestorsBreadcrumbs(Breadcrumbs):
672
715
  """
673
716
  Breadcrumbs class which can render list of ancestors of given instance.
@@ -675,7 +718,6 @@ class AncestorsBreadcrumbs(Breadcrumbs):
675
718
  Default behavior:
676
719
  - render breadcrumb item with link to the list view
677
720
  - dynamically add list of `InstanceBreadcrumbItem` from `ancestors()`
678
- - adds standard breadcrumb item with link to the details
679
721
  """
680
722
 
681
723
  def get_items_for_action(
@@ -714,7 +756,6 @@ class AncestorsBreadcrumbs(Breadcrumbs):
714
756
  return [
715
757
  ModelBreadcrumbItem(model=instance),
716
758
  *self.get_ancestors_items(instance),
717
- InstanceBreadcrumbItem(instance=instance, label=self.detail_item_label),
718
759
  ]
719
760
 
720
761
  def get_ancestors_items(self, instance: TreeModel) -> list[BaseBreadcrumbItem]:
@@ -32,7 +32,7 @@ class BaseBulkButton(object_detail.FormButton):
32
32
  class BulkRenameButton(BaseBulkButton):
33
33
  action = "rename"
34
34
  color = ButtonActionColorChoices.RENAME
35
- icon = "mdi-pencil"
35
+ icon = "mdi-rename"
36
36
  label = "Rename"
37
37
  weight = 200
38
38
 
@@ -66,37 +66,153 @@ class EChartsTypeChoices(ChoiceSet):
66
66
  )
67
67
 
68
68
 
69
- class EChartsTypeTheme(ChoiceSet):
70
- """Available chart theme for ECharts.
71
-
72
- Attributes:
73
- LIGHT (str): Light theme (value: light)
74
- DARK (str): Dark theme (value: dark)
75
- """
76
-
77
- LIGHT = "default"
78
- DARK = "dark"
69
+ class EChartsThemeColors(ChoiceSet):
70
+ """Available chart colors for ECharts."""
71
+
72
+ NAUTOBOT = "default"
73
+ LIGHTER_GREEN_AND_RED_ONLY = "lighter-green-red-only"
74
+ DEFAULT = NAUTOBOT
75
+
76
+ # Color lists - direct access
77
+ NAUTOBOT_COLORS = (
78
+ UI_COLORS["blue"],
79
+ UI_COLORS["purple"],
80
+ UI_COLORS["turquoise"],
81
+ UI_COLORS["orange"],
82
+ UI_COLORS["green"],
83
+ UI_COLORS["red"],
84
+ UI_COLORS["gray"],
85
+ UI_COLORS["blue-lighter"],
86
+ UI_COLORS["purple-lighter"],
87
+ UI_COLORS["turquoise-lighter"],
88
+ UI_COLORS["orange-lighter"],
89
+ UI_COLORS["green-lighter"],
90
+ UI_COLORS["red-lighter"],
91
+ UI_COLORS["gray-lighter"],
92
+ UI_COLORS["blue-darker"],
93
+ UI_COLORS["purple-darker"],
94
+ UI_COLORS["turquoise-darker"],
95
+ UI_COLORS["orange-darker"],
96
+ UI_COLORS["green-darker"],
97
+ UI_COLORS["red-darker"],
98
+ UI_COLORS["gray-darker"],
99
+ )
79
100
 
80
- DEFAULT = LIGHT
101
+ LIGHTER_GREEN_RED_COLORS = (
102
+ UI_COLORS["green-lighter"],
103
+ UI_COLORS["red-lighter"],
104
+ )
81
105
 
82
106
  CHOICES = (
83
- (LIGHT, "Default theme"),
84
- (DARK, "Dark theme"),
107
+ (NAUTOBOT, "Default Nautobot Colors"),
108
+ (LIGHTER_GREEN_AND_RED_ONLY, "Lighter Green and Red Only"),
85
109
  )
86
110
 
87
111
  COLORS = {
88
- LIGHT: [
89
- UI_COLORS["blue-0"]["light"],
90
- UI_COLORS["orange-0"]["light"],
91
- UI_COLORS["green-0"]["light"],
92
- UI_COLORS["red-0"]["light"],
93
- UI_COLORS["gray-0"]["light"],
94
- ],
95
- DARK: [
96
- UI_COLORS["blue-0"]["dark"],
97
- UI_COLORS["orange-0"]["dark"],
98
- UI_COLORS["green-0"]["dark"],
99
- UI_COLORS["red-0"]["dark"],
100
- UI_COLORS["gray-0"]["dark"],
101
- ],
112
+ NAUTOBOT: NAUTOBOT_COLORS,
113
+ LIGHTER_GREEN_AND_RED_ONLY: LIGHTER_GREEN_RED_COLORS,
102
114
  }
115
+
116
+
117
+ class NavigationIconChoices(ChoiceSet):
118
+ """Navigation icons for major Nautobot sections."""
119
+
120
+ DEVICES = "server"
121
+ IPAM = "sitemap-outline"
122
+ ORGANIZATION = "organization"
123
+ CIRCUITS = "cable-data"
124
+ VPN = "bus-shield"
125
+ ROUTING = "route"
126
+ POWER = "battery-3"
127
+ WIRELESS = "wifi"
128
+ DEVICE_LIFECYCLE = "device-lifecycle"
129
+ SECRETS = "secrets"
130
+ SECURITY = "security"
131
+ LOAD_BALANCERS = "arrow-decision"
132
+ VIRTUALIZATION = "cloud-upload"
133
+ CLOUD = "cloud"
134
+ DESIGN = "hammer"
135
+ APPROVAL_WORKFLOWS = "checkbox-circle"
136
+ EXTENSIBILITY = "extensibility"
137
+ GOLDEN_CONFIG = "sliders-vert-2"
138
+ JOBS = "share"
139
+ APPS = "elements"
140
+
141
+ CHOICES = (
142
+ (DEVICES, "Devices"),
143
+ (IPAM, "IPAM"),
144
+ (ORGANIZATION, "Organization"),
145
+ (CIRCUITS, "Circuits"),
146
+ (VPN, "VPN"),
147
+ (ROUTING, "Routing"),
148
+ (POWER, "Power"),
149
+ (WIRELESS, "Wireless"),
150
+ (DEVICE_LIFECYCLE, "Device Lifecycle"),
151
+ (SECRETS, "Secrets"),
152
+ (SECURITY, "Security"),
153
+ (LOAD_BALANCERS, "Load Balancers"),
154
+ (VIRTUALIZATION, "Virtualization"),
155
+ (CLOUD, "Cloud"),
156
+ (DESIGN, "Design"),
157
+ (APPROVAL_WORKFLOWS, "Approval Workflows"),
158
+ (EXTENSIBILITY, "Extensibility"),
159
+ (GOLDEN_CONFIG, "Golden Config"),
160
+ (JOBS, "Jobs"),
161
+ (APPS, "Apps"),
162
+ )
163
+
164
+
165
+ class NavigationWeightChoices(ChoiceSet):
166
+ """Navigation weights for major Nautobot sections."""
167
+
168
+ # In general we are looking to:
169
+ # - Keep data models before the default weight of 1000
170
+ # - Leave a gap between the default location of 1000
171
+ # - Keep non-model items after the default weight of 1000
172
+ # - Keep key items of GC, Jobs, and Apps at the end for easy access
173
+ DEVICES = 100
174
+ IPAM = 200
175
+ ORGANIZATION = 300
176
+ CIRCUITS = 400
177
+ VPN = 450
178
+ ROUTING = 500
179
+ POWER = 550
180
+ WIRELESS = 600
181
+ DEVICE_LIFECYCLE = 650
182
+ SECRETS = 700
183
+ SECURITY = 750
184
+ LOAD_BALANCERS = 800
185
+ VIRTUALIZATION = 850
186
+ CLOUD = 900
187
+ # We leave a gap here to allow for future expansion and don't use 1000
188
+ # since it the default weight for NavMenuTab if none is specified.
189
+ DESIGN = 1100
190
+ APPROVAL_WORKFLOWS = 1200
191
+ EXTENSIBILITY = 1300
192
+ # look to keep these last few items at the end of the nav for easy access
193
+ GOLDEN_CONFIG = 2000
194
+ JOBS = 2100
195
+ APPS = 2200
196
+
197
+ CHOICES = (
198
+ (DEVICES, "Devices"),
199
+ (IPAM, "IPAM"),
200
+ (ORGANIZATION, "Organization"),
201
+ (CIRCUITS, "Circuits"),
202
+ (VPN, "VPN"),
203
+ (ROUTING, "Routing"),
204
+ (POWER, "Power"),
205
+ (WIRELESS, "Wireless"),
206
+ (DEVICE_LIFECYCLE, "Device Lifecycle"),
207
+ (SECRETS, "Secrets"),
208
+ (SECURITY, "Security"),
209
+ (VIRTUALIZATION, "Virtualization"),
210
+ (LOAD_BALANCERS, "Load Balancers"),
211
+ (CLOUD, "Cloud"),
212
+ (DESIGN, "Design"),
213
+ (APPROVAL_WORKFLOWS, "Approval Workflows"),
214
+ (EXTENSIBILITY, "Extensibility"),
215
+ (GOLDEN_CONFIG, "Golden Config"),
216
+ (JOBS, "Jobs"),
217
+ (APPS, "Apps"),
218
+ )
@@ -1,22 +1,86 @@
1
1
  UI_COLORS = {
2
- "blue-0": {
2
+ "blue": {
3
3
  "light": "#007dff",
4
- "dark": "#045ab4",
4
+ "dark": "#339dff",
5
5
  },
6
- "orange-0": {
7
- "light": "#e07807",
8
- "dark": "#e07807",
6
+ "blue-darker": {
7
+ "light": "#0e4781",
8
+ "dark": "#255581",
9
+ },
10
+ "blue-lighter": {
11
+ "light": "#8cc5ff",
12
+ "dark": "#a3d3ff",
13
+ },
14
+ "gray": {
15
+ "light": "#b0b0b0",
16
+ "dark": "#828282",
17
+ },
18
+ "gray-darker": {
19
+ "light": "#5e5e5e",
20
+ "dark": "#494949",
21
+ },
22
+ "gray-lighter": {
23
+ "light": "#dbdbdb",
24
+ "dark": "#c7c7c7",
9
25
  },
10
- "green-0": {
26
+ "green": {
11
27
  "light": "#1ca92a",
12
- "dark": "#005c09",
28
+ "dark": "#2ecc40",
29
+ },
30
+ "green-darker": {
31
+ "light": "#1b5a21",
32
+ "dark": "#236a2b",
33
+ },
34
+ "green-lighter": {
35
+ "light": "#99d89f",
36
+ "dark": "#a1e8a9",
37
+ },
38
+ "orange": {
39
+ "light": "#e07807",
40
+ "dark": "#ff9933",
13
41
  },
14
- "red-0": {
42
+ "orange-darker": {
43
+ "light": "#734411",
44
+ "dark": "#815325",
45
+ },
46
+ "orange-lighter": {
47
+ "light": "#f1c28f",
48
+ "dark": "#ffd1a3",
49
+ },
50
+ "purple": {
51
+ "light": "#7b61ff",
52
+ "dark": "#a394ff",
53
+ },
54
+ "purple-darker": {
55
+ "light": "#463a81",
56
+ "dark": "#585181",
57
+ },
58
+ "purple-lighter": {
59
+ "light": "#c4b8ff",
60
+ "dark": "#d6cfff",
61
+ },
62
+ "red": {
15
63
  "light": "#e01f1f",
16
- "dark": "#960606",
64
+ "dark": "#ff4c4c",
65
+ },
66
+ "red-darker": {
67
+ "light": "#731c1c",
68
+ "dark": "#813131",
69
+ },
70
+ "red-lighter": {
71
+ "light": "#f19a9a",
72
+ "dark": "#ffaeae",
73
+ },
74
+ "turquoise": {
75
+ "light": "#00bfa5",
76
+ "dark": "#26d7c5",
77
+ },
78
+ "turquoise-darker": {
79
+ "light": "#0e6459",
80
+ "dark": "#1f6f67",
17
81
  },
18
- "gray-0": {
19
- "light": "#505d68",
20
- "dark": "#4f5868",
82
+ "turquoise-lighter": {
83
+ "light": "#8ce2d7",
84
+ "dark": "#9dede5",
21
85
  },
22
86
  }
@@ -5,7 +5,7 @@ from typing import Any, Callable
5
5
  from django.db.models import QuerySet
6
6
  from django.template import Context
7
7
 
8
- from nautobot.core.ui.choices import EChartsTypeChoices, EChartsTypeTheme
8
+ from nautobot.core.ui.choices import EChartsThemeColors, EChartsTypeChoices
9
9
  from nautobot.core.utils.lookup import resolve_attr
10
10
 
11
11
 
@@ -216,7 +216,7 @@ class EChartsBase:
216
216
  x_label="X",
217
217
  y_label="Y",
218
218
  legend=None,
219
- theme=EChartsTypeTheme.DEFAULT,
219
+ theme_colors=None,
220
220
  renderer="canvas",
221
221
  show_toolbox=True,
222
222
  save_image_options=None,
@@ -245,7 +245,8 @@ class EChartsBase:
245
245
  }
246
246
  You can also hide the legend entirely by setting `"show": False`.
247
247
  More details here: https://echarts.apache.org/handbook/en/concepts/legend.
248
- theme (str): Theme for chart (default: EChartsTypeTheme.DEFAULT).
248
+ theme_colors (list[dict[str, str]]): Color palette for chart, each color is {'light': str, 'dark': str}
249
+ (default: EChartsThemeColors.NAUTOBOT_COLORS).
249
250
  renderer (str): If the renderer is set to 'canvas' when chart initialized (default), then
250
251
  'png' (default) and 'jpg' are supported. If the renderer is set to 'svg' when chart
251
252
  initialized, then only 'svg' is supported for type. See more details:
@@ -271,7 +272,7 @@ class EChartsBase:
271
272
  self.x_label = x_label
272
273
  self.y_label = y_label
273
274
  self.legend = legend or {}
274
- self.theme = theme
275
+ self.theme_colors = theme_colors or EChartsThemeColors.NAUTOBOT_COLORS
275
276
  self.renderer = renderer
276
277
  self.show_toolbox = show_toolbox
277
278
  self.save_image_options = {"name": self.header or "echart", "show": True, **(save_image_options or {})}
@@ -279,10 +280,12 @@ class EChartsBase:
279
280
  self.permission = permission
280
281
  self.combined_with = combined_with
281
282
 
282
- @property
283
- def get_tranform_data(self) -> dict[str, Any]:
283
+ def get_transform_data(self, context: Context | None = None) -> dict[str, Any]:
284
284
  """Get the chart data in ECharts format, ready for rendering."""
285
- resolved_data = self._data() if callable(self._data) else self._data
285
+ if context and callable(self._data):
286
+ resolved_data = self._data(context)
287
+ else:
288
+ resolved_data = self._data() if callable(self._data) else self._data
286
289
  return self._transform_data(resolved_data or {})
287
290
 
288
291
  @property
@@ -299,11 +302,6 @@ class EChartsBase:
299
302
  """
300
303
  return self._data
301
304
 
302
- @data.setter
303
- def data(self, data):
304
- """Set the data."""
305
- self._data = data
306
-
307
305
  @property
308
306
  def chart_type(self):
309
307
  """Get the current chart type."""
@@ -378,13 +376,9 @@ class EChartsBase:
378
376
 
379
377
  return original
380
378
 
381
- def get_theme_colors(self):
382
- """Map SCSS palette to echarts theme colors (manual sync)."""
383
- return EChartsTypeTheme.COLORS.get(self.theme, EChartsTypeTheme.COLORS[EChartsTypeTheme.DEFAULT])
384
-
385
- def get_config(self):
379
+ def get_config(self, context: Context | None = None):
386
380
  """Return a dict ready to dump into echarts option JSON."""
387
- data = self.get_tranform_data
381
+ data = self.get_transform_data(context=context)
388
382
 
389
383
  # Base configuration
390
384
  config = {
@@ -393,7 +387,7 @@ class EChartsBase:
393
387
  "toolbox": self.strategy.get_toolbox_config(
394
388
  self.show_toolbox, self.save_image_options, self.data_view_options
395
389
  ),
396
- "color": self.get_theme_colors(),
390
+ "color": self.theme_colors,
397
391
  "legend": self.legend,
398
392
  }
399
393
  axis_config = self.strategy.get_axis_config(data, self.x_label, self.y_label)
@@ -408,7 +402,8 @@ class EChartsBase:
408
402
 
409
403
  # Handle combined charts
410
404
  if self.combined_with:
411
- combined_series = self.combined_with.strategy.get_series_config(self.combined_with.data)
405
+ data = self.combined_with.get_transform_data(context=context)
406
+ combined_series = self.combined_with.strategy.get_series_config(data)
412
407
  config["series"].extend(combined_series)
413
408
 
414
409
  return config