From 00ab43f7b731dd7582df1961133758da852debe5 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 7 Dec 2023 13:37:08 -0800 Subject: [PATCH 1/5] Nested select dynamic levels --- panel/tests/widgets/test_select.py | 39 ++++++++++++++++++++++++++++++ panel/widgets/select.py | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/panel/tests/widgets/test_select.py b/panel/tests/widgets/test_select.py index 0482a98bed..6e9df09fe3 100644 --- a/panel/tests/widgets/test_select.py +++ b/panel/tests/widgets/test_select.py @@ -601,6 +601,45 @@ def list_options(level, value): assert select._max_depth == 3 +def test_nested_select_dynamic_levels(document, comm): + select = NestedSelect( + options={ + "Easy": {"Easy_A": {}, "Easy_B": {}}, + "Medium": { + "Medium_A": {}, + "Medium_B": {}, + "Medium_C": { + "Medium_C_1": ["Medium_C_1_1"], + "Medium_C_2": ["Medium_C_2_1", "Medium_C_2_2"], + }, + }, + }, + levels=["Source", "Product", "Var", "GPH"], + ) + select.value = {"Source": "Easy", "Product": "Easy_A"} + assert select._widgets[0].visible + assert select._widgets[1].visible + assert not select._widgets[2].visible + assert not select._widgets[3].visible + + assert select._widgets[0].options == ["Easy", "Medium"] + assert select._widgets[1].options == ["Easy_A", "Easy_B"] + assert select._widgets[2].options == [] + assert select._widgets[3].options == [] + + # now update to Medium + select.value = {"Source": "Medium", "Product": "Medium_C"} + assert select._widgets[0].visible + assert select._widgets[1].visible + assert select._widgets[2].visible + assert select._widgets[3].visible + + assert select._widgets[0].options == ["Easy", "Medium"] + assert select._widgets[1].options == ["Medium_A", "Medium_B", "Medium_C"] + assert select._widgets[2].options == ["Medium_C_1", "Medium_C_2"] + assert select._widgets[3].options == ["Medium_C_1_1"] + + def test_nested_select_callable_must_have_levels(document, comm): def list_options(level, value): pass diff --git a/panel/widgets/select.py b/panel/widgets/select.py index 03a7d5e3b1..db917d37df 100644 --- a/panel/widgets/select.py +++ b/panel/widgets/select.py @@ -562,7 +562,7 @@ def _update_widget_options_interactively(self, event): options = options[select.value] else: options = options[list(options.keys())[0]] - visible = True + visible = bool(options) if i < start_i: # If the select widget is before the one From c13819ca7676eabfa7e7f5ebc2707a777a02b96b Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 7 Dec 2023 14:29:07 -0800 Subject: [PATCH 2/5] Fix for 1 level widgets --- panel/tests/widgets/test_select.py | 28 +++++++++++++++++++++------- panel/widgets/select.py | 4 +++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/panel/tests/widgets/test_select.py b/panel/tests/widgets/test_select.py index 6e9df09fe3..62c85a97cf 100644 --- a/panel/tests/widgets/test_select.py +++ b/panel/tests/widgets/test_select.py @@ -459,7 +459,7 @@ def test_nested_select_partial_options_set(document, comm): }, } select = NestedSelect(options=options) - select.options = {"Ben": {}} + select.options = {"Ben": []} assert select._widgets[0].value == 'Ben' assert select._widgets[0].visible assert select.value == {0: 'Ben'} @@ -607,38 +607,52 @@ def test_nested_select_dynamic_levels(document, comm): "Easy": {"Easy_A": {}, "Easy_B": {}}, "Medium": { "Medium_A": {}, - "Medium_B": {}, + "Medium_B": {"Medium_B_1": []}, "Medium_C": { "Medium_C_1": ["Medium_C_1_1"], "Medium_C_2": ["Medium_C_2_1", "Medium_C_2_2"], }, }, + "Hard": {} }, - levels=["Source", "Product", "Var", "GPH"], + levels=["A", "B", "C", "D"], ) - select.value = {"Source": "Easy", "Product": "Easy_A"} + select + select.value = {"A": "Easy", "B": "Easy_A"} assert select._widgets[0].visible assert select._widgets[1].visible assert not select._widgets[2].visible assert not select._widgets[3].visible - assert select._widgets[0].options == ["Easy", "Medium"] + assert select._widgets[0].options == ["Easy", "Medium", "Hard"] assert select._widgets[1].options == ["Easy_A", "Easy_B"] assert select._widgets[2].options == [] assert select._widgets[3].options == [] # now update to Medium - select.value = {"Source": "Medium", "Product": "Medium_C"} + select.value = {"A": "Medium", "B": "Medium_C"} assert select._widgets[0].visible assert select._widgets[1].visible assert select._widgets[2].visible assert select._widgets[3].visible - assert select._widgets[0].options == ["Easy", "Medium"] + assert select._widgets[0].options == ["Easy", "Medium", "Hard"] assert select._widgets[1].options == ["Medium_A", "Medium_B", "Medium_C"] assert select._widgets[2].options == ["Medium_C_1", "Medium_C_2"] assert select._widgets[3].options == ["Medium_C_1_1"] + # now update to Hard + select.value = {"A": "Hard"} + assert select._widgets[0].visible + assert not select._widgets[1].visible + assert not select._widgets[2].visible + assert not select._widgets[3].visible + + assert select._widgets[0].options == ["Easy", "Medium", "Hard"] + assert select._widgets[1].options == [] + assert select._widgets[2].options == [] + assert select._widgets[3].options == [] + def test_nested_select_callable_must_have_levels(document, comm): def list_options(level, value): diff --git a/panel/widgets/select.py b/panel/widgets/select.py index db917d37df..9295464e50 100644 --- a/panel/widgets/select.py +++ b/panel/widgets/select.py @@ -414,7 +414,9 @@ def _find_max_depth(self, d, depth=1): for value in d.values(): if isinstance(value, dict): max_depth = max(max_depth, self._find_max_depth(value, depth + 1)) - if len(value) == 0: + # dict means it's a level, so it's not the last level + # list means it's a leaf, so it's the last level + if isinstance(value, list) and len(value) == 0: max_depth -= 1 return max_depth From 8754347404106265746dd99c91c1f1a92110a5ee Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 7 Dec 2023 14:29:53 -0800 Subject: [PATCH 3/5] Fix for one level widgets --- panel/tests/widgets/test_select.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/panel/tests/widgets/test_select.py b/panel/tests/widgets/test_select.py index 62c85a97cf..e502a6d97f 100644 --- a/panel/tests/widgets/test_select.py +++ b/panel/tests/widgets/test_select.py @@ -629,6 +629,8 @@ def test_nested_select_dynamic_levels(document, comm): assert select._widgets[2].options == [] assert select._widgets[3].options == [] + assert select.value == {"A": "Easy", "B": "Easy_A", "C": None, "D": None} + # now update to Medium select.value = {"A": "Medium", "B": "Medium_C"} assert select._widgets[0].visible @@ -641,6 +643,8 @@ def test_nested_select_dynamic_levels(document, comm): assert select._widgets[2].options == ["Medium_C_1", "Medium_C_2"] assert select._widgets[3].options == ["Medium_C_1_1"] + assert select.value == {"A": "Medium", "B": "Medium_C", "C": "Medium_C_1", "D": "Medium_C_1_1"} + # now update to Hard select.value = {"A": "Hard"} assert select._widgets[0].visible @@ -653,6 +657,8 @@ def test_nested_select_dynamic_levels(document, comm): assert select._widgets[2].options == [] assert select._widgets[3].options == [] + assert select.value == {"A": "Hard", "B": None, "C": None, "D": None} + def test_nested_select_callable_must_have_levels(document, comm): def list_options(level, value): From 93816c0207e0dc44e55e2dcfb0758fd4ecf64321 Mon Sep 17 00:00:00 2001 From: Andrew <15331990+ahuang11@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:19:11 -0800 Subject: [PATCH 4/5] Update panel/tests/widgets/test_select.py --- panel/tests/widgets/test_select.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/panel/tests/widgets/test_select.py b/panel/tests/widgets/test_select.py index e502a6d97f..9979ee5828 100644 --- a/panel/tests/widgets/test_select.py +++ b/panel/tests/widgets/test_select.py @@ -617,8 +617,6 @@ def test_nested_select_dynamic_levels(document, comm): }, levels=["A", "B", "C", "D"], ) - select - select.value = {"A": "Easy", "B": "Easy_A"} assert select._widgets[0].visible assert select._widgets[1].visible assert not select._widgets[2].visible From 6d517eedf305ce99b7ff4cbd755084b0ac52897f Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 7 Dec 2023 15:39:46 -0800 Subject: [PATCH 5/5] Fix negative levels and values --- panel/widgets/select.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panel/widgets/select.py b/panel/widgets/select.py index 9295464e50..af667ee9be 100644 --- a/panel/widgets/select.py +++ b/panel/widgets/select.py @@ -385,7 +385,7 @@ def _gather_values_from_widgets(self, up_to_i=None): name = level.get("name", i) else: name = level - values[name] = select.value + values[name] = select.value if select.options else None return values @@ -416,7 +416,7 @@ def _find_max_depth(self, d, depth=1): max_depth = max(max_depth, self._find_max_depth(value, depth + 1)) # dict means it's a level, so it's not the last level # list means it's a leaf, so it's the last level - if isinstance(value, list) and len(value) == 0: + if isinstance(value, list) and len(value) == 0 and max_depth > 0: max_depth -= 1 return max_depth