From 0f37e9930bce0dcc33d20e9db0ad4bfa8f7017e0 Mon Sep 17 00:00:00 2001 From: "pappenreiter.thomas" Date: Tue, 4 Apr 2023 15:11:15 +0200 Subject: [PATCH 1/7] fix: using base_filters with FilterEqualFunction not working for relation fields --- examples/extendsecurity/__init__.py | 0 examples/extendsecurity/testdata.py | 4 ++-- flask_appbuilder/models/sqla/interface.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 examples/extendsecurity/__init__.py diff --git a/examples/extendsecurity/__init__.py b/examples/extendsecurity/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/extendsecurity/testdata.py b/examples/extendsecurity/testdata.py index e0cfd24848..6d2c7518a4 100644 --- a/examples/extendsecurity/testdata.py +++ b/examples/extendsecurity/testdata.py @@ -2,8 +2,8 @@ import logging import random -from .app import appbuilder, db, create_app -from .app.models import ContactGroup, Gender, Contact, Company +from app import appbuilder, db, create_app +from app.models import ContactGroup, Gender, Contact, Company log = logging.getLogger(__name__) diff --git a/flask_appbuilder/models/sqla/interface.py b/flask_appbuilder/models/sqla/interface.py index f52d5b1fe7..a08b2812f3 100644 --- a/flask_appbuilder/models/sqla/interface.py +++ b/flask_appbuilder/models/sqla/interface.py @@ -344,8 +344,8 @@ def get_inner_filters(self, filters: Optional[Filters]) -> Filters: if not is_column_dotted(flt.column_name): _filters.append((flt.column_name, flt.__class__, value)) elif self.is_relation_many_to_one( - flt.column_name - ) or self.is_relation_one_to_one(flt.column_name): + get_column_root_relation(flt.column_name) + ) or self.is_relation_one_to_one(get_column_root_relation(flt.column_name)): _filters.append((flt.column_name, flt.__class__, value)) inner_filters.add_filter_list(_filters) return inner_filters From 68a87e8513e2951cb4fdc6b12a319c7e1d67694d Mon Sep 17 00:00:00 2001 From: "pappenreiter.thomas" Date: Tue, 4 Apr 2023 15:11:15 +0200 Subject: [PATCH 2/7] fix: using base_filters with FilterEqualFunction not working for relation fields --- examples/extendsecurity/__init__.py | 0 examples/extendsecurity/testdata.py | 4 ++-- flask_appbuilder/models/sqla/interface.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 examples/extendsecurity/__init__.py diff --git a/examples/extendsecurity/__init__.py b/examples/extendsecurity/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/extendsecurity/testdata.py b/examples/extendsecurity/testdata.py index e0cfd24848..6d2c7518a4 100644 --- a/examples/extendsecurity/testdata.py +++ b/examples/extendsecurity/testdata.py @@ -2,8 +2,8 @@ import logging import random -from .app import appbuilder, db, create_app -from .app.models import ContactGroup, Gender, Contact, Company +from app import appbuilder, db, create_app +from app.models import ContactGroup, Gender, Contact, Company log = logging.getLogger(__name__) diff --git a/flask_appbuilder/models/sqla/interface.py b/flask_appbuilder/models/sqla/interface.py index f52d5b1fe7..a08b2812f3 100644 --- a/flask_appbuilder/models/sqla/interface.py +++ b/flask_appbuilder/models/sqla/interface.py @@ -344,8 +344,8 @@ def get_inner_filters(self, filters: Optional[Filters]) -> Filters: if not is_column_dotted(flt.column_name): _filters.append((flt.column_name, flt.__class__, value)) elif self.is_relation_many_to_one( - flt.column_name - ) or self.is_relation_one_to_one(flt.column_name): + get_column_root_relation(flt.column_name) + ) or self.is_relation_one_to_one(get_column_root_relation(flt.column_name)): _filters.append((flt.column_name, flt.__class__, value)) inner_filters.add_filter_list(_filters) return inner_filters From b7d0413b1ac2a37f5d0c1af34f7f4f22932c5581 Mon Sep 17 00:00:00 2001 From: "pappenreiter.thomas" Date: Fri, 21 Apr 2023 10:10:05 +0200 Subject: [PATCH 3/7] reformatting interface.py --- flask_appbuilder/models/sqla/interface.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask_appbuilder/models/sqla/interface.py b/flask_appbuilder/models/sqla/interface.py index a08b2812f3..c98372d02c 100644 --- a/flask_appbuilder/models/sqla/interface.py +++ b/flask_appbuilder/models/sqla/interface.py @@ -345,7 +345,9 @@ def get_inner_filters(self, filters: Optional[Filters]) -> Filters: _filters.append((flt.column_name, flt.__class__, value)) elif self.is_relation_many_to_one( get_column_root_relation(flt.column_name) - ) or self.is_relation_one_to_one(get_column_root_relation(flt.column_name)): + ) or self.is_relation_one_to_one( + get_column_root_relation(flt.column_name) + ): _filters.append((flt.column_name, flt.__class__, value)) inner_filters.add_filter_list(_filters) return inner_filters From c069886dbc27e4138f7cd1605670a6134630dcfd Mon Sep 17 00:00:00 2001 From: ThomasP0815 Date: Mon, 24 Apr 2023 22:43:33 +0200 Subject: [PATCH 4/7] test added for checking filterequalfunction applied on relation field --- flask_appbuilder/tests/test_mvc.py | 33 +++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/flask_appbuilder/tests/test_mvc.py b/flask_appbuilder/tests/test_mvc.py index 3d8d4f7f7b..dd881ee8e2 100644 --- a/flask_appbuilder/tests/test_mvc.py +++ b/flask_appbuilder/tests/test_mvc.py @@ -19,7 +19,7 @@ from flask_appbuilder.models.generic import PSSession from flask_appbuilder.models.generic.interface import GenericInterface from flask_appbuilder.models.group import aggregate_avg, aggregate_count, aggregate_sum -from flask_appbuilder.models.sqla.filters import FilterEqual, FilterStartsWith +from flask_appbuilder.models.sqla.filters import FilterEqual, FilterStartsWith, FilterEqualFunction from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_appbuilder.views import CompactCRUDMixin, MasterDetailView, ModelView from flask_wtf import CSRFProtect @@ -566,6 +566,18 @@ class Model1Filtered2View(ModelView): datamodel = SQLAInterface(Model1) base_filters = [["field_integer", FilterEqual, 0]] + def get_model1_by_name(datamodel, name): + model = datamodel.session.query(Model1) \ + .filter_by(field_string=name) \ + .one_or_none() + return model + + class Model2FilterEqualFunctionView(ModelView): + datamodel = SQLAInterface(Model2) + base_filters = [["group", FilterEqualFunction, lambda: get_model1_by_name(Model2FilterEqualFunctionView.datamodel,"test1")]] + list_columns = ["group"] + search_columns = ["field_integer"] + class Model2ChartView(ChartView): datamodel = SQLAInterface(Model2) chart_title = "Test Model1 Chart" @@ -668,6 +680,9 @@ def enabled(self): self.appbuilder.add_view( Model1Filtered2View, "Model1Filtered2", category="Model1" ) + self.appbuilder.add_view( + Model2FilterEqualFunctionView, "Model2FilterEqualFunction", category="Model2" + ) self.appbuilder.add_view( Model1FormattedView, "Model1FormattedView", category="Model1FormattedView" ) @@ -1309,6 +1324,22 @@ def test_model_base_filter(self): self.assertIn("test0", data) self.assertNotIn("test1", data) + def test_filterequalfunction_with_relation(self): + """ + Test FilterEqualFunction + """ + client = self.app.test_client() + self.browser_login(client, USERNAME_ADMIN, PASSWORD_ADMIN) + + # Base filter string starts with + rv = client.get("/model2filterequalfunctionview/list/") + self.assertEqual(rv.status_code, 200) + data = rv.data.decode("utf-8") + self.assertIn("test1", data) + self.assertNotIn("test0", data) + self.assertNotIn("test2", data) + + def test_model_list_method_field(self): """ Tests a model's field has a method From 6820eb0525ba81c752b0f64c6e66fe055f757b8b Mon Sep 17 00:00:00 2001 From: ThomasP0815 Date: Mon, 24 Apr 2023 23:01:11 +0200 Subject: [PATCH 5/7] formatting test_mvc.py --- flask_appbuilder/tests/test_mvc.py | 35 +++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/flask_appbuilder/tests/test_mvc.py b/flask_appbuilder/tests/test_mvc.py index dd881ee8e2..3467cb95df 100644 --- a/flask_appbuilder/tests/test_mvc.py +++ b/flask_appbuilder/tests/test_mvc.py @@ -19,7 +19,11 @@ from flask_appbuilder.models.generic import PSSession from flask_appbuilder.models.generic.interface import GenericInterface from flask_appbuilder.models.group import aggregate_avg, aggregate_count, aggregate_sum -from flask_appbuilder.models.sqla.filters import FilterEqual, FilterStartsWith, FilterEqualFunction +from flask_appbuilder.models.sqla.filters import ( + FilterEqual, + FilterStartsWith, + FilterEqualFunction, +) from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_appbuilder.views import CompactCRUDMixin, MasterDetailView, ModelView from flask_wtf import CSRFProtect @@ -567,14 +571,24 @@ class Model1Filtered2View(ModelView): base_filters = [["field_integer", FilterEqual, 0]] def get_model1_by_name(datamodel, name): - model = datamodel.session.query(Model1) \ - .filter_by(field_string=name) \ - .one_or_none() + model = ( + datamodel.session.query(Model1) + .filter_by(field_string=name) + .one_or_none() + ) return model - + class Model2FilterEqualFunctionView(ModelView): datamodel = SQLAInterface(Model2) - base_filters = [["group", FilterEqualFunction, lambda: get_model1_by_name(Model2FilterEqualFunctionView.datamodel,"test1")]] + base_filters = [ + [ + "group", + FilterEqualFunction, + lambda: get_model1_by_name( + Model2FilterEqualFunctionView.datamodel, "test1" + ), + ] + ] list_columns = ["group"] search_columns = ["field_integer"] @@ -681,7 +695,9 @@ def enabled(self): Model1Filtered2View, "Model1Filtered2", category="Model1" ) self.appbuilder.add_view( - Model2FilterEqualFunctionView, "Model2FilterEqualFunction", category="Model2" + Model2FilterEqualFunctionView, + "Model2FilterEqualFunction", + category="Model2", ) self.appbuilder.add_view( Model1FormattedView, "Model1FormattedView", category="Model1FormattedView" @@ -1330,7 +1346,7 @@ def test_filterequalfunction_with_relation(self): """ client = self.app.test_client() self.browser_login(client, USERNAME_ADMIN, PASSWORD_ADMIN) - + # Base filter string starts with rv = client.get("/model2filterequalfunctionview/list/") self.assertEqual(rv.status_code, 200) @@ -1338,8 +1354,7 @@ def test_filterequalfunction_with_relation(self): self.assertIn("test1", data) self.assertNotIn("test0", data) self.assertNotIn("test2", data) - - + def test_model_list_method_field(self): """ Tests a model's field has a method From 621eb0d51811280b6abf1a4dc5c87f13399ce078 Mon Sep 17 00:00:00 2001 From: ThomasP0815 Date: Mon, 24 Apr 2023 23:07:18 +0200 Subject: [PATCH 6/7] correct import order (linter complains) --- flask_appbuilder/tests/test_mvc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask_appbuilder/tests/test_mvc.py b/flask_appbuilder/tests/test_mvc.py index 3467cb95df..a77991c81c 100644 --- a/flask_appbuilder/tests/test_mvc.py +++ b/flask_appbuilder/tests/test_mvc.py @@ -21,8 +21,8 @@ from flask_appbuilder.models.group import aggregate_avg, aggregate_count, aggregate_sum from flask_appbuilder.models.sqla.filters import ( FilterEqual, - FilterStartsWith, FilterEqualFunction, + FilterStartsWith, ) from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_appbuilder.views import CompactCRUDMixin, MasterDetailView, ModelView From c2e8e53a6e61c143bfedd8d457e4e6ee62d0437d Mon Sep 17 00:00:00 2001 From: ThomasP0815 Date: Mon, 24 Apr 2023 23:27:40 +0200 Subject: [PATCH 7/7] fix test view creation and registration --- flask_appbuilder/tests/test_mvc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask_appbuilder/tests/test_mvc.py b/flask_appbuilder/tests/test_mvc.py index a77991c81c..c3ffc7c8ed 100644 --- a/flask_appbuilder/tests/test_mvc.py +++ b/flask_appbuilder/tests/test_mvc.py @@ -744,7 +744,7 @@ def test_fab_views(self): """ Test views creation and registration """ - self.assertEqual(len(self.appbuilder.baseviews), 37) + self.assertEqual(len(self.appbuilder.baseviews), 38) def test_back(self): """