flask-appbuilder 3.2.1rc1__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.1rc1.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.1rc1.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.1rc1.dist-info/RECORD +0 -270
  139. Flask_AppBuilder-3.2.1rc1.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.1rc1.dist-info → flask_appbuilder-5.0.2.dist-info}/LICENSE +0 -0
  228. {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2.dist-info}/top_level.txt +0 -0
flask_appbuilder/views.py CHANGED
@@ -1,34 +1,41 @@
1
- import json
2
1
  import logging
3
2
  import os.path as op
4
- from typing import Set
5
3
 
6
4
  from flask import (
7
- abort,
5
+ current_app,
8
6
  flash,
9
- jsonify,
10
- make_response,
11
7
  redirect,
12
8
  request,
13
9
  send_file,
14
10
  session,
15
- url_for,
16
11
  )
12
+ from flask_appbuilder._compat import as_unicode
13
+ from flask_appbuilder.baseviews import (
14
+ BaseCRUDView,
15
+ BaseFormView,
16
+ BaseView,
17
+ expose,
18
+ )
19
+ from flask_appbuilder.const import FLAMSG_ERR_SEC_ACCESS_DENIED, PERMISSION_PREFIX
20
+ from flask_appbuilder.exceptions import FABException
21
+ from flask_appbuilder.filemanager import uuid_originalname
22
+ from flask_appbuilder.security.decorators import (
23
+ has_access,
24
+ )
25
+ from flask_appbuilder.urltools import (
26
+ get_order_args,
27
+ get_page_args,
28
+ get_page_size_args,
29
+ )
30
+ from flask_appbuilder.widgets import GroupFormListWidget, ListMasterWidget
17
31
 
18
- from ._compat import as_unicode, string_types
19
- from .baseviews import BaseCRUDView, BaseFormView, BaseView, expose, expose_api
20
- from .const import FLAMSG_ERR_SEC_ACCESS_DENIED, PERMISSION_PREFIX
21
- from .filemanager import uuid_originalname
22
- from .security.decorators import has_access, has_access_api, permission_name
23
- from .urltools import get_filter_args, get_order_args, get_page_args, get_page_size_args
24
- from .widgets import GroupFormListWidget, ListMasterWidget
25
32
 
26
33
  log = logging.getLogger(__name__)
27
34
 
28
35
 
29
36
  class IndexView(BaseView):
30
37
  """
31
- A simple view that implements the index for the site
38
+ A simple view that implements the index for the site
32
39
  """
33
40
 
34
41
  route_base = ""
@@ -43,8 +50,8 @@ class IndexView(BaseView):
43
50
 
44
51
  class UtilView(BaseView):
45
52
  """
46
- A simple view that implements special util routes.
47
- At the moment it only supports the back special endpoint.
53
+ A simple view that implements special util routes.
54
+ At the moment it only supports the back special endpoint.
48
55
  """
49
56
 
50
57
  route_base = ""
@@ -57,15 +64,15 @@ class UtilView(BaseView):
57
64
 
58
65
  class SimpleFormView(BaseFormView):
59
66
  """
60
- View for presenting your own forms
61
- Inherit from this view to provide some base processing
62
- for your customized form views.
67
+ View for presenting your own forms
68
+ Inherit from this view to provide some base processing
69
+ for your customized form views.
63
70
 
64
- Notice that this class inherits from BaseView so all properties
65
- from the parent class can be overridden also.
71
+ Notice that this class inherits from BaseView so all properties
72
+ from the parent class can be overridden also.
66
73
 
67
- Implement form_get and form_post to implement your
68
- form pre-processing and post-processing
74
+ Implement form_get and form_post to implement your
75
+ form pre-processing and post-processing
69
76
  """
70
77
 
71
78
  @expose("/form", methods=["GET"])
@@ -107,15 +114,15 @@ class SimpleFormView(BaseFormView):
107
114
 
108
115
  class PublicFormView(BaseFormView):
109
116
  """
110
- View for presenting your own forms
111
- Inherit from this view to provide some base
112
- processing for your customized form views.
117
+ View for presenting your own forms
118
+ Inherit from this view to provide some base
119
+ processing for your customized form views.
113
120
 
114
- Notice that this class inherits from BaseView
115
- so all properties from the parent class can be overridden also.
121
+ Notice that this class inherits from BaseView
122
+ so all properties from the parent class can be overridden also.
116
123
 
117
- Implement form_get and form_post to implement
118
- your form pre-processing and post-processing
124
+ Implement form_get and form_post to implement
125
+ your form pre-processing and post-processing
119
126
  """
120
127
 
121
128
  @expose("/form", methods=["GET"])
@@ -151,373 +158,15 @@ class PublicFormView(BaseFormView):
151
158
  )
152
159
 
153
160
 
154
- class RestCRUDView(BaseCRUDView):
161
+ class ModelView(BaseCRUDView):
155
162
  """
156
- This class view exposes REST method for CRUD operations on you models
157
- """
158
-
159
- disable_api_route_methods: bool = False
160
- """ Flag to disable this class exposed methods, note that this class
161
- will eventually get deprecated """
162
-
163
- def __init__(self, **kwargs):
164
- if self.disable_api_route_methods:
165
- api_route_methods: Set = {
166
- "api",
167
- "api_read",
168
- "api_get",
169
- "api_create",
170
- "api_update",
171
- "api_delete",
172
- "api_column_add",
173
- "api_column_edit",
174
- "api_readvalues",
175
- }
176
- self.exclude_route_methods = self.exclude_route_methods | api_route_methods
177
- super().__init__(**kwargs)
178
-
179
- def _search_form_json(self):
180
- pass
181
-
182
- def _get_api_urls(self, api_urls=None):
183
- """
184
- Completes a dict with the CRUD urls of the API.
163
+ This is the CRUD generic view.
164
+ If you want to automatically implement create, edit,
165
+ delete, show, and list from your database tables,
166
+ inherit your views from this class.
185
167
 
186
- :param api_urls: A dict with the urls {'<FUNCTION>':'<URL>',...}
187
- :return: A dict with the CRUD urls of the base API.
188
- """
189
- view_name = self.__class__.__name__
190
- api_urls = api_urls or {}
191
- api_urls["read"] = url_for(view_name + ".api_read")
192
- api_urls["delete"] = url_for(view_name + ".api_delete", pk="")
193
- api_urls["create"] = url_for(view_name + ".api_create")
194
- api_urls["update"] = url_for(view_name + ".api_update", pk="")
195
- return api_urls
196
-
197
- def _get_modelview_urls(self, modelview_urls=None):
198
- view_name = self.__class__.__name__
199
- modelview_urls = modelview_urls or {}
200
- modelview_urls["show"] = url_for(view_name + ".show", pk="")
201
- modelview_urls["add"] = url_for(view_name + ".add")
202
- modelview_urls["edit"] = url_for(view_name + ".edit", pk="")
203
- return modelview_urls
204
-
205
- @expose("/api", methods=["GET"])
206
- @has_access_api
207
- @permission_name("list")
208
- def api(self):
209
- log.warning("This API is deprecated and will be removed on 1.15.X")
210
- view_name = self.__class__.__name__
211
- api_urls = self._get_api_urls()
212
- modelview_urls = self._get_modelview_urls()
213
- #
214
- # Collects the CRUD permissions
215
- can_show = self.appbuilder.sm.has_access("can_show", view_name)
216
- can_edit = self.appbuilder.sm.has_access("can_edit", view_name)
217
- can_add = self.appbuilder.sm.has_access("can_add", view_name)
218
- can_delete = self.appbuilder.sm.has_access("can_delete", view_name)
219
- #
220
- # Prepares the form with the search fields make it JSON serializable
221
- form_fields = {}
222
- search_filters = {}
223
- dict_filters = self._filters.get_search_filters()
224
- form = self.search_form.refresh()
225
- for col in self.search_columns:
226
- form_fields[col] = form[col]()
227
- search_filters[col] = [as_unicode(flt.name) for flt in dict_filters[col]]
228
-
229
- ret_json = jsonify(
230
- can_show=can_show,
231
- can_add=can_add,
232
- can_edit=can_edit,
233
- can_delete=can_delete,
234
- label_columns=self._label_columns_json(),
235
- list_columns=self.list_columns,
236
- order_columns=self.order_columns,
237
- page_size=self.page_size,
238
- modelview_name=view_name,
239
- api_urls=api_urls,
240
- search_filters=search_filters,
241
- search_fields=form_fields,
242
- modelview_urls=modelview_urls,
243
- )
244
- response = make_response(ret_json, 200)
245
- response.headers["Content-Type"] = "application/json"
246
- return response
247
-
248
- @expose_api(name="read", url="/api/read", methods=["GET"])
249
- @has_access_api
250
- @permission_name("list")
251
- def api_read(self):
252
- """
253
- """
254
- log.warning("This API is deprecated and will be removed on 2.3.X")
255
- # Get arguments for ordering
256
- if get_order_args().get(self.__class__.__name__):
257
- order_column, order_direction = get_order_args().get(
258
- self.__class__.__name__
259
- )
260
- else:
261
- order_column, order_direction = "", ""
262
- page = get_page_args().get(self.__class__.__name__)
263
- page_size = get_page_size_args().get(self.__class__.__name__)
264
- get_filter_args(self._filters)
265
- joined_filters = self._filters.get_joined_filters(self._base_filters)
266
- count, lst = self.datamodel.query(
267
- joined_filters,
268
- order_column,
269
- order_direction,
270
- page=page,
271
- page_size=page_size,
272
- )
273
- result = self.datamodel.get_values_json(lst, self.list_columns)
274
- pks = self.datamodel.get_keys(lst)
275
- ret_json = jsonify(
276
- label_columns=self._label_columns_json(),
277
- list_columns=self.list_columns,
278
- order_columns=self.order_columns,
279
- page=page,
280
- page_size=page_size,
281
- count=count,
282
- modelview_name=self.__class__.__name__,
283
- pks=pks,
284
- result=result,
285
- )
286
- response = make_response(ret_json, 200)
287
- response.headers["Content-Type"] = "application/json"
288
- return response
289
-
290
- def show_item_dict(self, item):
291
- """Returns a json-able dict for show"""
292
- d = {}
293
- for col in self.show_columns:
294
- v = getattr(item, col)
295
- if not isinstance(v, (int, float, string_types)):
296
- v = str(v)
297
- d[col] = v
298
- return d
299
-
300
- @expose_api(name="get", url="/api/get/<pk>", methods=["GET"])
301
- @has_access_api
302
- @permission_name("show")
303
- def api_get(self, pk):
304
- """
305
- """
306
- log.warning("This API is deprecated and will be removed on 2.3.X")
307
- # Get arguments for ordering
308
- item = self.datamodel.get(pk, self._base_filters)
309
- if not item:
310
- abort(404)
311
- ret_json = jsonify(
312
- pk=pk,
313
- label_columns=self._label_columns_json(),
314
- include_columns=self.show_columns,
315
- modelview_name=self.__class__.__name__,
316
- result=self.show_item_dict(item),
317
- )
318
- response = make_response(ret_json, 200)
319
- response.headers["Content-Type"] = "application/json"
320
- return response
321
-
322
- @expose_api(name="create", url="/api/create", methods=["POST"])
323
- @has_access_api
324
- @permission_name("add")
325
- def api_create(self):
326
- log.warning("This API is deprecated and will be removed on 2.3.X")
327
- get_filter_args(self._filters)
328
- exclude_cols = self._filters.get_relation_cols()
329
- form = self.add_form.refresh()
330
- self._fill_form_exclude_cols(exclude_cols, form)
331
- if form.validate():
332
- item = self.datamodel.obj()
333
- form.populate_obj(item)
334
- self.pre_add(item)
335
- if self.datamodel.add(item):
336
- self.post_add(item)
337
- http_return_code = 200
338
- else:
339
- http_return_code = 500
340
- payload = {
341
- "message": self.datamodel.message[0],
342
- "item": self.show_item_dict(item),
343
- "severity": self.datamodel.message[1],
344
- }
345
- else:
346
- payload = {"message": "Validation error", "error_details": form.errors}
347
- http_return_code = 500
348
- return make_response(jsonify(payload), http_return_code)
349
-
350
- @expose_api(name="update", url="/api/update/<pk>", methods=["PUT"])
351
- @has_access_api
352
- @permission_name("edit")
353
- def api_update(self, pk):
354
- log.warning("This API is deprecated and will be removed on 2.3.X")
355
- get_filter_args(self._filters)
356
- exclude_cols = self._filters.get_relation_cols()
357
-
358
- item = self.datamodel.get(pk, self._base_filters)
359
- if not item:
360
- abort(404)
361
- # convert pk to correct type, if pk is non string type.
362
- pk = self.datamodel.get_pk_value(item)
363
-
364
- form = self.edit_form.refresh(request.form)
365
- # fill the form with the suppressed cols, generated from exclude_cols
366
- self._fill_form_exclude_cols(exclude_cols, form)
367
- # trick to pass unique validation
368
- form._id = pk
369
- http_return_code = 500
370
- if form.validate():
371
-
372
- # Deleting form fields not specified as keys in POST data
373
- # this allows for other Model columns to be left untouched when
374
- # unspecified.
375
- form_fields = set([t for t in form._fields.keys()])
376
- for field in form_fields - set(request.form.keys()):
377
- delattr(form, field)
378
-
379
- form.populate_obj(item)
380
- self.pre_update(item)
381
- if self.datamodel.edit(item):
382
- self.post_update(item)
383
- http_return_code = 200
384
- payload = {
385
- "message": self.datamodel.message[0],
386
- "severity": self.datamodel.message[1],
387
- "item": self.show_item_dict(item),
388
- }
389
- else:
390
- payload = {
391
- "message": "Validation error",
392
- "error_details": form.errors,
393
- "severity": "warning",
394
- }
395
- return make_response(jsonify(payload), http_return_code)
396
-
397
- @expose_api(name="delete", url="/api/delete/<pk>", methods=["DELETE"])
398
- @has_access_api
399
- @permission_name("delete")
400
- def api_delete(self, pk):
401
- log.warning("This API is deprecated and will be removed on 2.3.X")
402
- item = self.datamodel.get(pk, self._base_filters)
403
- if not item:
404
- abort(404)
405
- self.pre_delete(item)
406
- if self.datamodel.delete(item):
407
- self.post_delete(item)
408
- http_return_code = 200
409
- else:
410
- http_return_code = 500
411
- response = make_response(
412
- jsonify(
413
- {
414
- "message": self.datamodel.message[0],
415
- "severity": self.datamodel.message[1],
416
- }
417
- ),
418
- http_return_code,
419
- )
420
- response.headers["Content-Type"] = "application/json"
421
- return response
422
-
423
- def _get_related_column_data(self, col_name, filters):
424
- rel_datamodel = self.datamodel.get_related_interface(col_name)
425
- _filters = rel_datamodel.get_filters(rel_datamodel.get_search_columns_list())
426
- get_filter_args(_filters)
427
- if filters:
428
- filters = _filters.add_filter_list(filters)
429
- else:
430
- filters = _filters
431
- result = rel_datamodel.query(filters)[1]
432
- ret_list = list()
433
- for item in result:
434
- pk = rel_datamodel.get_pk_value(item)
435
- ret_list.append({"id": int(pk), "text": str(item)})
436
- ret_json = json.dumps(ret_list)
437
- return ret_json
438
-
439
- @expose_api(name="column_add", url="/api/column/add/<col_name>", methods=["GET"])
440
- @has_access_api
441
- @permission_name("add")
442
- def api_column_add(self, col_name):
443
- """
444
- Returns list of (pk, object) nice to use on select2.
445
- Use only for related columns.
446
- Always filters with add_form_query_rel_fields, and accepts extra filters
447
- on endpoint arguments.
448
- :param col_name: The related column name
449
- :return: JSON response
450
- """
451
- log.warning("This API is deprecated and will be removed on 2.3.X")
452
- filter_rel_fields = None
453
- if self.add_form_query_rel_fields:
454
- filter_rel_fields = self.add_form_query_rel_fields.get(col_name)
455
- ret_json = self._get_related_column_data(col_name, filter_rel_fields)
456
- response = make_response(ret_json, 200)
457
- response.headers["Content-Type"] = "application/json"
458
- return response
459
-
460
- @expose_api(name="column_edit", url="/api/column/edit/<col_name>", methods=["GET"])
461
- @has_access_api
462
- @permission_name("edit")
463
- def api_column_edit(self, col_name):
464
- """
465
- Returns list of (pk, object) nice to use on select2.
466
- Use only for related columns.
467
- Always filters with edit_form_query_rel_fields, and accepts extra filters
468
- on endpoint arguments.
469
- :param col_name: The related column name
470
- :return: JSON response
471
- """
472
- log.warning("This API is deprecated and will be removed on 2.3.X")
473
- filter_rel_fields = None
474
- if self.edit_form_query_rel_fields:
475
- filter_rel_fields = self.edit_form_query_rel_fields
476
- ret_json = self._get_related_column_data(col_name, filter_rel_fields)
477
- response = make_response(ret_json, 200)
478
- response.headers["Content-Type"] = "application/json"
479
- return response
480
-
481
- @expose_api(name="readvalues", url="/api/readvalues", methods=["GET"])
482
- @has_access_api
483
- @permission_name("list")
484
- def api_readvalues(self):
485
- """
486
- """
487
- log.warning("This API is deprecated and will be removed on 2.3.X")
488
- # Get arguments for ordering
489
- if get_order_args().get(self.__class__.__name__):
490
- order_column, order_direction = get_order_args().get(
491
- self.__class__.__name__
492
- )
493
- else:
494
- order_column, order_direction = "", ""
495
- get_filter_args(self._filters)
496
- joined_filters = self._filters.get_joined_filters(self._base_filters)
497
- count, result = self.datamodel.query(
498
- joined_filters, order_column, order_direction
499
- )
500
-
501
- ret_list = list()
502
- for item in result:
503
- pk = self.datamodel.get_pk_value(item)
504
- ret_list.append({"id": int(pk), "text": str(item)})
505
-
506
- ret_json = json.dumps(ret_list)
507
- response = make_response(ret_json, 200)
508
- response.headers["Content-Type"] = "application/json"
509
- return response
510
-
511
-
512
- class ModelView(RestCRUDView):
513
- """
514
- This is the CRUD generic view.
515
- If you want to automatically implement create, edit,
516
- delete, show, and list from your database tables,
517
- inherit your views from this class.
518
-
519
- Notice that this class inherits from BaseCRUDView and BaseModelView
520
- so all properties from the parent class can be overridden.
168
+ Notice that this class inherits from BaseCRUDView and BaseModelView
169
+ so all properties from the parent class can be overridden.
521
170
  """
522
171
 
523
172
  def __init__(self, **kwargs):
@@ -547,8 +196,12 @@ class ModelView(RestCRUDView):
547
196
  @expose("/list/")
548
197
  @has_access
549
198
  def list(self):
550
-
551
- widgets = self._list()
199
+ self.update_redirect()
200
+ try:
201
+ widgets = self._list()
202
+ except FABException as exc:
203
+ flash(f"An error occurred: {exc}", "warning")
204
+ return redirect(self.get_redirect())
552
205
  return self.render_template(
553
206
  self.list_template, title=self.list_title, widgets=widgets
554
207
  )
@@ -633,14 +286,14 @@ class ModelView(RestCRUDView):
633
286
  @has_access
634
287
  def download(self, filename):
635
288
  return send_file(
636
- op.join(self.appbuilder.app.config["UPLOAD_FOLDER"], filename),
637
- attachment_filename=uuid_originalname(filename),
289
+ op.join(current_app.config["UPLOAD_FOLDER"], filename),
290
+ download_name=uuid_originalname(filename),
638
291
  as_attachment=True,
639
292
  )
640
293
 
641
294
  def get_action_permission_name(self, name: str) -> str:
642
295
  """
643
- Get the permission name of an action name
296
+ Get the permission name of an action name
644
297
  """
645
298
  _permission_name = self.method_permission_name.get(
646
299
  self.actions.get(name).func.__name__
@@ -653,7 +306,7 @@ class ModelView(RestCRUDView):
653
306
  @expose("/action/<string:name>/<pk>", methods=["GET", "POST"])
654
307
  def action(self, name, pk):
655
308
  """
656
- Action method to handle actions from a show view
309
+ Action method to handle actions from a show view
657
310
  """
658
311
  # Maintains compatibility but refuses to proceed if CSRF is enabled
659
312
  if not self.is_get_mutation_allowed():
@@ -674,7 +327,7 @@ class ModelView(RestCRUDView):
674
327
  @expose("/action_post", methods=["POST"])
675
328
  def action_post(self):
676
329
  """
677
- Action method to handle multiple records selected from a list view
330
+ Action method to handle multiple records selected from a list view
678
331
  """
679
332
  name = request.form["action"]
680
333
  pks = request.form.getlist("rowid")
@@ -693,18 +346,18 @@ class ModelView(RestCRUDView):
693
346
 
694
347
  class MasterDetailView(BaseCRUDView):
695
348
  """
696
- Implements behaviour for controlling two CRUD views
697
- linked by PK and FK, in a master/detail type with
698
- two lists.
349
+ Implements behaviour for controlling two CRUD views
350
+ linked by PK and FK, in a master/detail type with
351
+ two lists.
699
352
 
700
- Master view will behave like a left menu::
353
+ Master view will behave like a left menu::
701
354
 
702
- class DetailView(ModelView):
703
- datamodel = SQLAInterface(DetailTable, db.session)
355
+ class DetailView(ModelView):
356
+ datamodel = SQLAInterface(DetailTable, db.session)
704
357
 
705
- class MasterView(MasterDetailView):
706
- datamodel = SQLAInterface(MasterTable, db.session)
707
- related_views = [DetailView]
358
+ class MasterView(MasterDetailView):
359
+ datamodel = SQLAInterface(MasterTable, db.session)
360
+ related_views = [DetailView]
708
361
 
709
362
  """
710
363
 
@@ -744,13 +397,13 @@ class MasterDetailView(BaseCRUDView):
744
397
 
745
398
  class MultipleView(BaseView):
746
399
  """
747
- Use this view to render multiple views on the same page,
748
- exposed on the list endpoint.
400
+ Use this view to render multiple views on the same page,
401
+ exposed on the list endpoint.
749
402
 
750
- example (after defining GroupModelView and ContactModelView)::
403
+ example (after defining GroupModelView and ContactModelView)::
751
404
 
752
- class MultipleViewsExp(MultipleView):
753
- views = [GroupModelView, ContactModelView]
405
+ class MultipleViewsExp(MultipleView):
406
+ views = [GroupModelView, ContactModelView]
754
407
 
755
408
  """
756
409
 
@@ -803,7 +456,7 @@ class MultipleView(BaseView):
803
456
 
804
457
  class CompactCRUDMixin(BaseCRUDView):
805
458
  """
806
- Mix with ModelView to implement a list with add and edit on the same page.
459
+ Mix with ModelView to implement a list with add and edit on the same page.
807
460
  """
808
461
 
809
462
  @classmethod
@@ -816,8 +469,7 @@ class CompactCRUDMixin(BaseCRUDView):
816
469
 
817
470
  @classmethod
818
471
  def get_key(cls, k, default=None):
819
- """Matching get method for ``set_key``
820
- """
472
+ """Matching get method for ``set_key``"""
821
473
  k = cls.__name__ + "__" + k
822
474
  if k in session:
823
475
  return session[k]
@@ -826,14 +478,13 @@ class CompactCRUDMixin(BaseCRUDView):
826
478
 
827
479
  @classmethod
828
480
  def del_key(cls, k):
829
- """Matching get method for ``set_key``
830
- """
481
+ """Matching get method for ``set_key``"""
831
482
  k = cls.__name__ + "__" + k
832
483
  session.pop(k)
833
484
 
834
- def _get_list_widget(self, **args):
835
- """ get joined base filter and current active filter for query """
836
- widgets = super(CompactCRUDMixin, self)._get_list_widget(**args)
485
+ def _get_list_widget(self, **kwargs):
486
+ """get joined base filter and current active filter for query"""
487
+ widgets = super(CompactCRUDMixin, self)._get_list_widget(**kwargs)
837
488
  session_form_widget = self.get_key("session_form_widget", None)
838
489
 
839
490
  form_widget = None