flask-appbuilder 3.2.1rc1__py3-none-any.whl → 5.0.2rc1__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.
- flask_appbuilder/__init__.py +2 -3
- flask_appbuilder/_compat.py +0 -1
- flask_appbuilder/actions.py +14 -14
- flask_appbuilder/api/__init__.py +741 -527
- flask_appbuilder/api/convert.py +104 -98
- flask_appbuilder/api/manager.py +14 -8
- flask_appbuilder/api/schemas.py +12 -1
- flask_appbuilder/babel/manager.py +12 -16
- flask_appbuilder/base.py +353 -280
- flask_appbuilder/basemanager.py +1 -1
- flask_appbuilder/baseviews.py +241 -164
- flask_appbuilder/charts/jsontools.py +10 -10
- flask_appbuilder/charts/views.py +56 -60
- flask_appbuilder/cli.py +115 -70
- flask_appbuilder/const.py +52 -52
- flask_appbuilder/exceptions.py +67 -5
- flask_appbuilder/fields.py +32 -23
- flask_appbuilder/fieldwidgets.py +34 -27
- flask_appbuilder/filemanager.py +33 -45
- flask_appbuilder/filters.py +11 -13
- flask_appbuilder/forms.py +31 -35
- flask_appbuilder/hooks.py +90 -0
- flask_appbuilder/menu.py +35 -10
- flask_appbuilder/models/base.py +47 -57
- flask_appbuilder/models/decorators.py +13 -13
- flask_appbuilder/models/filters.py +42 -38
- flask_appbuilder/models/generic/__init__.py +29 -29
- flask_appbuilder/models/generic/filters.py +11 -3
- flask_appbuilder/models/generic/interface.py +1 -3
- flask_appbuilder/models/group.py +37 -39
- flask_appbuilder/models/mixins.py +22 -18
- flask_appbuilder/models/sqla/__init__.py +19 -72
- flask_appbuilder/models/sqla/base.py +24 -0
- flask_appbuilder/models/sqla/base_legacy.py +132 -0
- flask_appbuilder/models/sqla/filters.py +132 -19
- flask_appbuilder/models/sqla/interface.py +390 -276
- flask_appbuilder/security/api.py +31 -35
- flask_appbuilder/security/decorators.py +181 -83
- flask_appbuilder/security/forms.py +20 -31
- flask_appbuilder/security/manager.py +715 -489
- flask_appbuilder/security/registerviews.py +29 -112
- flask_appbuilder/security/schemas.py +43 -0
- flask_appbuilder/security/sqla/apis/__init__.py +8 -0
- flask_appbuilder/security/sqla/apis/group/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/group/api.py +227 -0
- flask_appbuilder/security/sqla/apis/group/schema.py +73 -0
- flask_appbuilder/security/sqla/apis/permission/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/permission/api.py +19 -0
- flask_appbuilder/security/sqla/apis/permission_view_menu/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/permission_view_menu/api.py +16 -0
- flask_appbuilder/security/sqla/apis/role/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/role/api.py +306 -0
- flask_appbuilder/security/sqla/apis/role/schema.py +27 -0
- flask_appbuilder/security/sqla/apis/user/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/user/api.py +292 -0
- flask_appbuilder/security/sqla/apis/user/schema.py +97 -0
- flask_appbuilder/security/sqla/apis/user/validator.py +27 -0
- flask_appbuilder/security/sqla/apis/view_menu/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/view_menu/api.py +18 -0
- flask_appbuilder/security/sqla/manager.py +421 -203
- flask_appbuilder/security/sqla/models.py +192 -57
- flask_appbuilder/security/utils.py +9 -0
- flask_appbuilder/security/views.py +232 -229
- flask_appbuilder/static/.DS_Store +0 -0
- flask_appbuilder/static/appbuilder/css/ab.css +20 -12
- flask_appbuilder/static/appbuilder/css/bootstrap-datepicker/bootstrap-datepicker3.min.css +7 -0
- flask_appbuilder/static/appbuilder/css/bootstrap.min.css.map +1 -0
- flask_appbuilder/static/appbuilder/css/flags/flags16.css +249 -245
- flask_appbuilder/static/appbuilder/css/fontawesome/all.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/brands.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/fontawesome.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/regular.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/solid.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/svg-with-js.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/v4-font-face.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/v4-shims.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/v5-font-face.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/images/flags16.png +0 -0
- flask_appbuilder/static/appbuilder/css/select2/select2-bootstrap.min.css +7 -0
- flask_appbuilder/static/appbuilder/css/select2/select2.min.css +1 -0
- flask_appbuilder/static/appbuilder/css/swagger/swagger-ui.css +3 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.ttf +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.woff2 +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.ttf +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.woff2 +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.ttf +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.woff2 +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.ttf +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.woff2 +0 -0
- flask_appbuilder/static/appbuilder/js/ab.js +33 -23
- flask_appbuilder/static/appbuilder/js/ab_filters.js +91 -84
- flask_appbuilder/static/appbuilder/js/bootstrap-datepicker/bootstrap-datepicker.min.js +8 -0
- flask_appbuilder/static/appbuilder/js/jquery-latest.js +2 -2
- flask_appbuilder/static/appbuilder/js/select2/select2.min.js +2 -0
- flask_appbuilder/static/appbuilder/js/swagger-ui-bundle.js +3 -0
- flask_appbuilder/templates/appbuilder/baselib.html +9 -3
- flask_appbuilder/templates/appbuilder/general/lib.html +60 -34
- flask_appbuilder/templates/appbuilder/general/model/edit.html +1 -1
- flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html +1 -1
- flask_appbuilder/templates/appbuilder/general/model/search.html +3 -2
- flask_appbuilder/templates/appbuilder/general/model/show.html +1 -1
- flask_appbuilder/templates/appbuilder/general/model/show_cascade.html +1 -1
- flask_appbuilder/templates/appbuilder/general/security/login_db.html +7 -7
- flask_appbuilder/templates/appbuilder/general/security/login_ldap.html +5 -5
- flask_appbuilder/templates/appbuilder/general/security/login_oauth.html +24 -49
- flask_appbuilder/templates/appbuilder/general/widgets/base_list.html +2 -1
- flask_appbuilder/templates/appbuilder/general/widgets/chart.html +4 -2
- flask_appbuilder/templates/appbuilder/general/widgets/direct_chart.html +4 -3
- flask_appbuilder/templates/appbuilder/general/widgets/multiple_chart.html +3 -2
- flask_appbuilder/templates/appbuilder/general/widgets/search.html +11 -10
- flask_appbuilder/templates/appbuilder/init.html +37 -43
- flask_appbuilder/templates/appbuilder/navbar_menu.html +1 -1
- flask_appbuilder/templates/appbuilder/navbar_right.html +2 -2
- flask_appbuilder/templates/appbuilder/swagger/swagger.html +22 -19
- flask_appbuilder/translations/de/LC_MESSAGES/messages.mo +0 -0
- flask_appbuilder/translations/de/LC_MESSAGES/messages.po +305 -161
- flask_appbuilder/translations/fa/LC_MESSAGES/messages.mo +0 -0
- flask_appbuilder/translations/fa/LC_MESSAGES/messages.po +802 -0
- flask_appbuilder/translations/fr/LC_MESSAGES/messages.po +461 -319
- flask_appbuilder/translations/pt_BR/LC_MESSAGES/messages.po +650 -650
- flask_appbuilder/translations/ru/LC_MESSAGES/messages.po +1 -1
- flask_appbuilder/translations/sl/LC_MESSAGES/messages.mo +0 -0
- flask_appbuilder/translations/sl/LC_MESSAGES/messages.po +690 -0
- flask_appbuilder/translations/tr/LC_MESSAGES/messages.mo +0 -0
- flask_appbuilder/translations/tr/LC_MESSAGES/messages.po +1015 -0
- flask_appbuilder/upload.py +20 -22
- flask_appbuilder/urltools.py +39 -19
- flask_appbuilder/utils/base.py +76 -0
- flask_appbuilder/utils/legacy.py +33 -0
- flask_appbuilder/utils/limit.py +20 -0
- flask_appbuilder/validators.py +73 -14
- flask_appbuilder/views.py +75 -424
- flask_appbuilder/widgets.py +50 -51
- {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2rc1.dist-info}/METADATA +36 -76
- flask_appbuilder-5.0.2rc1.dist-info/RECORD +240 -0
- {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2rc1.dist-info}/WHEEL +1 -1
- flask_appbuilder-5.0.2rc1.dist-info/entry_points.txt +2 -0
- Flask_AppBuilder-3.2.1rc1.dist-info/RECORD +0 -270
- Flask_AppBuilder-3.2.1rc1.dist-info/entry_points.txt +0 -6
- flask_appbuilder/console.py +0 -426
- flask_appbuilder/models/mongoengine/__init__.py +0 -0
- flask_appbuilder/models/mongoengine/fields.py +0 -65
- flask_appbuilder/models/mongoengine/filters.py +0 -145
- flask_appbuilder/models/mongoengine/interface.py +0 -328
- flask_appbuilder/security/mongoengine/__init__.py +0 -0
- flask_appbuilder/security/mongoengine/manager.py +0 -402
- flask_appbuilder/security/mongoengine/models.py +0 -120
- flask_appbuilder/static/appbuilder/css/font-awesome.min.css +0 -4
- flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.css +0 -9
- flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.js +0 -28
- flask_appbuilder/static/appbuilder/fonts/FontAwesome.otf +0 -0
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.eot +0 -0
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.svg +0 -2671
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.ttf +0 -0
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff +0 -0
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff2 +0 -0
- flask_appbuilder/static/appbuilder/img/aol.png +0 -0
- flask_appbuilder/static/appbuilder/img/flags/flags16.png +0 -0
- flask_appbuilder/static/appbuilder/img/flickr.png +0 -0
- flask_appbuilder/static/appbuilder/img/google.png +0 -0
- flask_appbuilder/static/appbuilder/img/myopenid.png +0 -0
- flask_appbuilder/static/appbuilder/img/yahoo.png +0 -0
- flask_appbuilder/static/appbuilder/js/_google_charts.js +0 -39
- flask_appbuilder/static/appbuilder/js/html5shiv.js +0 -8
- flask_appbuilder/static/appbuilder/js/respond.min.js +0 -6
- flask_appbuilder/static/appbuilder/select2/select2-spinner.gif +0 -0
- flask_appbuilder/static/appbuilder/select2/select2.css +0 -1205
- flask_appbuilder/static/appbuilder/select2/select2.js +0 -23
- flask_appbuilder/static/appbuilder/select2/select2.png +0 -0
- flask_appbuilder/static/appbuilder/select2/select2x2.png +0 -0
- flask_appbuilder/templates/appbuilder/general/security/login_oid.html +0 -129
- flask_appbuilder/templates/appbuilder/general/security/resetpassword.html +0 -29
- flask_appbuilder/tests/__init__.py +0 -0
- flask_appbuilder/tests/__pycache__/__init__.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/__init__.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/_test_auth_ldap.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/_test_auth_oauth.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/_test_ldapsearch.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/_test_oauth_registration_role.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/base.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/base.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/config_api.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/config_api.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/const.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/const.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_api.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_api.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_menu.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_menu.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_mongoengine.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_mvc.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_mvc.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-37.pyc +0 -0
- flask_appbuilder/tests/_test_auth_ldap.py +0 -1045
- flask_appbuilder/tests/_test_auth_oauth.py +0 -419
- flask_appbuilder/tests/_test_ldapsearch.py +0 -135
- flask_appbuilder/tests/_test_oauth_registration_role.py +0 -59
- flask_appbuilder/tests/app.db +0 -0
- flask_appbuilder/tests/base.py +0 -90
- flask_appbuilder/tests/config_api.py +0 -21
- flask_appbuilder/tests/const.py +0 -9
- flask_appbuilder/tests/mongoengine/__init__.py +0 -0
- flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-36.pyc +0 -0
- flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-37.pyc +0 -0
- flask_appbuilder/tests/mongoengine/__pycache__/models.cpython-36.pyc +0 -0
- flask_appbuilder/tests/mongoengine/models.py +0 -41
- flask_appbuilder/tests/sqla/__init__.py +0 -0
- flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-36.pyc +0 -0
- flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-37.pyc +0 -0
- flask_appbuilder/tests/sqla/__pycache__/models.cpython-36.pyc +0 -0
- flask_appbuilder/tests/sqla/__pycache__/models.cpython-37.pyc +0 -0
- flask_appbuilder/tests/sqla/models.py +0 -340
- flask_appbuilder/tests/test_0_fixture.py +0 -39
- flask_appbuilder/tests/test_api.py +0 -2790
- flask_appbuilder/tests/test_fab_cli.py +0 -72
- flask_appbuilder/tests/test_menu.py +0 -122
- flask_appbuilder/tests/test_mongoengine.py +0 -572
- flask_appbuilder/tests/test_mvc.py +0 -1710
- flask_appbuilder/tests/test_sqlalchemy.py +0 -24
- flask_appbuilder/translations/__pycache__/__init__.cpython-36.pyc +0 -0
- flask_appbuilder/translations/es/LC_MESSAGES/messages.po~ +0 -582
- {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2rc1.dist-info}/LICENSE +0 -0
- {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2rc1.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
|
|
8
|
-
from
|
|
9
|
-
from .
|
|
10
|
-
from .
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
56
|
+
log.error(LOGMSG_ERR_FAB_ADDON_IMPORT, class_path, e)
|
|
57
|
+
return None
|
|
41
58
|
|
|
42
59
|
|
|
43
|
-
class AppBuilder
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
72
|
+
app = Flask(__name__)
|
|
73
|
+
app.config.from_object('config')
|
|
74
|
+
db = SQLA(app)
|
|
75
|
+
appbuilder = AppBuilder(app, db.session)
|
|
63
76
|
|
|
64
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"""
|
|
135
|
-
self.
|
|
136
|
-
self.
|
|
137
|
-
|
|
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
|
-
|
|
144
|
+
Will initialize the Flask app, supporting the app factory pattern.
|
|
153
145
|
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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
|
|
220
|
-
|
|
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
|
|
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
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
241
|
+
def session(self) -> SessionBase:
|
|
247
242
|
"""
|
|
248
|
-
|
|
243
|
+
Get the current sqlalchemy session.
|
|
249
244
|
|
|
250
|
-
|
|
245
|
+
:return: SQLAlchemy Session
|
|
251
246
|
"""
|
|
252
|
-
return self.
|
|
247
|
+
return self._session
|
|
253
248
|
|
|
254
249
|
@property
|
|
255
|
-
def app_name(self):
|
|
250
|
+
def app_name(self) -> str:
|
|
256
251
|
"""
|
|
257
|
-
|
|
252
|
+
Get the App name
|
|
258
253
|
|
|
259
|
-
|
|
254
|
+
:return: String with app name
|
|
260
255
|
"""
|
|
261
|
-
return
|
|
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
|
-
|
|
261
|
+
Get the App theme name
|
|
267
262
|
|
|
268
|
-
|
|
263
|
+
:return: String app theme name
|
|
269
264
|
"""
|
|
270
|
-
return
|
|
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
|
-
|
|
270
|
+
Get the App icon location
|
|
276
271
|
|
|
277
|
-
|
|
272
|
+
:return: String with relative app icon location
|
|
278
273
|
"""
|
|
279
|
-
return
|
|
274
|
+
return current_app.config["APP_ICON"]
|
|
280
275
|
|
|
281
276
|
@property
|
|
282
|
-
def languages(self):
|
|
283
|
-
return
|
|
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
|
-
|
|
283
|
+
Get the current F.A.B. version
|
|
289
284
|
|
|
290
|
-
|
|
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(
|
|
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
|
-
|
|
301
|
+
current_app.register_blueprint(bp)
|
|
307
302
|
|
|
308
|
-
def _add_admin_views(self):
|
|
303
|
+
def _add_admin_views(self) -> None:
|
|
309
304
|
"""
|
|
310
|
-
|
|
305
|
+
Registers indexview, utilview (back function), babel views and Security views.
|
|
311
306
|
"""
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
317
|
+
Registers declared addon's
|
|
323
318
|
"""
|
|
324
319
|
for addon in self._addon_managers:
|
|
325
|
-
|
|
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
|
-
|
|
324
|
+
inst_addon_class: "BaseManager" = addon_class(self)
|
|
329
325
|
try:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
self.addon_managers[addon] =
|
|
334
|
-
log.info(LOGMSG_INF_FAB_ADDON_ADDED
|
|
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
|
|
338
|
-
|
|
339
|
-
def _check_and_init(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if
|
|
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.
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
489
|
-
|
|
502
|
+
def add_separator(
|
|
503
|
+
self, category: str, cond: Optional[Callable[..., bool]] = None
|
|
504
|
+
) -> None:
|
|
490
505
|
"""
|
|
491
|
-
|
|
506
|
+
Add a separator to the menu, you will sequentially create the menu
|
|
492
507
|
|
|
493
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
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
|
-
|
|
578
|
+
This method is useful when you use:
|
|
544
579
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
580
|
+
- `class_permission_name`
|
|
581
|
+
- `previous_class_permission_name`
|
|
582
|
+
- `method_permission_name`
|
|
583
|
+
- `previous_method_permission_name`
|
|
549
584
|
|
|
550
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
613
|
-
self
|
|
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 (
|