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/base.py CHANGED
@@ -1,13 +1,14 @@
1
+ from __future__ import annotations
2
+
1
3
  from functools import reduce
2
4
  import logging
3
- from typing import Dict
4
-
5
- from flask import Blueprint, current_app, url_for
5
+ from typing import Any, Callable, cast, Dict, List, Optional, Type, TYPE_CHECKING, Union
6
6
 
7
- from . import __version__
8
- from .api.manager import OpenApiManager
9
- from .babel.manager import BabelManager
10
- from .const import (
7
+ from flask import Blueprint, current_app, Flask, url_for
8
+ from flask_appbuilder import __version__
9
+ from flask_appbuilder.api.manager import OpenApiManager
10
+ from flask_appbuilder.babel.manager import BabelManager
11
+ from flask_appbuilder.const import (
11
12
  LOGMSG_ERR_FAB_ADD_PERMISSION_MENU,
12
13
  LOGMSG_ERR_FAB_ADD_PERMISSION_VIEW,
13
14
  LOGMSG_ERR_FAB_ADDON_IMPORT,
@@ -16,170 +17,168 @@ from .const import (
16
17
  LOGMSG_INF_FAB_ADDON_ADDED,
17
18
  LOGMSG_WAR_FAB_VIEW_EXISTS,
18
19
  )
19
- from .filters import TemplateFilters
20
- from .menu import Menu, MenuApiManager
21
- from .views import IndexView, UtilView
20
+ from flask_appbuilder.filters import TemplateFilters
21
+ from flask_appbuilder.menu import Menu, MenuApiManager
22
+ from flask_appbuilder.views import IndexView, UtilView
23
+ from sqlalchemy.orm.session import Session as SessionBase
24
+
25
+ if TYPE_CHECKING:
26
+ from flask_appbuilder.basemanager import BaseManager
27
+ from flask_appbuilder.baseviews import BaseView, AbstractViewApi
28
+ from flask_appbuilder.security.manager import BaseSecurityManager
22
29
 
23
30
  log = logging.getLogger(__name__)
24
31
 
25
32
 
26
- def dynamic_class_import(class_path):
33
+ DynamicImportType = Union[
34
+ Type["BaseManager"],
35
+ Type["BaseView"],
36
+ Type["BaseSecurityManager"],
37
+ Type[Menu],
38
+ Type["AbstractViewApi"],
39
+ ]
40
+
41
+
42
+ def dynamic_class_import(class_path: str) -> Optional[DynamicImportType]:
27
43
  """
28
- Will dynamically import a class from a string path
29
- :param class_path: string with class path
30
- :return: class
44
+ Will dynamically import a class from a string path
45
+ :param class_path: string with class path
46
+ :return: class
31
47
  """
32
48
  # Split first occurrence of path
33
49
  try:
34
50
  tmp = class_path.split(".")
35
51
  module_path = ".".join(tmp[0:-1])
36
52
  package = __import__(module_path)
37
- return reduce(getattr, tmp[1:], package)
53
+ return reduce(getattr, tmp[1:], package) # type: ignore
38
54
  except Exception as e:
39
55
  log.exception(e)
40
- log.error(LOGMSG_ERR_FAB_ADDON_IMPORT.format(class_path, e))
56
+ log.error(LOGMSG_ERR_FAB_ADDON_IMPORT, class_path, e)
57
+ return None
41
58
 
42
59
 
43
- class AppBuilder(object):
60
+ class AppBuilder:
44
61
  """
62
+ This is the base class for all the framework.
63
+ This is where you will register all your views and create the menu structure.
64
+ Will hold your flask app object, all your views, and security classes.
45
65
 
66
+ initialize your application like this for SQLAlchemy::
46
67
 
47
- This is the base class for all the framework.
48
- This is were you will register all your views
49
- and create the menu structure.
50
- Will hold your flask app object, all your views, and security classes.
51
-
52
- initialize your application like this for SQLAlchemy::
53
-
54
- from flask import Flask
55
- from flask_appbuilder import SQLA, AppBuilder
56
-
57
- app = Flask(__name__)
58
- app.config.from_object('config')
59
- db = SQLA(app)
60
- appbuilder = AppBuilder(app, db.session)
68
+ from flask import Flask
69
+ from flask_appbuilder import AppBuilder
70
+ from flask_appbuilder.models.sqla.base import SQLA
61
71
 
62
- When using MongoEngine::
72
+ app = Flask(__name__)
73
+ app.config.from_object('config')
74
+ db = SQLA(app)
75
+ appbuilder = AppBuilder(app, db.session)
63
76
 
64
- from flask import Flask
65
- from flask_appbuilder import AppBuilder
66
- from flask_appbuilder.security.mongoengine.manager import SecurityManager
67
- from flask_mongoengine import MongoEngine
68
-
69
- app = Flask(__name__)
70
- app.config.from_object('config')
71
- dbmongo = MongoEngine(app)
72
- appbuilder = AppBuilder(app, security_manager_class=SecurityManager)
73
-
74
- You can also create everything as an application factory.
77
+ You can also create everything as an application factory.
75
78
  """
76
79
 
77
- baseviews = []
78
80
  security_manager_class = None
79
- # Flask app
80
- app = None
81
- # Database Session
82
- session = None
83
- # Security Manager Class
84
- sm = None
85
- # Babel Manager Class
86
- bm = None
87
- # OpenAPI Manager Class
88
- openapi_manager = None
89
- # dict with addon name has key and intantiated class has value
90
- addon_managers = None
91
- # temporary list that hold addon_managers config key
92
- _addon_managers = None
93
-
94
- menu = None
95
- indexview = None
96
-
97
- static_folder = None
98
- static_url_path = None
99
81
 
100
82
  template_filters = None
101
83
 
102
84
  def __init__(
103
85
  self,
104
- app=None,
105
- session=None,
106
- menu=None,
107
- indexview=None,
108
- base_template="appbuilder/baselayout.html",
109
- static_folder="static/appbuilder",
110
- static_url_path="/appbuilder",
111
- security_manager_class=None,
112
- update_perms=True,
113
- ):
114
- """
115
- AppBuilder constructor
116
-
117
- :param app:
118
- The flask app object
119
- :param session:
120
- The SQLAlchemy session object
121
- :param menu:
122
- optional, a previous contructed menu
123
- :param indexview:
124
- optional, your customized indexview
125
- :param static_folder:
126
- optional, your override for the global static folder
127
- :param static_url_path:
128
- optional, your override for the global static url path
129
- :param security_manager_class:
130
- optional, pass your own security manager class
131
- :param update_perms:
132
- optional, update permissions flag (Boolean) you can use
133
- FAB_UPDATE_PERMS config key also
134
- """
135
- self.baseviews = []
136
- self._addon_managers = []
137
- self.addon_managers = {}
86
+ app: Optional[Flask] = None,
87
+ session: Optional[SessionBase] = None,
88
+ menu: Optional[Menu] = None,
89
+ indexview: Optional[Type["AbstractViewApi"]] = None,
90
+ base_template: str = "appbuilder/baselayout.html",
91
+ static_folder: str = "static/appbuilder",
92
+ static_url_path: str = "/appbuilder",
93
+ security_manager_class: Optional[Type["BaseSecurityManager"]] = None,
94
+ update_perms: bool = True,
95
+ ) -> None:
96
+ """
97
+ AppBuilder init
98
+
99
+ :param app:
100
+ The flask app object
101
+ :param session:
102
+ The SQLAlchemy session object
103
+ :param menu:
104
+ optional, a previous contructed menu
105
+ :param indexview:
106
+ optional, your customized indexview
107
+ :param static_folder:
108
+ optional, your override for the global static folder
109
+ :param static_url_path:
110
+ optional, your override for the global static url path
111
+ :param security_manager_class:
112
+ optional, pass your own security manager class
113
+ :param update_perms:
114
+ optional, update permissions flag (Boolean) you can use
115
+ FAB_UPDATE_PERMS config key also
116
+ """
117
+ self._session = None
118
+ self.baseviews: List[Union[Type["AbstractViewApi"], "AbstractViewApi"]] = []
119
+
120
+ # temporary list that hold addon_managers config key
121
+ self._addon_managers: List[str] = []
122
+ # dict with addon name has key and instantiated class has value
123
+ self.addon_managers: Dict[str, Any] = {}
138
124
  self.menu = menu
139
125
  self.base_template = base_template
140
126
  self.security_manager_class = security_manager_class
141
127
  self.indexview = indexview
142
128
  self.static_folder = static_folder
143
129
  self.static_url_path = static_url_path
144
- self.app = app
145
130
  self.update_perms = update_perms
146
131
 
132
+ # Security Manager Class
133
+ self.sm: BaseSecurityManager = None # type: ignore
134
+ # Babel Manager Class
135
+ self.bm: BabelManager = None # type: ignore
136
+ self.openapi_manager: OpenApiManager = None # type: ignore
137
+ self.menuapi_manager: MenuApiManager = None # type: ignore
138
+
147
139
  if app is not None:
148
- self.init_app(app, session)
140
+ self.init_app(app, session=session)
149
141
 
150
- def init_app(self, app, session):
142
+ def init_app(self, app: Flask, session: SessionBase) -> None:
151
143
  """
152
- Will initialize the Flask app, supporting the app factory pattern.
144
+ Will initialize the Flask app, supporting the app factory pattern.
153
145
 
154
- :param app:
155
- :param session: The SQLAlchemy session
146
+ :param app:
147
+ :param session: The SQLAlchemy session
156
148
 
157
149
  """
150
+ log.info("Initializing AppBuilder")
158
151
  app.config.setdefault("APP_NAME", "F.A.B.")
159
152
  app.config.setdefault("APP_THEME", "")
160
153
  app.config.setdefault("APP_ICON", "")
161
154
  app.config.setdefault("LANGUAGES", {"en": {"flag": "gb", "name": "English"}})
162
155
  app.config.setdefault("ADDON_MANAGERS", [])
156
+ app.config.setdefault("RATELIMIT_ENABLED", False)
163
157
  app.config.setdefault("FAB_API_MAX_PAGE_SIZE", 100)
164
158
  app.config.setdefault("FAB_BASE_TEMPLATE", self.base_template)
165
159
  app.config.setdefault("FAB_STATIC_FOLDER", self.static_folder)
166
160
  app.config.setdefault("FAB_STATIC_URL_PATH", self.static_url_path)
167
161
 
168
- self.app = app
169
-
162
+ self._init_extension(app)
163
+ self._session = session
170
164
  self.base_template = app.config.get("FAB_BASE_TEMPLATE", self.base_template)
171
165
  self.static_folder = app.config.get("FAB_STATIC_FOLDER", self.static_folder)
172
166
  self.static_url_path = app.config.get(
173
167
  "FAB_STATIC_URL_PATH", self.static_url_path
174
168
  )
175
169
  _index_view = app.config.get("FAB_INDEX_VIEW", None)
176
- if _index_view is not None:
177
- self.indexview = dynamic_class_import(_index_view)
170
+ if _index_view:
171
+ self.indexview = dynamic_class_import(_index_view) # type: ignore
178
172
  else:
179
173
  self.indexview = self.indexview or IndexView
174
+
180
175
  _menu = app.config.get("FAB_MENU", None)
176
+
177
+ # Setup Menu
181
178
  if _menu is not None:
182
- self.menu = dynamic_class_import(_menu)
179
+ menu = dynamic_class_import(_menu)
180
+ if menu is not None and issubclass(menu, Menu):
181
+ self.menu = menu()
183
182
  else:
184
183
  self.menu = self.menu or Menu()
185
184
 
@@ -189,8 +188,9 @@ class AppBuilder(object):
189
188
  "FAB_SECURITY_MANAGER_CLASS", None
190
189
  )
191
190
  if _security_manager_class_name is not None:
192
- self.security_manager_class = dynamic_class_import(
193
- _security_manager_class_name
191
+ security_manager_class = dynamic_class_import(_security_manager_class_name)
192
+ self.security_manager_class = cast(
193
+ Type["BaseSecurityManager"], security_manager_class
194
194
  )
195
195
  if self.security_manager_class is None:
196
196
  from flask_appbuilder.security.sqla.manager import SecurityManager
@@ -198,7 +198,6 @@ class AppBuilder(object):
198
198
  self.security_manager_class = SecurityManager
199
199
 
200
200
  self._addon_managers = app.config["ADDON_MANAGERS"]
201
- self.session = session
202
201
  self.sm = self.security_manager_class(self)
203
202
  self.bm = BabelManager(self)
204
203
  self.openapi_manager = OpenApiManager(self)
@@ -208,93 +207,89 @@ class AppBuilder(object):
208
207
  app.before_request(self.sm.before_request)
209
208
  self._add_admin_views()
210
209
  self._add_addon_views()
211
- if self.app:
212
- self._add_menu_permissions()
213
- else:
214
- self.post_init()
215
- self._init_extension(app)
210
+ self._add_menu_permissions()
211
+ log.info("Initializing AppBuilder done")
216
212
 
217
- def _init_extension(self, app):
213
+ def _init_extension(self, app: Flask) -> None:
218
214
  app.appbuilder = self
219
- if not hasattr(app, "extensions"):
220
- app.extensions = {}
215
+ if "appbuilder" in app.extensions:
216
+ raise RuntimeError(
217
+ "A 'Flask-AppBuilder' instance has"
218
+ " already been registered on this Flask app."
219
+ )
221
220
  app.extensions["appbuilder"] = self
222
221
 
223
- def post_init(self):
222
+ def post_init(self) -> None:
224
223
  for baseview in self.baseviews:
225
224
  # instantiate the views and add session
226
- self._check_and_init(baseview)
225
+ baseview = self._check_and_init(baseview)
227
226
  # Register the views has blueprints
228
- if baseview.__class__.__name__ not in self.get_app.blueprints.keys():
227
+ if baseview.__class__.__name__ not in current_app.blueprints.keys():
229
228
  self.register_blueprint(baseview)
230
229
  # Add missing permissions where needed
231
230
  self.add_permissions()
232
231
 
233
232
  @property
234
- def get_app(self):
235
- """
236
- Get current or configured flask app
237
-
238
- :return: Flask App
239
- """
240
- if self.app:
241
- return self.app
242
- else:
243
- return current_app
233
+ def app(self) -> Flask:
234
+ log.warning(
235
+ "appbuilder.app is deprecated and will be removed in a future version. "
236
+ "Use current_app instead"
237
+ )
238
+ return current_app
244
239
 
245
240
  @property
246
- def get_session(self):
241
+ def session(self) -> SessionBase:
247
242
  """
248
- Get the current sqlalchemy session.
243
+ Get the current sqlalchemy session.
249
244
 
250
- :return: SQLAlchemy Session
245
+ :return: SQLAlchemy Session
251
246
  """
252
- return self.session
247
+ return self._session
253
248
 
254
249
  @property
255
- def app_name(self):
250
+ def app_name(self) -> str:
256
251
  """
257
- Get the App name
252
+ Get the App name
258
253
 
259
- :return: String with app name
254
+ :return: String with app name
260
255
  """
261
- return self.get_app.config["APP_NAME"]
256
+ return current_app.config["APP_NAME"]
262
257
 
263
258
  @property
264
- def app_theme(self):
259
+ def app_theme(self) -> str:
265
260
  """
266
- Get the App theme name
261
+ Get the App theme name
267
262
 
268
- :return: String app theme name
263
+ :return: String app theme name
269
264
  """
270
- return self.get_app.config["APP_THEME"]
265
+ return current_app.config["APP_THEME"]
271
266
 
272
267
  @property
273
- def app_icon(self):
268
+ def app_icon(self) -> str:
274
269
  """
275
- Get the App icon location
270
+ Get the App icon location
276
271
 
277
- :return: String with relative app icon location
272
+ :return: String with relative app icon location
278
273
  """
279
- return self.get_app.config["APP_ICON"]
274
+ return current_app.config["APP_ICON"]
280
275
 
281
276
  @property
282
- def languages(self):
283
- return self.get_app.config["LANGUAGES"]
277
+ def languages(self) -> Dict[str, Any]:
278
+ return current_app.config["LANGUAGES"]
284
279
 
285
280
  @property
286
- def version(self):
281
+ def version(self) -> str:
287
282
  """
288
- Get the current F.A.B. version
283
+ Get the current F.A.B. version
289
284
 
290
- :return: String with the current F.A.B. version
285
+ :return: String with the current F.A.B. version
291
286
  """
292
287
  return __version__
293
288
 
294
- def _add_global_filters(self):
295
- self.template_filters = TemplateFilters(self.get_app, self.sm)
289
+ def _add_global_filters(self) -> None:
290
+ self.template_filters = TemplateFilters(current_app, self.sm)
296
291
 
297
- def _add_global_static(self):
292
+ def _add_global_static(self) -> None:
298
293
  bp = Blueprint(
299
294
  "appbuilder",
300
295
  __name__,
@@ -303,60 +298,59 @@ class AppBuilder(object):
303
298
  static_folder=self.static_folder,
304
299
  static_url_path=self.static_url_path,
305
300
  )
306
- self.get_app.register_blueprint(bp)
301
+ current_app.register_blueprint(bp)
307
302
 
308
- def _add_admin_views(self):
303
+ def _add_admin_views(self) -> None:
309
304
  """
310
- Registers indexview, utilview (back function), babel views and Security views.
305
+ Registers indexview, utilview (back function), babel views and Security views.
311
306
  """
312
- self.indexview = self._check_and_init(self.indexview)
313
- self.add_view_no_menu(self.indexview)
314
- self.add_view_no_menu(UtilView())
307
+ if self.indexview:
308
+ self._indexview = self.add_view_no_menu(self.indexview)
309
+ self.add_view_no_menu(UtilView)
315
310
  self.bm.register_views()
316
311
  self.sm.register_views()
317
312
  self.openapi_manager.register_views()
318
313
  self.menuapi_manager.register_views()
319
314
 
320
- def _add_addon_views(self):
315
+ def _add_addon_views(self) -> None:
321
316
  """
322
- Registers declared addon's
317
+ Registers declared addon's
323
318
  """
324
319
  for addon in self._addon_managers:
325
- addon_class = dynamic_class_import(addon)
320
+ addon_class_ = dynamic_class_import(addon)
321
+ addon_class = cast(Type["BaseManager"], addon_class_)
326
322
  if addon_class:
327
323
  # Instantiate manager with appbuilder (self)
328
- addon_class = addon_class(self)
324
+ inst_addon_class: "BaseManager" = addon_class(self)
329
325
  try:
330
- addon_class.pre_process()
331
- addon_class.register_views()
332
- addon_class.post_process()
333
- self.addon_managers[addon] = addon_class
334
- log.info(LOGMSG_INF_FAB_ADDON_ADDED.format(str(addon)))
326
+ inst_addon_class.pre_process()
327
+ inst_addon_class.register_views()
328
+ inst_addon_class.post_process()
329
+ self.addon_managers[addon] = inst_addon_class
330
+ log.info(LOGMSG_INF_FAB_ADDON_ADDED, addon)
335
331
  except Exception as e:
336
332
  log.exception(e)
337
- log.error(LOGMSG_ERR_FAB_ADDON_PROCESS.format(addon, e))
338
-
339
- def _check_and_init(self, baseview):
340
- # If class if not instantiated, instantiate it
341
- # and add db session from security models.
342
- if hasattr(baseview, "datamodel"):
343
- if baseview.datamodel.session is None:
344
- baseview.datamodel.session = self.session
345
- if hasattr(baseview, "__call__"):
333
+ log.error(LOGMSG_ERR_FAB_ADDON_PROCESS, addon, e)
334
+
335
+ def _check_and_init(
336
+ self, baseview: Union[Type["AbstractViewApi"], "AbstractViewApi"]
337
+ ) -> "AbstractViewApi":
338
+ if isinstance(baseview, type):
346
339
  baseview = baseview()
347
340
  return baseview
348
341
 
349
342
  def add_view(
350
343
  self,
351
- baseview,
352
- name,
353
- href="",
354
- icon="",
355
- label="",
356
- category="",
357
- category_icon="",
358
- category_label="",
359
- ):
344
+ baseview: Union[Type["AbstractViewApi"], "AbstractViewApi"],
345
+ name: str,
346
+ href: str = "",
347
+ icon: str = "",
348
+ label: str = "",
349
+ category: str = "",
350
+ category_icon: str = "",
351
+ category_label: str = "",
352
+ menu_cond: Optional[Callable[..., bool]] = None,
353
+ ) -> "AbstractViewApi":
360
354
  """
361
355
  Add your views associated with menus using this method.
362
356
 
@@ -382,6 +376,12 @@ class AppBuilder(object):
382
376
  :param category_label:
383
377
  The label that will be displayed on the menu,
384
378
  if absent param name will be used
379
+ :param menu_cond:
380
+ If a callable, :code:`menu_cond` will be invoked when
381
+ constructing the menu items. If it returns :code:`True`,
382
+ then this link will be a part of the menu. Otherwise, it
383
+ will not be included in the menu items. Defaults to
384
+ :code:`None`, meaning the item will always be present.
385
385
 
386
386
  Examples::
387
387
 
@@ -407,19 +407,27 @@ class AppBuilder(object):
407
407
  category_icon='fa-envelop',
408
408
  category_label=_('Other View')
409
409
  )
410
+ # Register a view whose menu item will be conditionally displayed
411
+ appbuilder.add_view(
412
+ YourFeatureView,
413
+ "Your Feature",
414
+ icon='fa-feature',
415
+ label=_('Your Feature'),
416
+ menu_cond=lambda: is_feature_enabled("your-feature"),
417
+ )
410
418
  # Add a link
411
419
  appbuilder.add_link("google", href="www.google.com", icon = "fa-google-plus")
412
420
  """
413
421
  baseview = self._check_and_init(baseview)
414
- log.info(LOGMSG_INF_FAB_ADD_VIEW.format(baseview.__class__.__name__, name))
422
+ log.debug(LOGMSG_INF_FAB_ADD_VIEW, baseview.__class__.__name__, name)
415
423
 
416
424
  if not self._view_exists(baseview):
417
425
  baseview.appbuilder = self
418
426
  self.baseviews.append(baseview)
419
427
  self._process_inner_views()
420
- if self.app:
421
- self.register_blueprint(baseview)
422
- self._add_permission(baseview)
428
+ self.register_blueprint(baseview)
429
+ self._add_permission(baseview)
430
+ self.add_limits(baseview)
423
431
  self.add_link(
424
432
  name=name,
425
433
  href=href,
@@ -429,43 +437,53 @@ class AppBuilder(object):
429
437
  category_icon=category_icon,
430
438
  category_label=category_label,
431
439
  baseview=baseview,
440
+ cond=menu_cond,
432
441
  )
433
442
  return baseview
434
443
 
435
444
  def add_link(
436
445
  self,
437
- name,
438
- href,
439
- icon="",
440
- label="",
441
- category="",
442
- category_icon="",
443
- category_label="",
444
- baseview=None,
445
- ):
446
- """
447
- Add your own links to menu using this method
448
-
449
- :param name:
450
- The string name that identifies the menu.
451
- :param href:
452
- Override the generated href for the menu.
453
- You can use an url string or an endpoint name
454
- :param icon:
455
- Font-Awesome icon name, optional.
456
- :param label:
457
- The label that will be displayed on the menu,
458
- if absent param name will be used
459
- :param category:
460
- The menu category where the menu will be included,
461
- if non provided the view will be accessible as a top menu.
462
- :param category_icon:
463
- Font-Awesome icon name for the category, optional.
464
- :param category_label:
465
- The label that will be displayed on the menu,
466
- if absent param name will be used
446
+ name: str,
447
+ href: str,
448
+ icon: str = "",
449
+ label: str = "",
450
+ category: str = "",
451
+ category_icon: str = "",
452
+ category_label: str = "",
453
+ baseview: Optional["AbstractViewApi"] = None,
454
+ cond: Optional[Callable[..., bool]] = None,
455
+ ) -> None:
456
+ """
457
+ Add your own links to menu using this method
467
458
 
468
- """
459
+ :param baseview:
460
+ :param name:
461
+ The string name that identifies the menu.
462
+ :param href:
463
+ Override the generated href for the menu.
464
+ You can use an url string or an endpoint name
465
+ :param icon:
466
+ Font-Awesome icon name, optional.
467
+ :param label:
468
+ The label that will be displayed on the menu,
469
+ if absent param name will be used
470
+ :param category:
471
+ The menu category where the menu will be included,
472
+ if non provided the view will be accessible as a top menu.
473
+ :param category_icon:
474
+ Font-Awesome icon name for the category, optional.
475
+ :param category_label:
476
+ The label that will be displayed on the menu,
477
+ if absent param name will be used
478
+ :param cond:
479
+ If a callable, :code:`cond` will be invoked when
480
+ constructing the menu items. If it returns :code:`True`,
481
+ then this link will be a part of the menu. Otherwise, it
482
+ will not be included in the menu items. Defaults to
483
+ :code:`None`, meaning the item will always be present.
484
+ """
485
+ if self.menu is None:
486
+ return
469
487
  self.menu.add_link(
470
488
  name=name,
471
489
  href=href,
@@ -475,114 +493,159 @@ class AppBuilder(object):
475
493
  category_icon=category_icon,
476
494
  category_label=category_label,
477
495
  baseview=baseview,
496
+ cond=cond,
478
497
  )
479
- if self.app:
480
- self._add_permissions_menu(name)
481
- if category:
482
- self._add_permissions_menu(category)
483
-
484
- def add_separator(self, category):
485
- """
486
- Add a separator to the menu, you will sequentially create the menu
498
+ self._add_permissions_menu(name)
499
+ if category:
500
+ self._add_permissions_menu(category)
487
501
 
488
- :param category:
489
- The menu category where the separator will be included.
502
+ def add_separator(
503
+ self, category: str, cond: Optional[Callable[..., bool]] = None
504
+ ) -> None:
490
505
  """
491
- self.menu.add_separator(category)
506
+ Add a separator to the menu, you will sequentially create the menu
492
507
 
493
- def add_view_no_menu(self, baseview, endpoint=None, static_folder=None):
508
+ :param category:
509
+ The menu category where the separator will be included.
510
+ :param cond:
511
+ If a callable, :code:`cond` will be invoked when
512
+ constructing the menu items. If it returns :code:`True`,
513
+ then this separator will be a part of the menu. Otherwise,
514
+ it will not be included in the menu items. Defaults to
515
+ :code:`None`, meaning the separator will always be present.
516
+ """
517
+ if self.menu is None:
518
+ return
519
+ self.menu.add_separator(category, cond=cond)
520
+
521
+ def add_view_no_menu(
522
+ self,
523
+ baseview: Union[Type["AbstractViewApi"], "AbstractViewApi"],
524
+ endpoint: Optional[str] = None,
525
+ static_folder: Optional[str] = None,
526
+ ) -> "AbstractViewApi":
494
527
  """
495
- Add your views without creating a menu.
528
+ Add your views without creating a menu.
496
529
 
497
530
  :param baseview:
498
531
  A BaseView type class instantiated.
532
+ :param endpoint: The endpoint path for the Flask blueprint
533
+ :param static_folder: The static folder for the Flask blueprint
499
534
 
500
535
  """
501
536
  baseview = self._check_and_init(baseview)
502
- log.info(LOGMSG_INF_FAB_ADD_VIEW.format(baseview.__class__.__name__, ""))
537
+ log.debug(LOGMSG_INF_FAB_ADD_VIEW, baseview.__class__.__name__, "")
503
538
 
504
539
  if not self._view_exists(baseview):
505
540
  baseview.appbuilder = self
506
541
  self.baseviews.append(baseview)
507
542
  self._process_inner_views()
508
- if self.app:
509
- self.register_blueprint(
510
- baseview, endpoint=endpoint, static_folder=static_folder
511
- )
512
- self._add_permission(baseview)
543
+ self.register_blueprint(
544
+ baseview, endpoint=endpoint, static_folder=static_folder
545
+ )
546
+ self._add_permission(baseview)
547
+ self.add_limits(baseview)
513
548
  else:
514
- log.warning(LOGMSG_WAR_FAB_VIEW_EXISTS.format(baseview.__class__.__name__))
549
+ log.warning(LOGMSG_WAR_FAB_VIEW_EXISTS, baseview.__class__.__name__)
515
550
  return baseview
516
551
 
517
- def add_api(self, baseview):
552
+ def add_api(self, baseview: Type["AbstractViewApi"]) -> "AbstractViewApi":
518
553
  """
519
- Add a BaseApi class or child to AppBuilder
554
+ Add a BaseApi class or child to AppBuilder
520
555
 
521
556
  :param baseview: A BaseApi type class
522
557
  :return: The instantiated base view
523
558
  """
524
559
  return self.add_view_no_menu(baseview)
525
560
 
526
- def security_cleanup(self):
561
+ def security_cleanup(self) -> None:
527
562
  """
528
- This method is useful if you have changed
529
- the name of your menus or classes,
530
- changing them will leave behind permissions
531
- that are not associated with anything.
563
+ This method is useful if you have changed
564
+ the name of your menus or classes,
565
+ changing them will leave behind permissions
566
+ that are not associated with anything.
532
567
 
533
- You can use it always or just sometimes to
534
- perform a security cleanup. Warning this will delete any permission
535
- that is no longer part of any registered view or menu.
568
+ You can use it always or just sometimes to
569
+ perform a security cleanup. Warning this will delete any permission
570
+ that is no longer part of any registered view or menu.
536
571
 
537
- Remember invoke ONLY AFTER YOU HAVE REGISTERED ALL VIEWS
572
+ Remember invoke ONLY AFTER YOU HAVE REGISTERED ALL VIEWS
538
573
  """
539
574
  self.sm.security_cleanup(self.baseviews, self.menu)
540
575
 
541
- def security_converge(self, dry=False) -> Dict:
576
+ def security_converge(self, dry: bool = False) -> Dict[str, Any]:
542
577
  """
543
- This method is useful when you use:
578
+ This method is useful when you use:
544
579
 
545
- - `class_permission_name`
546
- - `previous_class_permission_name`
547
- - `method_permission_name`
548
- - `previous_method_permission_name`
580
+ - `class_permission_name`
581
+ - `previous_class_permission_name`
582
+ - `method_permission_name`
583
+ - `previous_method_permission_name`
549
584
 
550
- migrates all permissions to the new names on all the Roles
585
+ migrates all permissions to the new names on all the Roles
551
586
 
552
587
  :param dry: If True will not change DB
553
588
  :return: Dict with all computed necessary operations
554
589
  """
555
- return self.sm.security_converge(self.baseviews, self.menu, dry)
590
+ if self.menu is None:
591
+ return {}
592
+ return self.sm.security_converge(self.baseviews, self.menu.menu, dry)
593
+
594
+ def get_url_for_login_with(self, next_url: str | None = None) -> str:
595
+ if self.sm.auth_view is None:
596
+ return ""
597
+ return url_for("%s.%s" % (self.sm.auth_view.endpoint, "login"), next=next_url)
556
598
 
557
599
  @property
558
- def get_url_for_login(self):
600
+ def get_url_for_login(self) -> str:
601
+ if self.sm.auth_view is None:
602
+ return ""
559
603
  return url_for("%s.%s" % (self.sm.auth_view.endpoint, "login"))
560
604
 
561
605
  @property
562
- def get_url_for_logout(self):
606
+ def get_url_for_logout(self) -> str:
607
+ if self.sm.auth_view is None:
608
+ return ""
563
609
  return url_for("%s.%s" % (self.sm.auth_view.endpoint, "logout"))
564
610
 
565
611
  @property
566
- def get_url_for_index(self):
567
- return url_for("%s.%s" % (self.indexview.endpoint, self.indexview.default_view))
612
+ def get_url_for_index(self) -> str:
613
+ if self._indexview is None:
614
+ return ""
615
+ return url_for(
616
+ "%s.%s" % (self._indexview.endpoint, self._indexview.default_view)
617
+ )
568
618
 
569
619
  @property
570
- def get_url_for_userinfo(self):
620
+ def get_url_for_userinfo(self) -> str:
621
+ if self.sm.user_view is None:
622
+ return ""
571
623
  return url_for("%s.%s" % (self.sm.user_view.endpoint, "userinfo"))
572
624
 
573
- def get_url_for_locale(self, lang):
625
+ def get_url_for_locale(self, lang: str) -> str:
626
+ if self.bm.locale_view is None:
627
+ return ""
574
628
  return url_for(
575
629
  "%s.%s" % (self.bm.locale_view.endpoint, self.bm.locale_view.default_view),
576
630
  locale=lang,
577
631
  )
578
632
 
579
- def add_permissions(self, update_perms=False):
633
+ def add_limits(self, baseview: "AbstractViewApi") -> None:
634
+ if hasattr(baseview, "limits"):
635
+ self.sm.add_limit_view(baseview)
636
+
637
+ def add_permissions(self, update_perms: bool = False) -> None:
638
+ from flask_appbuilder.baseviews import AbstractViewApi
639
+
580
640
  if self.update_perms or update_perms:
581
641
  for baseview in self.baseviews:
642
+ baseview = cast(AbstractViewApi, baseview)
582
643
  self._add_permission(baseview, update_perms=update_perms)
583
644
  self._add_menu_permissions(update_perms=update_perms)
584
645
 
585
- def _add_permission(self, baseview, update_perms=False):
646
+ def _add_permission(
647
+ self, baseview: "AbstractViewApi", update_perms: bool = False
648
+ ) -> None:
586
649
  if self.update_perms or update_perms:
587
650
  try:
588
651
  self.sm.add_permissions_view(
@@ -590,17 +653,19 @@ class AppBuilder(object):
590
653
  )
591
654
  except Exception as e:
592
655
  log.exception(e)
593
- log.error(LOGMSG_ERR_FAB_ADD_PERMISSION_VIEW.format(str(e)))
656
+ log.error(LOGMSG_ERR_FAB_ADD_PERMISSION_VIEW, e)
594
657
 
595
- def _add_permissions_menu(self, name, update_perms=False):
658
+ def _add_permissions_menu(self, name: str, update_perms: bool = False) -> None:
596
659
  if self.update_perms or update_perms:
597
660
  try:
598
661
  self.sm.add_permissions_menu(name)
599
662
  except Exception as e:
600
663
  log.exception(e)
601
- log.error(LOGMSG_ERR_FAB_ADD_PERMISSION_MENU.format(str(e)))
664
+ log.error(LOGMSG_ERR_FAB_ADD_PERMISSION_MENU, e)
602
665
 
603
- def _add_menu_permissions(self, update_perms=False):
666
+ def _add_menu_permissions(self, update_perms: bool = False) -> None:
667
+ if self.menu is None:
668
+ return
604
669
  if self.update_perms or update_perms:
605
670
  for category in self.menu.get_list():
606
671
  self._add_permissions_menu(category.name, update_perms=update_perms)
@@ -609,21 +674,29 @@ class AppBuilder(object):
609
674
  if item.name != "-":
610
675
  self._add_permissions_menu(item.name, update_perms=update_perms)
611
676
 
612
- def register_blueprint(self, baseview, endpoint=None, static_folder=None):
613
- self.get_app.register_blueprint(
677
+ def register_blueprint(
678
+ self,
679
+ baseview: "AbstractViewApi",
680
+ endpoint: Optional[str] = None,
681
+ static_folder: Optional[str] = None,
682
+ ) -> None:
683
+ current_app.register_blueprint(
614
684
  baseview.create_blueprint(
615
685
  self, endpoint=endpoint, static_folder=static_folder
616
686
  )
617
687
  )
618
688
 
619
- def _view_exists(self, view):
689
+ def _view_exists(self, view: "AbstractViewApi") -> bool:
620
690
  for baseview in self.baseviews:
621
691
  if baseview.__class__ == view.__class__:
622
692
  return True
623
693
  return False
624
694
 
625
- def _process_inner_views(self):
695
+ def _process_inner_views(self) -> None:
696
+ from flask_appbuilder.baseviews import AbstractViewApi
697
+
626
698
  for view in self.baseviews:
699
+ view = cast(AbstractViewApi, view)
627
700
  for inner_class in view.get_uninit_inner_views():
628
701
  for v in self.baseviews:
629
702
  if (