diff --git a/vizro-core/changelog.d/20241213_161527_petar_pejovic_fix_model_manager_get_model_children.md b/vizro-core/changelog.d/20241213_161527_petar_pejovic_fix_model_manager_get_model_children.md new file mode 100644 index 000000000..0d990d011 --- /dev/null +++ b/vizro-core/changelog.d/20241213_161527_petar_pejovic_fix_model_manager_get_model_children.md @@ -0,0 +1,48 @@ + + + + + + + + +### Fixed + +- Enable that custom components can be nested arbitrarily deep inside basic type structures (e.g. lists within lists), and not just specific attributes. ([#929](https://github.com/mckinsey/vizro/pull/929)) + + + diff --git a/vizro-core/examples/scratch_dev/app.py b/vizro-core/examples/scratch_dev/app.py index 8866948f9..10daa1903 100644 --- a/vizro-core/examples/scratch_dev/app.py +++ b/vizro-core/examples/scratch_dev/app.py @@ -1,31 +1,69 @@ """Dev app to try things out.""" -from vizro import Vizro -import vizro.plotly.express as px -import vizro.models as vm -from vizro.tables import dash_ag_grid import pandas as pd +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro +from typing import List, Literal, Tuple +from vizro.models.types import ControlType +from dash import html -df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv") -columnDefs = [ - {"field": "athlete", "headerName": "The full Name of the athlete"}, - {"field": "age", "headerName": "The number of Years since the athlete was born"}, - {"field": "country", "headerName": "The Country the athlete was born in"}, - {"field": "sport", "headerName": "The Sport the athlete participated in"}, - {"field": "total", "headerName": "The Total number of medals won by the athlete"}, -] -defaultColDef = { - "wrapHeaderText": True, - "autoHeaderHeight": True, -} +class CustomGroup(vm.VizroBaseModel): + """Container to group controls.""" + + type: Literal["custom_group"] = "custom_group" + controls: List[Tuple[str, List[ControlType]]] = [[]] + + def build(self): + return html.Div( + children=[ + html.Div( + children=[html.Br(), html.H5(control_tuple[0]), *[control.build() for control in control_tuple[1]]], + ) + for control_tuple in self.controls + ] + ) + + +vm.Page.add_type("controls", CustomGroup) -# Test app ----------------- page = vm.Page( - title="Page Title", - components=[vm.AgGrid(figure=dash_ag_grid(df, columnDefs=columnDefs, defaultColDef=defaultColDef))], + title="Title", + components=[ + vm.Graph(id="graph_id", figure=px.scatter(px.data.iris(), x="sepal_width", y="sepal_length", color="species")), + ], + controls=[ + CustomGroup( + controls=[ + ( + "Categorical Filters", + [ + vm.Filter(column="species"), + ], + ), + ( + "Numeric Filters", + [ + vm.Filter(column="petal_length"), + vm.Filter(column="sepal_length"), + ], + ), + ], + ), + vm.Parameter( + targets=["graph_id.x"], + selector=vm.RadioItems( + title="Select X Axis", + options=["sepal_width", "sepal_length", "petal_width", "petal_length"], + value="sepal_width", + ), + ), + ], ) + + dashboard = vm.Dashboard(pages=[page]) if __name__ == "__main__": diff --git a/vizro-core/src/vizro/managers/_model_manager.py b/vizro-core/src/vizro/managers/_model_manager.py index fe19a1e11..b605b1e89 100644 --- a/vizro-core/src/vizro/managers/_model_manager.py +++ b/vizro-core/src/vizro/managers/_model_manager.py @@ -84,28 +84,25 @@ def _get_models( def __get_model_children(self, model: Model) -> Generator[Model, None, None]: """Iterates through children of `model`. - Currently looks only through certain fields so might miss some children models. + Currently, this method looks only through certain fields so might miss some children models. """ from vizro.models import VizroBaseModel if isinstance(model, VizroBaseModel): yield model + # We don't handle dicts of models at the moment. See below TO-DOs for how this will all be improved in future. + if isinstance(model, (list, tuple)): + for single_model in model: + yield from self.__get_model_children(single_model) + # TODO: in future this list should not be maintained manually. Instead we should look through all model children - # by looking at model.model_fields. + # by looking at model.model_fields. model_fields = ["components", "tabs", "controls", "actions", "selector"] for model_field in model_fields: if (model_field_value := getattr(model, model_field, None)) is not None: - if isinstance(model_field_value, list): - # For fields like components that are list of models. - for single_model_field_value in model_field_value: - yield from self.__get_model_children(single_model_field_value) - else: - # For fields that have single model like selector. - yield from self.__get_model_children(model_field_value) - # We don't handle dicts of models at the moment. See below TODO for how this will all be improved in - # future. + yield from self.__get_model_children(model_field_value) # TODO: Add navigation, accordions and other page objects. Won't be needed once have made whole model # manager work better recursively and have better ways to navigate the hierarchy. In pydantic v2 this would use