flask-appbuilder 3.2.1__py3-none-any.whl → 5.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. flask_appbuilder/__init__.py +2 -3
  2. flask_appbuilder/_compat.py +0 -1
  3. flask_appbuilder/actions.py +14 -14
  4. flask_appbuilder/api/__init__.py +741 -527
  5. flask_appbuilder/api/convert.py +104 -98
  6. flask_appbuilder/api/manager.py +14 -8
  7. flask_appbuilder/api/schemas.py +12 -1
  8. flask_appbuilder/babel/manager.py +12 -16
  9. flask_appbuilder/base.py +353 -280
  10. flask_appbuilder/basemanager.py +1 -1
  11. flask_appbuilder/baseviews.py +241 -164
  12. flask_appbuilder/charts/jsontools.py +10 -10
  13. flask_appbuilder/charts/views.py +56 -60
  14. flask_appbuilder/cli.py +115 -70
  15. flask_appbuilder/const.py +52 -52
  16. flask_appbuilder/exceptions.py +67 -5
  17. flask_appbuilder/fields.py +32 -23
  18. flask_appbuilder/fieldwidgets.py +34 -27
  19. flask_appbuilder/filemanager.py +33 -45
  20. flask_appbuilder/filters.py +11 -13
  21. flask_appbuilder/forms.py +31 -35
  22. flask_appbuilder/hooks.py +90 -0
  23. flask_appbuilder/menu.py +35 -10
  24. flask_appbuilder/models/base.py +47 -57
  25. flask_appbuilder/models/decorators.py +13 -13
  26. flask_appbuilder/models/filters.py +42 -38
  27. flask_appbuilder/models/generic/__init__.py +29 -29
  28. flask_appbuilder/models/generic/filters.py +11 -3
  29. flask_appbuilder/models/generic/interface.py +1 -3
  30. flask_appbuilder/models/group.py +37 -39
  31. flask_appbuilder/models/mixins.py +22 -18
  32. flask_appbuilder/models/sqla/__init__.py +19 -72
  33. flask_appbuilder/models/sqla/base.py +24 -0
  34. flask_appbuilder/models/sqla/base_legacy.py +132 -0
  35. flask_appbuilder/models/sqla/filters.py +132 -19
  36. flask_appbuilder/models/sqla/interface.py +390 -276
  37. flask_appbuilder/security/api.py +31 -35
  38. flask_appbuilder/security/decorators.py +181 -83
  39. flask_appbuilder/security/forms.py +20 -31
  40. flask_appbuilder/security/manager.py +715 -489
  41. flask_appbuilder/security/registerviews.py +29 -112
  42. flask_appbuilder/security/schemas.py +43 -0
  43. flask_appbuilder/security/sqla/apis/__init__.py +8 -0
  44. flask_appbuilder/security/sqla/apis/group/__init__.py +1 -0
  45. flask_appbuilder/security/sqla/apis/group/api.py +227 -0
  46. flask_appbuilder/security/sqla/apis/group/schema.py +73 -0
  47. flask_appbuilder/security/sqla/apis/permission/__init__.py +1 -0
  48. flask_appbuilder/security/sqla/apis/permission/api.py +19 -0
  49. flask_appbuilder/security/sqla/apis/permission_view_menu/__init__.py +1 -0
  50. flask_appbuilder/security/sqla/apis/permission_view_menu/api.py +16 -0
  51. flask_appbuilder/security/sqla/apis/role/__init__.py +1 -0
  52. flask_appbuilder/security/sqla/apis/role/api.py +306 -0
  53. flask_appbuilder/security/sqla/apis/role/schema.py +27 -0
  54. flask_appbuilder/security/sqla/apis/user/__init__.py +1 -0
  55. flask_appbuilder/security/sqla/apis/user/api.py +292 -0
  56. flask_appbuilder/security/sqla/apis/user/schema.py +97 -0
  57. flask_appbuilder/security/sqla/apis/user/validator.py +27 -0
  58. flask_appbuilder/security/sqla/apis/view_menu/__init__.py +1 -0
  59. flask_appbuilder/security/sqla/apis/view_menu/api.py +18 -0
  60. flask_appbuilder/security/sqla/manager.py +421 -203
  61. flask_appbuilder/security/sqla/models.py +192 -57
  62. flask_appbuilder/security/utils.py +9 -0
  63. flask_appbuilder/security/views.py +232 -229
  64. flask_appbuilder/static/.DS_Store +0 -0
  65. flask_appbuilder/static/appbuilder/css/ab.css +20 -12
  66. flask_appbuilder/static/appbuilder/css/bootstrap-datepicker/bootstrap-datepicker3.min.css +7 -0
  67. flask_appbuilder/static/appbuilder/css/bootstrap.min.css.map +1 -0
  68. flask_appbuilder/static/appbuilder/css/flags/flags16.css +249 -245
  69. flask_appbuilder/static/appbuilder/css/fontawesome/all.min.css +6 -0
  70. flask_appbuilder/static/appbuilder/css/fontawesome/brands.min.css +6 -0
  71. flask_appbuilder/static/appbuilder/css/fontawesome/fontawesome.min.css +6 -0
  72. flask_appbuilder/static/appbuilder/css/fontawesome/regular.min.css +6 -0
  73. flask_appbuilder/static/appbuilder/css/fontawesome/solid.min.css +6 -0
  74. flask_appbuilder/static/appbuilder/css/fontawesome/svg-with-js.min.css +6 -0
  75. flask_appbuilder/static/appbuilder/css/fontawesome/v4-font-face.min.css +6 -0
  76. flask_appbuilder/static/appbuilder/css/fontawesome/v4-shims.min.css +6 -0
  77. flask_appbuilder/static/appbuilder/css/fontawesome/v5-font-face.min.css +6 -0
  78. flask_appbuilder/static/appbuilder/css/images/flags16.png +0 -0
  79. flask_appbuilder/static/appbuilder/css/select2/select2-bootstrap.min.css +7 -0
  80. flask_appbuilder/static/appbuilder/css/select2/select2.min.css +1 -0
  81. flask_appbuilder/static/appbuilder/css/swagger/swagger-ui.css +3 -0
  82. flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.ttf +0 -0
  83. flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.woff2 +0 -0
  84. flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.ttf +0 -0
  85. flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.woff2 +0 -0
  86. flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.ttf +0 -0
  87. flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.woff2 +0 -0
  88. flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.ttf +0 -0
  89. flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.woff2 +0 -0
  90. flask_appbuilder/static/appbuilder/js/ab.js +33 -23
  91. flask_appbuilder/static/appbuilder/js/ab_filters.js +91 -84
  92. flask_appbuilder/static/appbuilder/js/bootstrap-datepicker/bootstrap-datepicker.min.js +8 -0
  93. flask_appbuilder/static/appbuilder/js/jquery-latest.js +2 -2
  94. flask_appbuilder/static/appbuilder/js/select2/select2.min.js +2 -0
  95. flask_appbuilder/static/appbuilder/js/swagger-ui-bundle.js +3 -0
  96. flask_appbuilder/templates/appbuilder/baselib.html +9 -3
  97. flask_appbuilder/templates/appbuilder/general/lib.html +60 -34
  98. flask_appbuilder/templates/appbuilder/general/model/edit.html +1 -1
  99. flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html +1 -1
  100. flask_appbuilder/templates/appbuilder/general/model/search.html +3 -2
  101. flask_appbuilder/templates/appbuilder/general/model/show.html +1 -1
  102. flask_appbuilder/templates/appbuilder/general/model/show_cascade.html +1 -1
  103. flask_appbuilder/templates/appbuilder/general/security/login_db.html +7 -7
  104. flask_appbuilder/templates/appbuilder/general/security/login_ldap.html +5 -5
  105. flask_appbuilder/templates/appbuilder/general/security/login_oauth.html +24 -49
  106. flask_appbuilder/templates/appbuilder/general/widgets/base_list.html +2 -1
  107. flask_appbuilder/templates/appbuilder/general/widgets/chart.html +4 -2
  108. flask_appbuilder/templates/appbuilder/general/widgets/direct_chart.html +4 -3
  109. flask_appbuilder/templates/appbuilder/general/widgets/multiple_chart.html +3 -2
  110. flask_appbuilder/templates/appbuilder/general/widgets/search.html +11 -10
  111. flask_appbuilder/templates/appbuilder/init.html +37 -43
  112. flask_appbuilder/templates/appbuilder/navbar_menu.html +1 -1
  113. flask_appbuilder/templates/appbuilder/navbar_right.html +2 -2
  114. flask_appbuilder/templates/appbuilder/swagger/swagger.html +22 -19
  115. flask_appbuilder/translations/de/LC_MESSAGES/messages.mo +0 -0
  116. flask_appbuilder/translations/de/LC_MESSAGES/messages.po +305 -161
  117. flask_appbuilder/translations/fa/LC_MESSAGES/messages.mo +0 -0
  118. flask_appbuilder/translations/fa/LC_MESSAGES/messages.po +802 -0
  119. flask_appbuilder/translations/fr/LC_MESSAGES/messages.po +461 -319
  120. flask_appbuilder/translations/pt_BR/LC_MESSAGES/messages.po +650 -650
  121. flask_appbuilder/translations/ru/LC_MESSAGES/messages.po +1 -1
  122. flask_appbuilder/translations/sl/LC_MESSAGES/messages.mo +0 -0
  123. flask_appbuilder/translations/sl/LC_MESSAGES/messages.po +690 -0
  124. flask_appbuilder/translations/tr/LC_MESSAGES/messages.mo +0 -0
  125. flask_appbuilder/translations/tr/LC_MESSAGES/messages.po +1015 -0
  126. flask_appbuilder/upload.py +20 -22
  127. flask_appbuilder/urltools.py +39 -19
  128. flask_appbuilder/utils/base.py +76 -0
  129. flask_appbuilder/utils/legacy.py +33 -0
  130. flask_appbuilder/utils/limit.py +20 -0
  131. flask_appbuilder/validators.py +73 -14
  132. flask_appbuilder/views.py +75 -424
  133. flask_appbuilder/widgets.py +50 -51
  134. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/METADATA +36 -76
  135. flask_appbuilder-5.0.2.dist-info/RECORD +240 -0
  136. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/WHEEL +1 -1
  137. flask_appbuilder-5.0.2.dist-info/entry_points.txt +2 -0
  138. Flask_AppBuilder-3.2.1.dist-info/RECORD +0 -270
  139. Flask_AppBuilder-3.2.1.dist-info/entry_points.txt +0 -6
  140. flask_appbuilder/console.py +0 -426
  141. flask_appbuilder/models/mongoengine/__init__.py +0 -0
  142. flask_appbuilder/models/mongoengine/fields.py +0 -65
  143. flask_appbuilder/models/mongoengine/filters.py +0 -145
  144. flask_appbuilder/models/mongoengine/interface.py +0 -328
  145. flask_appbuilder/security/mongoengine/__init__.py +0 -0
  146. flask_appbuilder/security/mongoengine/manager.py +0 -402
  147. flask_appbuilder/security/mongoengine/models.py +0 -120
  148. flask_appbuilder/static/appbuilder/css/font-awesome.min.css +0 -4
  149. flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.css +0 -9
  150. flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.js +0 -28
  151. flask_appbuilder/static/appbuilder/fonts/FontAwesome.otf +0 -0
  152. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.eot +0 -0
  153. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.svg +0 -2671
  154. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.ttf +0 -0
  155. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff +0 -0
  156. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff2 +0 -0
  157. flask_appbuilder/static/appbuilder/img/aol.png +0 -0
  158. flask_appbuilder/static/appbuilder/img/flags/flags16.png +0 -0
  159. flask_appbuilder/static/appbuilder/img/flickr.png +0 -0
  160. flask_appbuilder/static/appbuilder/img/google.png +0 -0
  161. flask_appbuilder/static/appbuilder/img/myopenid.png +0 -0
  162. flask_appbuilder/static/appbuilder/img/yahoo.png +0 -0
  163. flask_appbuilder/static/appbuilder/js/_google_charts.js +0 -39
  164. flask_appbuilder/static/appbuilder/js/html5shiv.js +0 -8
  165. flask_appbuilder/static/appbuilder/js/respond.min.js +0 -6
  166. flask_appbuilder/static/appbuilder/select2/select2-spinner.gif +0 -0
  167. flask_appbuilder/static/appbuilder/select2/select2.css +0 -1205
  168. flask_appbuilder/static/appbuilder/select2/select2.js +0 -23
  169. flask_appbuilder/static/appbuilder/select2/select2.png +0 -0
  170. flask_appbuilder/static/appbuilder/select2/select2x2.png +0 -0
  171. flask_appbuilder/templates/appbuilder/general/security/login_oid.html +0 -129
  172. flask_appbuilder/templates/appbuilder/general/security/resetpassword.html +0 -29
  173. flask_appbuilder/tests/__init__.py +0 -0
  174. flask_appbuilder/tests/__pycache__/__init__.cpython-36.pyc +0 -0
  175. flask_appbuilder/tests/__pycache__/__init__.cpython-37.pyc +0 -0
  176. flask_appbuilder/tests/__pycache__/_test_auth_ldap.cpython-37.pyc +0 -0
  177. flask_appbuilder/tests/__pycache__/_test_auth_oauth.cpython-37.pyc +0 -0
  178. flask_appbuilder/tests/__pycache__/_test_ldapsearch.cpython-36.pyc +0 -0
  179. flask_appbuilder/tests/__pycache__/_test_oauth_registration_role.cpython-36.pyc +0 -0
  180. flask_appbuilder/tests/__pycache__/base.cpython-36.pyc +0 -0
  181. flask_appbuilder/tests/__pycache__/base.cpython-37.pyc +0 -0
  182. flask_appbuilder/tests/__pycache__/config_api.cpython-36.pyc +0 -0
  183. flask_appbuilder/tests/__pycache__/config_api.cpython-37.pyc +0 -0
  184. flask_appbuilder/tests/__pycache__/const.cpython-36.pyc +0 -0
  185. flask_appbuilder/tests/__pycache__/const.cpython-37.pyc +0 -0
  186. flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-36.pyc +0 -0
  187. flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-37.pyc +0 -0
  188. flask_appbuilder/tests/__pycache__/test_api.cpython-36.pyc +0 -0
  189. flask_appbuilder/tests/__pycache__/test_api.cpython-37.pyc +0 -0
  190. flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-36.pyc +0 -0
  191. flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-37.pyc +0 -0
  192. flask_appbuilder/tests/__pycache__/test_menu.cpython-36.pyc +0 -0
  193. flask_appbuilder/tests/__pycache__/test_menu.cpython-37.pyc +0 -0
  194. flask_appbuilder/tests/__pycache__/test_mongoengine.cpython-36.pyc +0 -0
  195. flask_appbuilder/tests/__pycache__/test_mvc.cpython-36.pyc +0 -0
  196. flask_appbuilder/tests/__pycache__/test_mvc.cpython-37.pyc +0 -0
  197. flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-36.pyc +0 -0
  198. flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-37.pyc +0 -0
  199. flask_appbuilder/tests/_test_auth_ldap.py +0 -1045
  200. flask_appbuilder/tests/_test_auth_oauth.py +0 -419
  201. flask_appbuilder/tests/_test_ldapsearch.py +0 -135
  202. flask_appbuilder/tests/_test_oauth_registration_role.py +0 -59
  203. flask_appbuilder/tests/app.db +0 -0
  204. flask_appbuilder/tests/base.py +0 -90
  205. flask_appbuilder/tests/config_api.py +0 -21
  206. flask_appbuilder/tests/const.py +0 -9
  207. flask_appbuilder/tests/mongoengine/__init__.py +0 -0
  208. flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-36.pyc +0 -0
  209. flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-37.pyc +0 -0
  210. flask_appbuilder/tests/mongoengine/__pycache__/models.cpython-36.pyc +0 -0
  211. flask_appbuilder/tests/mongoengine/models.py +0 -41
  212. flask_appbuilder/tests/sqla/__init__.py +0 -0
  213. flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-36.pyc +0 -0
  214. flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-37.pyc +0 -0
  215. flask_appbuilder/tests/sqla/__pycache__/models.cpython-36.pyc +0 -0
  216. flask_appbuilder/tests/sqla/__pycache__/models.cpython-37.pyc +0 -0
  217. flask_appbuilder/tests/sqla/models.py +0 -340
  218. flask_appbuilder/tests/test_0_fixture.py +0 -39
  219. flask_appbuilder/tests/test_api.py +0 -2790
  220. flask_appbuilder/tests/test_fab_cli.py +0 -72
  221. flask_appbuilder/tests/test_menu.py +0 -122
  222. flask_appbuilder/tests/test_mongoengine.py +0 -572
  223. flask_appbuilder/tests/test_mvc.py +0 -1710
  224. flask_appbuilder/tests/test_sqlalchemy.py +0 -24
  225. flask_appbuilder/translations/__pycache__/__init__.cpython-36.pyc +0 -0
  226. flask_appbuilder/translations/es/LC_MESSAGES/messages.po~ +0 -582
  227. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/LICENSE +0 -0
  228. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,21 @@
1
- from typing import List, Optional, Type
1
+ from typing import Any, Callable, Dict, List, Optional, Type
2
2
 
3
+ from flask import current_app
3
4
  from flask_appbuilder.models.sqla import Model
4
5
  from flask_appbuilder.models.sqla.interface import SQLAInterface
5
- from marshmallow import fields
6
+ from marshmallow import fields, Schema
6
7
  from marshmallow.fields import Field
7
- from marshmallow_enum import EnumField
8
8
  from marshmallow_sqlalchemy import field_for
9
9
  from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
10
10
 
11
11
 
12
12
  class TreeNode:
13
- def __init__(self, data):
14
- self.data = data
15
- self.childs = list()
13
+ def __init__(self, name: str) -> None:
14
+ self.name = name
15
+ self.children: List["TreeNode"] = []
16
16
 
17
- def __repr__(self):
18
- return f"{self.data}.{str(self.childs)}"
17
+ def __repr__(self) -> str:
18
+ return f"{self.name}.{str(self.children)}"
19
19
 
20
20
 
21
21
  class Tree:
@@ -23,26 +23,26 @@ class Tree:
23
23
  Simplistic one level Tree
24
24
  """
25
25
 
26
- def __init__(self):
26
+ def __init__(self) -> None:
27
27
  self.root = TreeNode("+")
28
28
 
29
- def add(self, data):
30
- node = TreeNode(data)
31
- self.root.childs.append(node)
29
+ def add(self, name: str) -> None:
30
+ node = TreeNode(name)
31
+ self.root.children.append(node)
32
32
 
33
- def add_child(self, parent, data):
34
- node = TreeNode(data)
35
- for n in self.root.childs:
36
- if n.data == parent:
37
- n.childs.append(node)
33
+ def add_child(self, parent: str, name: str) -> None:
34
+ node = TreeNode(name)
35
+ for child in self.root.children:
36
+ if child.name == parent:
37
+ child.children.append(node)
38
38
  return
39
39
  root = TreeNode(parent)
40
- self.root.childs.append(root)
41
- root.childs.append(node)
40
+ self.root.children.append(root)
41
+ root.children.append(node)
42
42
 
43
- def __repr__(self):
43
+ def __repr__(self) -> str:
44
44
  ret = ""
45
- for node in self.root.childs:
45
+ for node in self.root.children:
46
46
  ret += str(node)
47
47
  return ret
48
48
 
@@ -51,43 +51,62 @@ def columns2Tree(columns: List[str]) -> Tree:
51
51
  tree = Tree()
52
52
  for column in columns:
53
53
  if "." in column:
54
- tree.add_child(column.split(".")[0], column.split(".")[1])
54
+ parent, child = column.split(".")
55
+ tree.add_child(parent, child)
55
56
  else:
56
57
  tree.add(column)
57
58
  return tree
58
59
 
59
60
 
60
61
  class BaseModel2SchemaConverter(object):
61
- def __init__(self, datamodel: SQLAInterface, validators_columns):
62
+ def __init__(
63
+ self,
64
+ datamodel: SQLAInterface,
65
+ validators_columns: Dict[str, Callable[[Any], Any]],
66
+ ):
62
67
  """
63
68
  :param datamodel: SQLAInterface
64
69
  """
65
70
  self.datamodel = datamodel
66
71
  self.validators_columns = validators_columns
67
72
 
68
- def convert(self, columns, **kwargs):
73
+ def convert(
74
+ self,
75
+ columns: List[str],
76
+ model: Optional[Type[Model]] = None,
77
+ nested: bool = True,
78
+ parent_schema_name: Optional[str] = None,
79
+ ) -> SQLAlchemyAutoSchema:
69
80
  pass
70
81
 
71
82
 
72
83
  class Model2SchemaConverter(BaseModel2SchemaConverter):
73
84
  """
74
- Class that converts Models to marshmallow Schemas
85
+ Class that converts Models to marshmallow Schemas
75
86
  """
76
87
 
77
- def __init__(self, datamodel: SQLAInterface, validators_columns):
88
+ def __init__(
89
+ self,
90
+ datamodel: SQLAInterface,
91
+ validators_columns: Dict[str, Callable[[Any], Any]],
92
+ ):
78
93
  """
79
94
  :param datamodel: SQLAInterface
80
95
  """
81
96
  super(Model2SchemaConverter, self).__init__(datamodel, validators_columns)
82
97
 
83
98
  @staticmethod
84
- def _debug_schema(schema):
99
+ def _debug_schema(schema: SQLAlchemyAutoSchema) -> None:
85
100
  for k, v in schema._declared_fields.items():
86
101
  print(k, v)
87
102
 
88
103
  def _meta_schema_factory(
89
- self, columns: List[str], model: Model, class_mixin, parent_schema_name=None
90
- ):
104
+ self,
105
+ columns: List[str],
106
+ model: Optional[Type[Model]],
107
+ class_mixin: Type[Schema],
108
+ parent_schema_name: Optional[str] = None,
109
+ ) -> Type[SQLAlchemyAutoSchema]:
91
110
  """
92
111
  Creates ModelSchema marshmallow-sqlalchemy
93
112
 
@@ -100,45 +119,44 @@ class Model2SchemaConverter(BaseModel2SchemaConverter):
100
119
  _parent_schema_name = parent_schema_name
101
120
  if columns:
102
121
 
103
- class MetaSchema(SQLAlchemyAutoSchema, class_mixin):
122
+ class MetaSchema(SQLAlchemyAutoSchema, class_mixin): # type: ignore
104
123
  class Meta:
105
124
  model = _model
106
125
  fields = columns
107
126
  load_instance = True
108
- sqla_session = self.datamodel.session
127
+ sqla_session = current_app.appbuilder.session
109
128
  # The parent_schema_name is useful to humanize nested schema names
110
129
  # This name comes from ModelRestApi
111
130
  parent_schema_name = _parent_schema_name
112
131
 
113
- else:
132
+ return MetaSchema
114
133
 
115
- class MetaSchema(SQLAlchemyAutoSchema, class_mixin):
116
- class Meta:
117
- model = _model
118
- load_instance = True
119
- sqla_session = self.datamodel.session
120
- # The parent_schema_name is useful to humanize nested schema names
121
- # This name comes from ModelRestApi
122
- parent_schema_name = _parent_schema_name
134
+ class MetaSchema(SQLAlchemyAutoSchema, class_mixin): # type: ignore
135
+ class Meta:
136
+ model = _model
137
+ load_instance = True
138
+ sqla_session = current_app.appbuilder.session
139
+ # The parent_schema_name is useful to humanize nested schema names
140
+ # This name comes from ModelRestApi
141
+ parent_schema_name = _parent_schema_name
123
142
 
124
143
  return MetaSchema
125
144
 
126
- def _column2enum(
127
- self,
128
- datamodel: SQLAInterface,
129
- column: TreeNode,
130
- enum_dump_by_name: bool = False,
131
- ):
132
- required = not datamodel.is_nullable(column.data)
133
- enum_class = datamodel.list_columns[column.data].info.get(
134
- "enum_class", datamodel.list_columns[column.data].type
135
- )
136
- if enum_dump_by_name:
137
- enum_dump_by = EnumField.NAME
145
+ def _column2enum(self, datamodel: SQLAInterface, column: TreeNode) -> Field:
146
+ required = not datamodel.is_nullable(column.name)
147
+ sqla_column = datamodel.list_columns[column.name]
148
+ # get SQLAlchemy column user info, we use it to get the marshmallow enum options
149
+ column_info = sqla_column.info
150
+ # TODO: Default should be False, but keeping this to True to keep compatibility
151
+ # Turn this to False in the next major release
152
+ by_value = column_info.get("marshmallow_by_value", True)
153
+ # Get the original enum class from SQLAlchemy Enum field
154
+ enum_class = sqla_column.type.enum_class
155
+ if not enum_class:
156
+ field = field_for(datamodel.obj, column.name)
138
157
  else:
139
- enum_dump_by = EnumField.VALUE
140
- field = EnumField(enum_class, dump_by=enum_dump_by, required=required)
141
- field.unique = datamodel.is_unique(column.data)
158
+ field = fields.Enum(enum_class, required=required, by_value=by_value)
159
+ field.unique = datamodel.is_unique(column.name)
142
160
  return field
143
161
 
144
162
  def _column2relation(
@@ -147,40 +165,37 @@ class Model2SchemaConverter(BaseModel2SchemaConverter):
147
165
  column: TreeNode,
148
166
  nested: bool = False,
149
167
  parent_schema_name: Optional[str] = None,
150
- ):
168
+ ) -> Field:
151
169
  if nested:
152
- required = not datamodel.is_nullable(column.data)
153
- nested_model = datamodel.get_related_model(column.data)
154
- lst = [item.data for item in column.childs]
170
+ required = not datamodel.is_nullable(column.name)
171
+ nested_model = datamodel.get_related_model(column.name)
172
+ lst = [item.name for item in column.children]
155
173
  nested_schema = self.convert(
156
174
  lst, nested_model, nested=False, parent_schema_name=parent_schema_name
157
175
  )
158
- if datamodel.is_relation_many_to_one(column.data):
176
+ if datamodel.is_relation_many_to_one(column.name):
159
177
  many = False
160
- elif datamodel.is_relation_many_to_many(column.data):
178
+ elif datamodel.is_relation_many_to_many(column.name):
161
179
  many = True
162
180
  required = False
163
- elif datamodel.is_relation_one_to_many(column.data):
181
+ elif datamodel.is_relation_one_to_many(column.name):
164
182
  many = True
165
183
  else:
166
184
  many = False
167
185
  field = fields.Nested(nested_schema, many=many, required=required)
168
- field.unique = datamodel.is_unique(column.data)
186
+ field.unique = datamodel.is_unique(column.name)
169
187
  return field
170
188
  # Handle bug on marshmallow-sqlalchemy
171
189
  # https://github.com/marshmallow-code/marshmallow-sqlalchemy/issues/163
172
190
  if datamodel.is_relation_many_to_many(
173
- column.data
174
- ) or datamodel.is_relation_one_to_many(column.data):
175
- if datamodel.get_info(column.data).get("required", False):
176
- required = True
177
- else:
178
- required = False
191
+ column.name
192
+ ) or datamodel.is_relation_one_to_many(column.name):
193
+ required = datamodel.get_info(column.name).get("required", False)
179
194
  else:
180
- required = not datamodel.is_nullable(column.data)
181
- field = field_for(datamodel.obj, column.data)
195
+ required = not datamodel.is_nullable(column.name)
196
+ field = field_for(datamodel.obj, column.name)
182
197
  field.required = required
183
- field.unique = datamodel.is_unique(column.data)
198
+ field.unique = datamodel.is_unique(column.name)
184
199
  return field
185
200
 
186
201
  def _column2field(
@@ -188,7 +203,6 @@ class Model2SchemaConverter(BaseModel2SchemaConverter):
188
203
  datamodel: SQLAInterface,
189
204
  column: TreeNode,
190
205
  nested: bool = True,
191
- enum_dump_by_name: bool = False,
192
206
  parent_schema_name: Optional[str] = None,
193
207
  ) -> Field:
194
208
  """
@@ -196,34 +210,31 @@ class Model2SchemaConverter(BaseModel2SchemaConverter):
196
210
  :param datamodel: SQLAInterface
197
211
  :param column: TreeNode column (childs are dotted columns)
198
212
  :param nested: Boolean if will create nested fields
199
- :param enum_dump_by_name:
200
213
  :return: Schema.field
201
214
  """
202
215
  # Handle relations
203
- if datamodel.is_relation(column.data):
216
+ if datamodel.is_relation(column.name):
204
217
  return self._column2relation(
205
218
  datamodel, column, nested=nested, parent_schema_name=parent_schema_name
206
219
  )
207
220
  # Handle Enums
208
- elif datamodel.is_enum(column.data):
209
- return self._column2enum(
210
- datamodel, column, enum_dump_by_name=enum_dump_by_name
211
- )
221
+ if datamodel.is_enum(column.name):
222
+ return self._column2enum(datamodel, column)
212
223
  # is custom property method field?
213
- if hasattr(getattr(datamodel.obj, column.data), "fget"):
224
+ if hasattr(getattr(datamodel.obj, column.name), "fget"):
214
225
  return fields.Raw(dump_only=True)
215
226
  # its a model function
216
- if hasattr(getattr(datamodel.obj, column.data), "__call__"):
217
- return fields.Function(getattr(datamodel.obj, column.data), dump_only=True)
227
+ if hasattr(getattr(datamodel.obj, column.name), "__call__"):
228
+ return fields.Function(getattr(datamodel.obj, column.name), dump_only=True)
218
229
  # is a normal model field not a function?
219
- if not hasattr(getattr(datamodel.obj, column.data), "__call__"):
220
- field = field_for(datamodel.obj, column.data)
221
- field.unique = datamodel.is_unique(column.data)
222
- if column.data in self.validators_columns:
230
+ if not hasattr(getattr(datamodel.obj, column.name), "__call__"):
231
+ field = field_for(datamodel.obj, column.name)
232
+ field.unique = datamodel.is_unique(column.name)
233
+ if column.name in self.validators_columns:
223
234
  if field.validate is None:
224
235
  field.validate = []
225
- field.validate.append(self.validators_columns[column.data])
226
- field.validators.append(self.validators_columns[column.data])
236
+ field.validate.append(self.validators_columns[column.name])
237
+ field.validators.append(self.validators_columns[column.name])
227
238
  return field
228
239
 
229
240
  def convert(
@@ -231,9 +242,8 @@ class Model2SchemaConverter(BaseModel2SchemaConverter):
231
242
  columns: List[str],
232
243
  model: Optional[Type[Model]] = None,
233
244
  nested: bool = True,
234
- enum_dump_by_name: bool = False,
235
245
  parent_schema_name: Optional[str] = None,
236
- ):
246
+ ) -> SQLAlchemyAutoSchema:
237
247
  """
238
248
  Creates a Marshmallow ModelSchema class
239
249
 
@@ -257,16 +267,12 @@ class Model2SchemaConverter(BaseModel2SchemaConverter):
257
267
 
258
268
  _columns = list()
259
269
  tree_columns = columns2Tree(columns)
260
- for column in tree_columns.root.childs:
270
+ for column in tree_columns.root.children:
261
271
  # Get child model is column is dotted notation
262
- ma_sqla_fields_override[column.data] = self._column2field(
263
- _datamodel,
264
- column,
265
- nested,
266
- enum_dump_by_name,
267
- parent_schema_name=parent_schema_name,
272
+ ma_sqla_fields_override[column.name] = self._column2field(
273
+ _datamodel, column, nested, parent_schema_name=parent_schema_name
268
274
  )
269
- _columns.append(column.data)
275
+ _columns.append(column.name)
270
276
  for k, v in ma_sqla_fields_override.items():
271
277
  setattr(SchemaMixin, k, v)
272
278
  return self._meta_schema_factory(
@@ -1,7 +1,7 @@
1
1
  from apispec import APISpec
2
2
  from apispec.ext.marshmallow import MarshmallowPlugin
3
3
  from apispec.ext.marshmallow.common import resolve_schema_cls
4
- from flask import current_app
4
+ from flask import current_app, request
5
5
  from flask_appbuilder.api import BaseApi
6
6
  from flask_appbuilder.api import expose, protect, safe
7
7
  from flask_appbuilder.basemanager import BaseManager
@@ -30,7 +30,7 @@ class OpenApi(BaseApi):
30
30
  @protect()
31
31
  @safe
32
32
  def get(self, version):
33
- """ Endpoint that renders an OpenApi spec for all views that belong
33
+ """Endpoint that renders an OpenApi spec for all views that belong
34
34
  to a certain version
35
35
  ---
36
36
  get:
@@ -66,18 +66,20 @@ class OpenApi(BaseApi):
66
66
 
67
67
  @staticmethod
68
68
  def _create_api_spec(version):
69
+ servers = current_app.config.get(
70
+ "FAB_OPENAPI_SERVERS", [{"url": request.host_url}]
71
+ )
69
72
  return APISpec(
70
73
  title=current_app.appbuilder.app_name,
71
74
  version=version,
72
75
  openapi_version="3.0.2",
73
76
  info=dict(description=current_app.appbuilder.app_name),
74
77
  plugins=[MarshmallowPlugin(schema_name_resolver=resolver)],
75
- servers=[{"url": "/api/{}".format(version)}],
78
+ servers=servers,
76
79
  )
77
80
 
78
81
 
79
82
  class SwaggerView(BaseView):
80
-
81
83
  route_base = "/swagger"
82
84
  default_view = "ui"
83
85
  openapi_uri = "/api/{}/_openapi"
@@ -85,16 +87,20 @@ class SwaggerView(BaseView):
85
87
  @expose("/<version>")
86
88
  @has_access
87
89
  def show(self, version):
90
+ # Apply APPLICATION_ROOT config to the swagger URL
91
+ openapi_uri = request.script_root + self.openapi_uri.format(version)
88
92
  return self.render_template(
89
- "appbuilder/swagger/swagger.html",
90
- openapi_uri=self.openapi_uri.format(version),
93
+ current_app.config.get(
94
+ "FAB_API_SWAGGER_TEMPLATE", "appbuilder/swagger/swagger.html"
95
+ ),
96
+ openapi_uri=openapi_uri,
91
97
  )
92
98
 
93
99
 
94
100
  class OpenApiManager(BaseManager):
95
101
  def register_views(self):
96
- if not self.appbuilder.app.config.get("FAB_ADD_SECURITY_VIEWS", True):
102
+ if not current_app.config.get("FAB_ADD_OPENAPI_VIEWS", True):
97
103
  return
98
- if self.appbuilder.get_app.config.get("FAB_API_SWAGGER_UI", False):
104
+ if current_app.config.get("FAB_API_SWAGGER_UI", False):
99
105
  self.appbuilder.add_api(OpenApi)
100
106
  self.appbuilder.add_view_no_menu(SwaggerView)
@@ -18,6 +18,7 @@ from ..const import (
18
18
  API_PERMISSIONS_RIS_KEY,
19
19
  API_SELECT_COLUMNS_RIS_KEY,
20
20
  API_SELECT_KEYS_RIS_KEY,
21
+ API_SELECT_SEL_COLUMNS_RIS_KEY,
21
22
  API_SHOW_COLUMNS_RIS_KEY,
22
23
  API_SHOW_TITLE_RIS_KEY,
23
24
  )
@@ -70,6 +71,7 @@ get_list_schema = {
70
71
  },
71
72
  },
72
73
  API_SELECT_COLUMNS_RIS_KEY: {"type": "array", "items": {"type": "string"}},
74
+ API_SELECT_SEL_COLUMNS_RIS_KEY: {"type": "array", "items": {"type": "string"}},
73
75
  API_ORDER_COLUMN_RIS_KEY: {"type": "string"},
74
76
  API_ORDER_DIRECTION_RIS_KEY: {"type": "string", "enum": ["asc", "desc"]},
75
77
  API_PAGE_INDEX_RIS_KEY: {"type": "integer"},
@@ -86,7 +88,16 @@ get_list_schema = {
86
88
  {"type": "number"},
87
89
  {"type": "string"},
88
90
  {"type": "boolean"},
89
- {"type": "array"},
91
+ {
92
+ "type": "array",
93
+ "items": {
94
+ "anyOf": [
95
+ {"type": "number"},
96
+ {"type": "string"},
97
+ {"type": "boolean"},
98
+ ]
99
+ },
100
+ },
90
101
  ]
91
102
  },
92
103
  },
@@ -1,31 +1,28 @@
1
1
  import os
2
2
 
3
- from flask import has_request_context, request, session
3
+ from flask import current_app, has_request_context, request, session
4
+ from flask_appbuilder.babel.views import LocaleView
5
+ from flask_appbuilder.basemanager import BaseManager
4
6
  from flask_babel import Babel
5
7
 
6
- from .views import LocaleView
7
- from ..basemanager import BaseManager
8
-
9
8
 
10
9
  class BabelManager(BaseManager):
11
-
12
10
  babel = None
13
11
  locale_view = None
14
12
 
15
13
  def __init__(self, appbuilder):
16
14
  super(BabelManager, self).__init__(appbuilder)
17
- app = appbuilder.get_app
18
- app.config.setdefault("BABEL_DEFAULT_LOCALE", "en")
19
- if not app.config.get("LANGUAGES"):
20
- app.config["LANGUAGES"] = {"en": {"flag": "us", "name": "English"}}
15
+ current_app.config.setdefault("BABEL_DEFAULT_LOCALE", "en")
16
+ if not current_app.config.get("LANGUAGES"):
17
+ current_app.config["LANGUAGES"] = {"en": {"flag": "us", "name": "English"}}
21
18
  appbuilder_parent_dir = os.path.join(
22
19
  os.path.dirname(os.path.abspath(__file__)), os.pardir
23
20
  )
24
21
  appbuilder_translations_path = os.path.join(
25
22
  appbuilder_parent_dir, "translations"
26
23
  )
27
- if "BABEL_TRANSLATION_DIRECTORIES" in app.config:
28
- current_translation_directories = app.config.get(
24
+ if "BABEL_TRANSLATION_DIRECTORIES" in current_app.config:
25
+ current_translation_directories = current_app.config.get(
29
26
  "BABEL_TRANSLATION_DIRECTORIES"
30
27
  )
31
28
  translations_path = (
@@ -33,9 +30,8 @@ class BabelManager(BaseManager):
33
30
  )
34
31
  else:
35
32
  translations_path = appbuilder_translations_path + ";translations"
36
- app.config["BABEL_TRANSLATION_DIRECTORIES"] = translations_path
37
- self.babel = Babel(app)
38
- self.babel.locale_selector_func = self.get_locale
33
+ current_app.config["BABEL_TRANSLATION_DIRECTORIES"] = translations_path
34
+ self.babel = Babel(current_app, locale_selector=self.get_locale)
39
35
 
40
36
  def register_views(self):
41
37
  self.locale_view = LocaleView()
@@ -43,11 +39,11 @@ class BabelManager(BaseManager):
43
39
 
44
40
  @property
45
41
  def babel_default_locale(self):
46
- return self.appbuilder.get_app.config["BABEL_DEFAULT_LOCALE"]
42
+ return current_app.config["BABEL_DEFAULT_LOCALE"]
47
43
 
48
44
  @property
49
45
  def languages(self):
50
- return self.appbuilder.get_app.config["LANGUAGES"]
46
+ return current_app.config["LANGUAGES"]
51
47
 
52
48
  def get_locale(self):
53
49
  if has_request_context():