From 71af09db3db74f07340384d8c9a2f589d855b696 Mon Sep 17 00:00:00 2001 From: gugupy Date: Thu, 3 Jul 2025 11:33:59 +0530 Subject: [PATCH 1/5] fix: missing user permission validation for related views --- flask_appbuilder/baseviews.py | 4 ++++ .../templates/appbuilder/general/model/show.html | 14 ++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/flask_appbuilder/baseviews.py b/flask_appbuilder/baseviews.py index b273f2d14e..45e961da2e 100644 --- a/flask_appbuilder/baseviews.py +++ b/flask_appbuilder/baseviews.py @@ -1034,6 +1034,10 @@ def _get_related_views_widgets( widgets = widgets or {} widgets["related_views"] = [] for view in self._related_views: + # Skip related views if the current user does not have 'can_list' permission + if not self.appbuilder.sm.has_access("can_list", view.__class__.__name__): + continue + if orders.get(view.__class__.__name__): order_column, order_direction = orders.get(view.__class__.__name__) else: diff --git a/flask_appbuilder/templates/appbuilder/general/model/show.html b/flask_appbuilder/templates/appbuilder/general/model/show.html index 8c2510ca30..6f7dc4651f 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/show.html +++ b/flask_appbuilder/templates/appbuilder/general/model/show.html @@ -8,16 +8,22 @@
{% for view in related_views %}
- {{ widgets.get('related_views')[loop.index - 1](pk = pk)|safe }} + {% set widget_related_views = widgets.get('related_views') %} + {% if loop.index0 < widget_related_views|length %} + {{ widget_related_views[loop.index0](pk=pk)|safe }} + {% endif %}
{% endfor %} {% endif %} From c2fc9dd2782042991e74e1cfcd629a945025e2c6 Mon Sep 17 00:00:00 2001 From: gugupy Date: Thu, 3 Jul 2025 15:08:33 +0530 Subject: [PATCH 2/5] fix: all template that show related_views widget --- .../templates/appbuilder/general/model/edit.html | 5 ++++- .../templates/appbuilder/general/model/edit_cascade.html | 5 ++++- .../appbuilder/general/model/left_master_detail.html | 5 ++++- .../templates/appbuilder/general/model/show_cascade.html | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/flask_appbuilder/templates/appbuilder/general/model/edit.html b/flask_appbuilder/templates/appbuilder/general/model/edit.html index e0f04fc3f0..e2b95c070a 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/edit.html +++ b/flask_appbuilder/templates/appbuilder/general/model/edit.html @@ -15,7 +15,10 @@
{% for view in related_views %}
- {{ widgets.get('related_views')[loop.index - 1]()|safe }} + {% set widget_related_views = widgets.get('related_views') %} + {% if loop.index0 < widget_related_views|length %} + {{ widget_related_views[loop.index0](pk=pk)|safe }} + {% endif %}
{% endfor %} {% endif %} diff --git a/flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html b/flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html index 7b7e3fec63..6133cd062a 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html +++ b/flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html @@ -13,7 +13,10 @@ {% if related_views is defined %} {% for view in related_views %} {% call lib.accordion_tag(view.__class__.__name__,view.title, False) %} - {{ widgets.get('related_views')[loop.index - 1](pk = pk)|safe }} + {% set widget_related_views = widgets.get('related_views') %} + {% if loop.index0 < widget_related_views|length %} + {{ widget_related_views[loop.index0](pk=pk)|safe }} + {% endif %} {% endcall %} {% endfor %} {% endif %} diff --git a/flask_appbuilder/templates/appbuilder/general/model/left_master_detail.html b/flask_appbuilder/templates/appbuilder/general/model/left_master_detail.html index c26eceb7aa..8262e7550b 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/left_master_detail.html +++ b/flask_appbuilder/templates/appbuilder/general/model/left_master_detail.html @@ -12,7 +12,10 @@
{{ lib.panel_begin(view.list_title) }}
- {{ widgets.get('related_views')[loop.index - 1](pk = pk)|safe }} + {% set widget_related_views = widgets.get('related_views') %} + {% if loop.index0 < widget_related_views|length %} + {{ widget_related_views[loop.index0](pk=pk)|safe }} + {% endif %}
{{ lib.panel_end() }}
diff --git a/flask_appbuilder/templates/appbuilder/general/model/show_cascade.html b/flask_appbuilder/templates/appbuilder/general/model/show_cascade.html index 087e7fe2ce..cf2f78bdb5 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/show_cascade.html +++ b/flask_appbuilder/templates/appbuilder/general/model/show_cascade.html @@ -12,7 +12,10 @@ {% if related_views is defined %} {% for view in related_views %} {% call lib.accordion_tag(view.__class__.__name__,view.title, False) %} - {{ widgets.get('related_views')[loop.index - 1](pk = pk)|safe }} + {% set widget_related_views = widgets.get('related_views') %} + {% if loop.index0 < widget_related_views|length %} + {{ widget_related_views[loop.index0](pk=pk)|safe }} + {% endif %} {% endcall %} {% endfor %} {% endif %} From 5b4300cf16dd1eca8dc3ab7e9488a0f836d171b6 Mon Sep 17 00:00:00 2001 From: gugupy Date: Thu, 3 Jul 2025 16:00:26 +0530 Subject: [PATCH 3/5] fix: get related_views widget before loop start --- flask_appbuilder/templates/appbuilder/general/model/edit.html | 2 +- .../templates/appbuilder/general/model/edit_cascade.html | 2 +- .../templates/appbuilder/general/model/left_master_detail.html | 2 +- flask_appbuilder/templates/appbuilder/general/model/show.html | 2 +- .../templates/appbuilder/general/model/show_cascade.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flask_appbuilder/templates/appbuilder/general/model/edit.html b/flask_appbuilder/templates/appbuilder/general/model/edit.html index e2b95c070a..3a4dad749f 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/edit.html +++ b/flask_appbuilder/templates/appbuilder/general/model/edit.html @@ -13,9 +13,9 @@
+ {% set widget_related_views = widgets.get('related_views') %} {% for view in related_views %}
- {% set widget_related_views = widgets.get('related_views') %} {% if loop.index0 < widget_related_views|length %} {{ widget_related_views[loop.index0](pk=pk)|safe }} {% endif %} diff --git a/flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html b/flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html index 6133cd062a..cb32d793c1 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html +++ b/flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html @@ -11,9 +11,9 @@ {% block related_views %} {% if related_views is defined %} + {% set widget_related_views = widgets.get('related_views') %} {% for view in related_views %} {% call lib.accordion_tag(view.__class__.__name__,view.title, False) %} - {% set widget_related_views = widgets.get('related_views') %} {% if loop.index0 < widget_related_views|length %} {{ widget_related_views[loop.index0](pk=pk)|safe }} {% endif %} diff --git a/flask_appbuilder/templates/appbuilder/general/model/left_master_detail.html b/flask_appbuilder/templates/appbuilder/general/model/left_master_detail.html index 8262e7550b..772e2b9bbc 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/left_master_detail.html +++ b/flask_appbuilder/templates/appbuilder/general/model/left_master_detail.html @@ -8,11 +8,11 @@ {{ widgets.get('list')()|safe }} {{ lib.panel_end() }}
+{% set widget_related_views = widgets.get('related_views') %} {% for view in related_views %}
{{ lib.panel_begin(view.list_title) }}
- {% set widget_related_views = widgets.get('related_views') %} {% if loop.index0 < widget_related_views|length %} {{ widget_related_views[loop.index0](pk=pk)|safe }} {% endif %} diff --git a/flask_appbuilder/templates/appbuilder/general/model/show.html b/flask_appbuilder/templates/appbuilder/general/model/show.html index 6f7dc4651f..3db43f47da 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/show.html +++ b/flask_appbuilder/templates/appbuilder/general/model/show.html @@ -18,9 +18,9 @@
+ {% set widget_related_views = widgets.get('related_views') %} {% for view in related_views %}
- {% set widget_related_views = widgets.get('related_views') %} {% if loop.index0 < widget_related_views|length %} {{ widget_related_views[loop.index0](pk=pk)|safe }} {% endif %} diff --git a/flask_appbuilder/templates/appbuilder/general/model/show_cascade.html b/flask_appbuilder/templates/appbuilder/general/model/show_cascade.html index cf2f78bdb5..9a5e967110 100644 --- a/flask_appbuilder/templates/appbuilder/general/model/show_cascade.html +++ b/flask_appbuilder/templates/appbuilder/general/model/show_cascade.html @@ -10,9 +10,9 @@ {% block related_views %} {% if related_views is defined %} + {% set widget_related_views = widgets.get('related_views') %} {% for view in related_views %} {% call lib.accordion_tag(view.__class__.__name__,view.title, False) %} - {% set widget_related_views = widgets.get('related_views') %} {% if loop.index0 < widget_related_views|length %} {{ widget_related_views[loop.index0](pk=pk)|safe }} {% endif %} From 84e93bcc37db2ce00561140eab5a949cf5cfc5a5 Mon Sep 17 00:00:00 2001 From: gugupy Date: Thu, 3 Jul 2025 16:01:59 +0530 Subject: [PATCH 4/5] remove the related_view which has no permission --- flask_appbuilder/baseviews.py | 1 + 1 file changed, 1 insertion(+) diff --git a/flask_appbuilder/baseviews.py b/flask_appbuilder/baseviews.py index 45e961da2e..84b4ccf794 100644 --- a/flask_appbuilder/baseviews.py +++ b/flask_appbuilder/baseviews.py @@ -1036,6 +1036,7 @@ def _get_related_views_widgets( for view in self._related_views: # Skip related views if the current user does not have 'can_list' permission if not self.appbuilder.sm.has_access("can_list", view.__class__.__name__): + self._related_views.remove(view) continue if orders.get(view.__class__.__name__): From bce2c7719b1f657827f008e798c5c7a8b7b54b9e Mon Sep 17 00:00:00 2001 From: gugupy Date: Wed, 16 Jul 2025 20:34:55 +0530 Subject: [PATCH 5/5] show only allowed related views --- flask_appbuilder/baseviews.py | 6 +++++- flask_appbuilder/views.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/flask_appbuilder/baseviews.py b/flask_appbuilder/baseviews.py index 84b4ccf794..0ef328c912 100644 --- a/flask_appbuilder/baseviews.py +++ b/flask_appbuilder/baseviews.py @@ -679,6 +679,8 @@ class MyView(ModelView): """ _related_views = None """ internal list with ref to instantiated view classes """ + allowed_related_views = None + """ Holds related views where the user has 'can_list' permission. """ list_title = "" """ List Title, if not configured the default is 'List ' with pretty model name """ show_title = "" @@ -1032,13 +1034,15 @@ def _get_related_views_widgets( Model View widgets """ widgets = widgets or {} + self.allowed_related_views = [] widgets["related_views"] = [] for view in self._related_views: # Skip related views if the current user does not have 'can_list' permission if not self.appbuilder.sm.has_access("can_list", view.__class__.__name__): - self._related_views.remove(view) continue + self.allowed_related_views.append(view) + if orders.get(view.__class__.__name__): order_column, order_direction = orders.get(view.__class__.__name__) else: diff --git a/flask_appbuilder/views.py b/flask_appbuilder/views.py index 67cdf109fd..aa4480cc0b 100644 --- a/flask_appbuilder/views.py +++ b/flask_appbuilder/views.py @@ -571,7 +571,7 @@ def show(self, pk): pk=pk, title=self.show_title, widgets=widgets, - related_views=self._related_views, + related_views=self.allowed_related_views, ) """ @@ -609,7 +609,7 @@ def edit(self, pk): self.edit_template, title=self.edit_title, widgets=widgets, - related_views=self._related_views, + related_views=self.allowed_related_views, ) """ @@ -731,7 +731,7 @@ def list(self, pk=None): widgets = self._get_related_views_widgets( item, orders=orders, pages=pages, page_sizes=page_sizes, widgets=widgets ) - related_views = self._related_views + related_views = self.allowed_related_views else: related_views = []