Skip to content

Commit 628bce5

Browse files
authored
Merge branch 'master' into fix/related-views-permission-check
2 parents bce2c77 + 73c9cea commit 628bce5

File tree

5 files changed

+96
-26
lines changed

5 files changed

+96
-26
lines changed

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Flask-AppBuilder ChangeLog
22
==========================
33

4+
Improvements and Bug fixes on 4.8.0
5+
-----------------------------------
6+
7+
- feat: Support the select_columns arg on the Get Item method (#2372) [Vitor Avila]
8+
49
Improvements and Bug fixes on 4.7.0
510
-----------------------------------
611

flask_appbuilder/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = "Daniel Vaz Gaspar"
2-
__version__ = "4.7.0"
2+
__version__ = "4.8.0"
33

44
from .actions import action # noqa: F401
55
from .api import ModelRestApi # noqa: F401

flask_appbuilder/api/__init__.py

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@
7373
API_URI_RIS_KEY,
7474
PERMISSION_PREFIX,
7575
)
76-
from ..exceptions import FABException, InvalidOrderByColumnFABException
76+
from ..exceptions import (
77+
FABException,
78+
InvalidColumnArgsFABException,
79+
InvalidOrderByColumnFABException,
80+
)
7781
from ..hooks import get_before_request_hooks, wrap_route_handler_with_hooks
7882
from ..models.filters import Filters
7983
from ..security.decorators import permission_name, protect
@@ -1467,19 +1471,27 @@ def get_headless(self, pk: ModelKeyType, **kwargs: Any) -> Response:
14671471
:param kwargs: Query string parameter arguments
14681472
:return: HTTP Response
14691473
"""
1474+
response = {}
1475+
args = kwargs.get("rison", {})
1476+
# handle select columns
1477+
try:
1478+
select_columns, pruned_select_cols = self._handle_columns_args(
1479+
args,
1480+
self.show_select_columns,
1481+
self.show_columns,
1482+
)
1483+
except InvalidColumnArgsFABException as e:
1484+
return self.response_400(message=str(e))
1485+
14701486
item = self.datamodel.get(
14711487
pk,
14721488
self._base_filters,
1473-
self.show_select_columns,
1489+
select_columns,
14741490
self.show_outer_default_load,
14751491
)
14761492
if not item:
14771493
return self.response_404()
14781494

1479-
response = {}
1480-
args = kwargs.get("rison", {})
1481-
select_cols = args.get(API_SELECT_COLUMNS_RIS_KEY, [])
1482-
pruned_select_cols = [col for col in select_cols if col in self.show_columns]
14831495
self.set_response_key_mappings(
14841496
response, self.get, args, **{API_SELECT_COLUMNS_RIS_KEY: pruned_select_cols}
14851497
)
@@ -1582,23 +1594,15 @@ def get_list_headless(self, **kwargs: Any) -> Response:
15821594
response = dict()
15831595
args = kwargs.get("rison", {})
15841596
# handle select columns
1585-
output_select_cols = args.get(API_SELECT_COLUMNS_RIS_KEY, [])
1586-
select_cols = args.get(API_SELECT_SEL_COLUMNS_RIS_KEY, [])
1587-
if select_cols and output_select_cols:
1588-
return self.response_400(message="Cannot use both select and sel columns")
1589-
list_select_columns = self.list_select_columns
1590-
pruned_select_cols = []
1591-
if output_select_cols:
1592-
pruned_select_cols = [
1593-
col for col in output_select_cols if col in self.list_columns
1594-
]
1595-
if select_cols:
1596-
pruned_select_cols = [
1597-
col for col in select_cols if col in self.list_columns
1598-
]
1599-
list_select_columns = [
1600-
col for col in select_cols if col in self.list_select_columns
1601-
]
1597+
try:
1598+
select_columns, pruned_select_cols = self._handle_columns_args(
1599+
args,
1600+
self.list_select_columns,
1601+
self.list_columns,
1602+
)
1603+
except InvalidColumnArgsFABException as e:
1604+
return self.response_400(message=str(e))
1605+
16021606
# map decorated metadata
16031607
self.set_response_key_mappings(
16041608
response,
@@ -1631,7 +1635,7 @@ def get_list_headless(self, **kwargs: Any) -> Response:
16311635
order_direction,
16321636
page=page_index,
16331637
page_size=page_size,
1634-
select_columns=list_select_columns,
1638+
select_columns=select_columns,
16351639
outer_default_load=self.list_outer_default_load,
16361640
)
16371641
pks = self.datamodel.get_keys(lst)
@@ -1977,6 +1981,37 @@ def _handle_filters_args(self, rison_args: Dict[str, Any]) -> Filters:
19771981
self._filters.rest_add_filters(rison_args.get(API_FILTERS_RIS_KEY, []))
19781982
return self._filters.get_joined_filters(self._base_filters)
19791983

1984+
def _handle_columns_args(
1985+
self,
1986+
args: Dict[str, Any],
1987+
default_select_columns: List[str],
1988+
default_response_columns: List[str],
1989+
) -> Tuple[List[str], List[str]]:
1990+
"""
1991+
Handle the column args from the request.
1992+
"""
1993+
select_columns_arg = args.get(API_SELECT_SEL_COLUMNS_RIS_KEY, [])
1994+
response_columns_arg = args.get(API_SELECT_COLUMNS_RIS_KEY, [])
1995+
if select_columns_arg and response_columns_arg:
1996+
raise InvalidColumnArgsFABException(
1997+
"Cannot use both select and sel columns"
1998+
)
1999+
select_columns = default_select_columns
2000+
response_columns = []
2001+
if select_columns_arg:
2002+
select_columns = [
2003+
col for col in select_columns_arg if col in default_select_columns
2004+
]
2005+
response_columns = [
2006+
col for col in select_columns_arg if col in default_response_columns
2007+
]
2008+
elif response_columns_arg:
2009+
response_columns = [
2010+
col for col in response_columns_arg if col in default_response_columns
2011+
]
2012+
2013+
return select_columns, response_columns
2014+
19802015
def _description_columns_json(
19812016
self, cols: Optional[List[str]] = None
19822017
) -> Dict[str, Any]:

flask_appbuilder/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ class InvalidOrderByColumnFABException(FABException):
3434
...
3535

3636

37+
class InvalidColumnArgsFABException(FABException):
38+
"""Invalid combination of column arguments"""
39+
40+
...
41+
42+
3743
class InterfaceQueryWithoutSession(FABException):
3844
"""You need to setup a session on the interface to perform queries"""
3945

tests/test_api.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,7 @@ def assert_get_item(self, rv, data, value):
879879

880880
def test_get_item_choose_cols(self):
881881
"""
882-
REST Api: Test get item with select columns
882+
REST Api: Test get item with the columns arg
883883
"""
884884
client = self.app.test_client()
885885
token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
@@ -901,6 +901,30 @@ def test_get_item_choose_cols(self):
901901
)
902902
self.assertEqual(rv.status_code, 200)
903903

904+
def test_get_item_choose_select_cols(self):
905+
"""
906+
REST Api: Test get item with the select_columns arg
907+
"""
908+
client = self.app.test_client()
909+
token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
910+
with model1_data(self.appbuilder.session, 1) as models:
911+
model_id = models[0].id
912+
uri = (
913+
f"api/v1/model1api/{model_id}?"
914+
f"q=({API_SELECT_SEL_COLUMNS_RIS_KEY}:!(field_integer))"
915+
)
916+
rv = self.auth_client_get(client, token, uri)
917+
data = json.loads(rv.data.decode("utf-8"))
918+
self.assertEqual(data[API_RESULT_RES_KEY], {"field_integer": 0})
919+
self.assertEqual(
920+
data[API_DESCRIPTION_COLUMNS_RES_KEY],
921+
{"field_integer": "Field Integer"},
922+
)
923+
self.assertEqual(
924+
data[API_LABEL_COLUMNS_RES_KEY], {"field_integer": "Field Integer"}
925+
)
926+
self.assertEqual(rv.status_code, 200)
927+
904928
def test_get_item_dotted_mo_notation(self):
905929
"""
906930
REST Api: Test get item with dotted M-O related field

0 commit comments

Comments
 (0)