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
|
@@ -5,17 +5,17 @@ from flask_appbuilder._compat import as_unicode
|
|
|
5
5
|
|
|
6
6
|
def dict_to_json(xcol, ycols, labels, value_columns): # pragma: no cover
|
|
7
7
|
"""
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Converts a list of dicts from datamodel query results
|
|
9
|
+
to google chart json data.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
:param xcol:
|
|
12
|
+
The name of a string column to be used has X axis on chart
|
|
13
|
+
:param ycols:
|
|
14
|
+
A list with the names of series cols, that can be used as numeric
|
|
15
|
+
:param labels:
|
|
16
|
+
A dict with the columns labels.
|
|
17
|
+
:param value_columns:
|
|
18
|
+
A list of dicts with the values to convert
|
|
19
19
|
"""
|
|
20
20
|
json_data = dict()
|
|
21
21
|
|
flask_appbuilder/charts/views.py
CHANGED
|
@@ -15,10 +15,10 @@ log = logging.getLogger(__name__)
|
|
|
15
15
|
|
|
16
16
|
class BaseChartView(BaseModelView):
|
|
17
17
|
"""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
This is the base class for all chart views.
|
|
19
|
+
Use DirectByChartView or GroupByChartView, override their properties
|
|
20
|
+
and their base classes
|
|
21
|
+
(BaseView, BaseModelView, BaseChartView) to customise your charts
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
chart_template = "appbuilder/general/charts/chart.html"
|
|
@@ -60,8 +60,8 @@ class BaseChartView(BaseModelView):
|
|
|
60
60
|
|
|
61
61
|
def _get_view_widget(self, **kwargs):
|
|
62
62
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
:return:
|
|
64
|
+
Returns a widget
|
|
65
65
|
"""
|
|
66
66
|
return self._get_chart_widget(**kwargs).get("chart")
|
|
67
67
|
|
|
@@ -139,7 +139,7 @@ class GroupByChartView(BaseChartView):
|
|
|
139
139
|
|
|
140
140
|
def get_group_by_class(self, definition):
|
|
141
141
|
"""
|
|
142
|
-
|
|
142
|
+
intantiates the processing class (Direct or Grouped) and returns it.
|
|
143
143
|
"""
|
|
144
144
|
group_by = definition["group"]
|
|
145
145
|
series = definition["series"]
|
|
@@ -160,7 +160,6 @@ class GroupByChartView(BaseChartView):
|
|
|
160
160
|
definition="",
|
|
161
161
|
**args
|
|
162
162
|
):
|
|
163
|
-
|
|
164
163
|
height = height or self.height
|
|
165
164
|
widgets = widgets or dict()
|
|
166
165
|
joined_filters = filters.get_joined_filters(self._base_filters)
|
|
@@ -221,45 +220,45 @@ class GroupByChartView(BaseChartView):
|
|
|
221
220
|
|
|
222
221
|
class DirectByChartView(GroupByChartView):
|
|
223
222
|
"""
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
Use this class to display charts with multiple series,
|
|
224
|
+
based on columns or methods defined on models.
|
|
225
|
+
You can display multiple charts on the same view.
|
|
227
226
|
|
|
228
|
-
|
|
227
|
+
Default routing point is '/chart'
|
|
229
228
|
|
|
230
|
-
|
|
229
|
+
Setup definitions property to configure the chart
|
|
231
230
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
231
|
+
:label: (optional) String label to display on chart selection.
|
|
232
|
+
:group: String with the column name or method from model.
|
|
233
|
+
:formatter: (optional) function that formats the output of 'group' key
|
|
234
|
+
:series: A list of tuples with the aggregation function and the column name
|
|
235
|
+
to apply the aggregation
|
|
237
236
|
|
|
238
|
-
|
|
237
|
+
The **definitions** property respects the following grammar::
|
|
239
238
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
239
|
+
definitions = [
|
|
240
|
+
{
|
|
241
|
+
'label': 'label for chart definition',
|
|
242
|
+
'group': '<COLNAME>'|'<MODEL FUNCNAME>',
|
|
243
|
+
'formatter': <FUNC FORMATTER FOR GROUP COL>,
|
|
244
|
+
'series': ['<COLNAME>'|'<MODEL FUNCNAME>',...]
|
|
245
|
+
}, ...
|
|
246
|
+
]
|
|
248
247
|
|
|
249
|
-
|
|
248
|
+
example::
|
|
250
249
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
250
|
+
class CountryDirectChartView(DirectByChartView):
|
|
251
|
+
datamodel = SQLAInterface(CountryStats)
|
|
252
|
+
chart_title = 'Direct Data Example'
|
|
254
253
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
254
|
+
definitions = [
|
|
255
|
+
{
|
|
256
|
+
'label': 'Unemployment',
|
|
257
|
+
'group': 'stat_date',
|
|
258
|
+
'series': ['unemployed_perc',
|
|
259
|
+
'college_perc']
|
|
260
|
+
}
|
|
261
|
+
]
|
|
263
262
|
|
|
264
263
|
"""
|
|
265
264
|
|
|
@@ -293,7 +292,6 @@ class BaseSimpleGroupByChartView(BaseChartView): # pragma: no cover
|
|
|
293
292
|
height=None,
|
|
294
293
|
**args
|
|
295
294
|
):
|
|
296
|
-
|
|
297
295
|
height = height or self.height
|
|
298
296
|
widgets = widgets or dict()
|
|
299
297
|
group_by = group_by or self.group_by_columns[0]
|
|
@@ -333,8 +331,8 @@ class BaseSimpleDirectChartView(BaseChartView): # pragma: no cover
|
|
|
333
331
|
|
|
334
332
|
def get_group_by_columns(self):
|
|
335
333
|
"""
|
|
336
|
-
|
|
337
|
-
|
|
334
|
+
returns the keys from direct_columns
|
|
335
|
+
Used in template, so that user can choose from options
|
|
338
336
|
"""
|
|
339
337
|
return list(self.direct_columns.keys())
|
|
340
338
|
|
|
@@ -348,7 +346,6 @@ class BaseSimpleDirectChartView(BaseChartView): # pragma: no cover
|
|
|
348
346
|
height=None,
|
|
349
347
|
**args
|
|
350
348
|
):
|
|
351
|
-
|
|
352
349
|
height = height or self.height
|
|
353
350
|
widgets = widgets or dict()
|
|
354
351
|
joined_filters = filters.get_joined_filters(self._base_filters)
|
|
@@ -377,11 +374,11 @@ class BaseSimpleDirectChartView(BaseChartView): # pragma: no cover
|
|
|
377
374
|
|
|
378
375
|
class ChartView(BaseSimpleGroupByChartView): # pragma: no cover
|
|
379
376
|
"""
|
|
380
|
-
|
|
377
|
+
**DEPRECATED**
|
|
381
378
|
|
|
382
|
-
|
|
379
|
+
Provides a simple (and hopefully nice) way to draw charts on your application.
|
|
383
380
|
|
|
384
|
-
|
|
381
|
+
This will show Google Charts based on group by of your tables.
|
|
385
382
|
"""
|
|
386
383
|
|
|
387
384
|
@expose("/chart/<group_by>")
|
|
@@ -410,12 +407,12 @@ class ChartView(BaseSimpleGroupByChartView): # pragma: no cover
|
|
|
410
407
|
|
|
411
408
|
class TimeChartView(BaseSimpleGroupByChartView): # pragma: no cover
|
|
412
409
|
"""
|
|
413
|
-
|
|
410
|
+
**DEPRECATED**
|
|
414
411
|
|
|
415
|
-
|
|
412
|
+
Provides a simple way to draw some time charts on your application.
|
|
416
413
|
|
|
417
|
-
|
|
418
|
-
|
|
414
|
+
This will show Google Charts based on count and group
|
|
415
|
+
by month and year for your tables.
|
|
419
416
|
"""
|
|
420
417
|
|
|
421
418
|
chart_template = "appbuilder/general/charts/chart_time.html"
|
|
@@ -432,7 +429,6 @@ class TimeChartView(BaseSimpleGroupByChartView): # pragma: no cover
|
|
|
432
429
|
height=None,
|
|
433
430
|
**args
|
|
434
431
|
):
|
|
435
|
-
|
|
436
432
|
height = height or self.height
|
|
437
433
|
widgets = widgets or dict()
|
|
438
434
|
group_by = group_by or self.group_by_columns[0]
|
|
@@ -487,17 +483,17 @@ class TimeChartView(BaseSimpleGroupByChartView): # pragma: no cover
|
|
|
487
483
|
|
|
488
484
|
class DirectChartView(BaseSimpleDirectChartView): # pragma: no cover
|
|
489
485
|
"""
|
|
490
|
-
|
|
486
|
+
**DEPRECATED**
|
|
491
487
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
488
|
+
This class is responsible for displaying a Google chart with
|
|
489
|
+
direct model values. Chart widget uses json.
|
|
490
|
+
No group by is processed, example::
|
|
495
491
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
492
|
+
class StatsChartView(DirectChartView):
|
|
493
|
+
datamodel = SQLAInterface(Stats)
|
|
494
|
+
chart_title = lazy_gettext('Statistics')
|
|
495
|
+
direct_columns = {'Some Stats': ('X_col_1', 'stat_col_1', 'stat_col_2'),
|
|
496
|
+
'Other Stats': ('X_col2', 'stat_col_3')}
|
|
501
497
|
|
|
502
498
|
"""
|
|
503
499
|
|
flask_appbuilder/cli.py
CHANGED
|
@@ -1,35 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from io import BytesIO
|
|
2
4
|
import os
|
|
3
5
|
import shutil
|
|
6
|
+
from typing import Any, Optional, Union
|
|
4
7
|
from urllib.request import urlopen
|
|
5
8
|
from zipfile import ZipFile
|
|
6
9
|
|
|
7
10
|
import click
|
|
8
11
|
from flask import current_app
|
|
9
12
|
from flask.cli import with_appcontext
|
|
13
|
+
import jinja2
|
|
10
14
|
|
|
11
|
-
from .const import AUTH_DB, AUTH_LDAP, AUTH_OAUTH,
|
|
15
|
+
from .const import AUTH_DB, AUTH_LDAP, AUTH_OAUTH, AUTH_REMOTE_USER
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
SQLA_REPO_URL = (
|
|
15
|
-
"https://github.com/dpgaspar/Flask-AppBuilder-Skeleton/archive/
|
|
16
|
-
)
|
|
17
|
-
MONGOENGIE_REPO_URL = (
|
|
18
|
-
"https://github.com/dpgaspar/Flask-AppBuilder-Skeleton-me/archive/master.zip"
|
|
19
|
+
"https://github.com/dpgaspar/Flask-AppBuilder-Skeleton/archive/refs/heads/v5.zip"
|
|
19
20
|
)
|
|
20
21
|
ADDON_REPO_URL = (
|
|
21
22
|
"https://github.com/dpgaspar/Flask-AppBuilder-Skeleton-AddOn/archive/master.zip"
|
|
22
23
|
)
|
|
23
24
|
|
|
25
|
+
MIN_SECRET_KEY_SIZE = 20
|
|
26
|
+
|
|
24
27
|
|
|
25
|
-
def
|
|
28
|
+
def validate_secret_key(ctx: click.Context, param: click.Option, value: str) -> str:
|
|
29
|
+
if len(value) < MIN_SECRET_KEY_SIZE:
|
|
30
|
+
raise click.BadParameter(f"SECRET_KEY size is less then {MIN_SECRET_KEY_SIZE}")
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def echo_header(title: str) -> None:
|
|
26
35
|
click.echo(click.style(title, fg="green"))
|
|
27
36
|
click.echo(click.style("-" * len(title), fg="green"))
|
|
28
37
|
|
|
29
38
|
|
|
39
|
+
def cast_int_like_to_int(cli_arg: Any) -> Union[None, str, int]:
|
|
40
|
+
"""Cast int-like objects to int if possible
|
|
41
|
+
|
|
42
|
+
If the arg cannot be cast to an integer, return the unmodified object instead."""
|
|
43
|
+
try:
|
|
44
|
+
cli_arg_int = int(cli_arg)
|
|
45
|
+
return cli_arg_int
|
|
46
|
+
except TypeError:
|
|
47
|
+
# Don't cast if None
|
|
48
|
+
return cli_arg
|
|
49
|
+
except ValueError:
|
|
50
|
+
# Don't cast non-int-like strings
|
|
51
|
+
return cli_arg
|
|
52
|
+
|
|
53
|
+
|
|
30
54
|
@click.group()
|
|
31
|
-
def fab():
|
|
32
|
-
"""
|
|
55
|
+
def fab() -> None:
|
|
56
|
+
"""FAB flask group commands"""
|
|
33
57
|
pass
|
|
34
58
|
|
|
35
59
|
|
|
@@ -40,13 +64,14 @@ def fab():
|
|
|
40
64
|
@click.option("--email", default="admin@fab.org", prompt="Email")
|
|
41
65
|
@click.password_option()
|
|
42
66
|
@with_appcontext
|
|
43
|
-
def create_admin(
|
|
67
|
+
def create_admin(
|
|
68
|
+
username: str, firstname: str, lastname: str, email: str, password: str
|
|
69
|
+
) -> None:
|
|
44
70
|
"""
|
|
45
|
-
|
|
71
|
+
Creates an admin user
|
|
46
72
|
"""
|
|
47
73
|
auth_type = {
|
|
48
74
|
AUTH_DB: "Database Authentications",
|
|
49
|
-
AUTH_OID: "OpenID Authentication",
|
|
50
75
|
AUTH_LDAP: "LDAP Authentication",
|
|
51
76
|
AUTH_REMOTE_USER: "WebServer REMOTE_USER Authentication",
|
|
52
77
|
AUTH_OAUTH: "OAuth Authentication",
|
|
@@ -76,7 +101,7 @@ def create_admin(username, firstname, lastname, email, password):
|
|
|
76
101
|
if user:
|
|
77
102
|
click.echo(click.style("Admin User {0} created.".format(username), fg="green"))
|
|
78
103
|
else:
|
|
79
|
-
click.echo(click.style("No user created an error
|
|
104
|
+
click.echo(click.style("No user created an error occurred", fg="red"))
|
|
80
105
|
|
|
81
106
|
|
|
82
107
|
@fab.command("create-user")
|
|
@@ -87,9 +112,11 @@ def create_admin(username, firstname, lastname, email, password):
|
|
|
87
112
|
@click.option("--email", prompt="Email")
|
|
88
113
|
@click.password_option()
|
|
89
114
|
@with_appcontext
|
|
90
|
-
def create_user(
|
|
115
|
+
def create_user(
|
|
116
|
+
role: str, username: str, firstname: str, lastname: str, email: str, password: str
|
|
117
|
+
) -> None:
|
|
91
118
|
"""
|
|
92
|
-
|
|
119
|
+
Create a user
|
|
93
120
|
"""
|
|
94
121
|
user = current_app.appbuilder.sm.find_user(username=username)
|
|
95
122
|
if user:
|
|
@@ -121,9 +148,9 @@ def create_user(role, username, firstname, lastname, email, password):
|
|
|
121
148
|
)
|
|
122
149
|
@click.password_option()
|
|
123
150
|
@with_appcontext
|
|
124
|
-
def reset_password(username, password):
|
|
151
|
+
def reset_password(username: str, password: str) -> None:
|
|
125
152
|
"""
|
|
126
|
-
|
|
153
|
+
Resets a user's password
|
|
127
154
|
"""
|
|
128
155
|
user = current_app.appbuilder.sm.find_user(username=username)
|
|
129
156
|
if not user:
|
|
@@ -135,22 +162,45 @@ def reset_password(username, password):
|
|
|
135
162
|
|
|
136
163
|
@fab.command("create-db")
|
|
137
164
|
@with_appcontext
|
|
138
|
-
def create_db():
|
|
165
|
+
def create_db() -> None:
|
|
139
166
|
"""
|
|
140
|
-
|
|
167
|
+
Create all your database objects (SQLAlchemy specific).
|
|
141
168
|
"""
|
|
142
169
|
from flask_appbuilder.models.sqla import Model
|
|
143
170
|
|
|
144
|
-
engine = current_app.appbuilder.
|
|
171
|
+
engine = current_app.appbuilder.session.get_bind(mapper=None, clause=None)
|
|
145
172
|
Model.metadata.create_all(engine)
|
|
146
173
|
click.echo(click.style("DB objects created", fg="green"))
|
|
147
174
|
|
|
148
175
|
|
|
176
|
+
@fab.command("export-roles")
|
|
177
|
+
@with_appcontext
|
|
178
|
+
@click.option("--path", "-path", help="Specify filepath to export roles to")
|
|
179
|
+
@click.option("--indent", help="Specify indent of generated JSON file")
|
|
180
|
+
def export_roles(
|
|
181
|
+
path: Optional[str] = None, indent: Optional[Union[int, str]] = None
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Exports roles with permissions and view menus to JSON file"""
|
|
184
|
+
# Cast negative numbers to int (as they are passed as str from CLI)
|
|
185
|
+
cast_indent = cast_int_like_to_int(indent)
|
|
186
|
+
current_app.appbuilder.sm.export_roles(path=path, indent=cast_indent)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@fab.command("import-roles")
|
|
190
|
+
@with_appcontext
|
|
191
|
+
@click.option(
|
|
192
|
+
"--path", "-p", help="Path to a JSON file containing roles", required=True
|
|
193
|
+
)
|
|
194
|
+
def import_roles(path: str) -> None:
|
|
195
|
+
"""Imports roles with permissions and view menus from JSON file"""
|
|
196
|
+
current_app.appbuilder.sm.import_roles(path)
|
|
197
|
+
|
|
198
|
+
|
|
149
199
|
@fab.command("version")
|
|
150
200
|
@with_appcontext
|
|
151
|
-
def version():
|
|
201
|
+
def version() -> None:
|
|
152
202
|
"""
|
|
153
|
-
|
|
203
|
+
Flask-AppBuilder package version
|
|
154
204
|
"""
|
|
155
205
|
click.echo(
|
|
156
206
|
click.style(
|
|
@@ -163,9 +213,9 @@ def version():
|
|
|
163
213
|
|
|
164
214
|
@fab.command("security-cleanup")
|
|
165
215
|
@with_appcontext
|
|
166
|
-
def security_cleanup():
|
|
216
|
+
def security_cleanup() -> None:
|
|
167
217
|
"""
|
|
168
|
-
|
|
218
|
+
Cleanup unused permissions from views and roles.
|
|
169
219
|
"""
|
|
170
220
|
current_app.appbuilder.security_cleanup()
|
|
171
221
|
click.echo(click.style("Finished security cleanup", fg="green"))
|
|
@@ -176,9 +226,9 @@ def security_cleanup():
|
|
|
176
226
|
"--dry-run", "-d", is_flag=True, help="Dry run & print state transitions."
|
|
177
227
|
)
|
|
178
228
|
@with_appcontext
|
|
179
|
-
def security_converge(dry_run=False):
|
|
229
|
+
def security_converge(dry_run: bool = False) -> None:
|
|
180
230
|
"""
|
|
181
|
-
|
|
231
|
+
Converges security deletes previous_class_permission_name
|
|
182
232
|
"""
|
|
183
233
|
state_transitions = current_app.appbuilder.security_converge(dry=dry_run)
|
|
184
234
|
if dry_run:
|
|
@@ -201,9 +251,9 @@ def security_converge(dry_run=False):
|
|
|
201
251
|
|
|
202
252
|
@fab.command("create-permissions")
|
|
203
253
|
@with_appcontext
|
|
204
|
-
def create_permissions():
|
|
254
|
+
def create_permissions() -> None:
|
|
205
255
|
"""
|
|
206
|
-
|
|
256
|
+
Creates all permissions and add them to the ADMIN Role.
|
|
207
257
|
"""
|
|
208
258
|
current_app.appbuilder.add_permissions(update_perms=True)
|
|
209
259
|
click.echo(click.style("Created all permissions", fg="green"))
|
|
@@ -211,9 +261,9 @@ def create_permissions():
|
|
|
211
261
|
|
|
212
262
|
@fab.command("list-views")
|
|
213
263
|
@with_appcontext
|
|
214
|
-
def list_views():
|
|
264
|
+
def list_views() -> None:
|
|
215
265
|
"""
|
|
216
|
-
|
|
266
|
+
List all registered views
|
|
217
267
|
"""
|
|
218
268
|
echo_header("List of registered views")
|
|
219
269
|
for view in current_app.appbuilder.baseviews:
|
|
@@ -226,9 +276,9 @@ def list_views():
|
|
|
226
276
|
|
|
227
277
|
@fab.command("list-users")
|
|
228
278
|
@with_appcontext
|
|
229
|
-
def list_users():
|
|
279
|
+
def list_users() -> None:
|
|
230
280
|
"""
|
|
231
|
-
|
|
281
|
+
List all users on the database
|
|
232
282
|
"""
|
|
233
283
|
echo_header("List of users")
|
|
234
284
|
for user in current_app.appbuilder.sm.get_all_users():
|
|
@@ -246,43 +296,38 @@ def list_users():
|
|
|
246
296
|
help="Your application name, directory will have this name",
|
|
247
297
|
)
|
|
248
298
|
@click.option(
|
|
249
|
-
"--
|
|
250
|
-
prompt="Your
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
299
|
+
"--secret-key",
|
|
300
|
+
prompt="Your app SECRET_KEY. It should be a long random string. Minimal size is 20",
|
|
301
|
+
callback=validate_secret_key,
|
|
302
|
+
help="This secret key is used by Flask for"
|
|
303
|
+
"securely signing the session cookie and can be used for any other security"
|
|
304
|
+
"related needs by extensions or your application."
|
|
305
|
+
"It should be a long random bytes or str",
|
|
254
306
|
)
|
|
255
|
-
def create_app(name,
|
|
307
|
+
def create_app(name: str, secret_key: str) -> None:
|
|
256
308
|
"""
|
|
257
|
-
|
|
309
|
+
Create a Skeleton application (needs internet connection to github)
|
|
258
310
|
"""
|
|
259
311
|
try:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
dirname = "Flask-AppBuilder-Skeleton-master"
|
|
263
|
-
elif engine.lower() == "mongoengine":
|
|
264
|
-
url = urlopen(MONGOENGIE_REPO_URL)
|
|
265
|
-
dirname = "Flask-AppBuilder-Skeleton-me-master"
|
|
312
|
+
url = urlopen(SQLA_REPO_URL)
|
|
313
|
+
dirname = "Flask-AppBuilder-Skeleton-5"
|
|
266
314
|
zipfile = ZipFile(BytesIO(url.read()))
|
|
267
315
|
zipfile.extractall()
|
|
268
316
|
os.rename(dirname, name)
|
|
317
|
+
|
|
318
|
+
template_filename = os.path.join(os.path.abspath(name), "config.py.tpl")
|
|
319
|
+
config_filename = os.path.join(os.path.abspath(name), "config.py")
|
|
320
|
+
template = jinja2.Template(open(template_filename).read())
|
|
321
|
+
rendered_template = template.render({"secret_key": secret_key})
|
|
322
|
+
with open(config_filename, "w") as fd:
|
|
323
|
+
fd.write(rendered_template)
|
|
324
|
+
|
|
269
325
|
click.echo(click.style("Downloaded the skeleton app, good coding!", fg="green"))
|
|
270
|
-
return True
|
|
271
326
|
except Exception as e:
|
|
272
327
|
click.echo(click.style("Something went wrong {0}".format(e), fg="red"))
|
|
273
|
-
|
|
274
|
-
click.
|
|
275
|
-
|
|
276
|
-
"Try downloading from {0}".format(SQLA_REPO_URL), fg="green"
|
|
277
|
-
)
|
|
278
|
-
)
|
|
279
|
-
elif engine.lower() == "mongoengine":
|
|
280
|
-
click.echo(
|
|
281
|
-
click.style(
|
|
282
|
-
"Try downloading from {0}".format(MONGOENGIE_REPO_URL), fg="green"
|
|
283
|
-
)
|
|
284
|
-
)
|
|
285
|
-
return False
|
|
328
|
+
click.echo(
|
|
329
|
+
click.style("Try downloading from {0}".format(SQLA_REPO_URL), fg="green")
|
|
330
|
+
)
|
|
286
331
|
|
|
287
332
|
|
|
288
333
|
@fab.command("create-addon")
|
|
@@ -291,9 +336,9 @@ def create_app(name, engine):
|
|
|
291
336
|
prompt="Your new addon name",
|
|
292
337
|
help="Your addon name will be prefixed by fab_addon_, directory will have this name",
|
|
293
338
|
)
|
|
294
|
-
def create_addon(name):
|
|
339
|
+
def create_addon(name: str) -> None:
|
|
295
340
|
"""
|
|
296
|
-
|
|
341
|
+
Create a Skeleton AddOn (needs internet connection to github)
|
|
297
342
|
"""
|
|
298
343
|
try:
|
|
299
344
|
full_name = "fab_addon_" + name
|
|
@@ -311,19 +356,17 @@ def create_addon(name):
|
|
|
311
356
|
click.echo(
|
|
312
357
|
click.style("Downloaded the skeleton addon, good coding!", fg="green")
|
|
313
358
|
)
|
|
314
|
-
return True
|
|
315
359
|
except Exception as e:
|
|
316
360
|
click.echo(click.style("Something went wrong {0}".format(e), fg="red"))
|
|
317
|
-
return False
|
|
318
361
|
|
|
319
362
|
|
|
320
363
|
@fab.command("collect-static")
|
|
321
364
|
@click.option(
|
|
322
365
|
"--static_folder", default="app/static", help="Your projects static folder"
|
|
323
366
|
)
|
|
324
|
-
def collect_static(static_folder):
|
|
367
|
+
def collect_static(static_folder: str) -> None:
|
|
325
368
|
"""
|
|
326
|
-
|
|
369
|
+
Copies flask-appbuilder static files to your projects static folder
|
|
327
370
|
"""
|
|
328
371
|
appbuilder_static_path = os.path.join(
|
|
329
372
|
os.path.dirname(os.path.abspath(__file__)), "static/appbuilder"
|
|
@@ -357,9 +400,11 @@ def collect_static(static_folder):
|
|
|
357
400
|
@click.option(
|
|
358
401
|
"--keywords", "-k", multiple=True, default=["lazy_gettext", "gettext", "_", "__"]
|
|
359
402
|
)
|
|
360
|
-
def babel_extract(
|
|
403
|
+
def babel_extract(
|
|
404
|
+
config: str, input: str, output: str, target: str, keywords: list[str]
|
|
405
|
+
) -> None:
|
|
361
406
|
"""
|
|
362
|
-
|
|
407
|
+
Babel, Extracts and updates all messages marked for translation
|
|
363
408
|
"""
|
|
364
409
|
click.echo(
|
|
365
410
|
click.style(
|
|
@@ -369,10 +414,10 @@ def babel_extract(config, input, output, target, keywords):
|
|
|
369
414
|
fg="green",
|
|
370
415
|
)
|
|
371
416
|
)
|
|
372
|
-
|
|
417
|
+
keywords_args = " -k ".join(keywords)
|
|
373
418
|
os.popen(
|
|
374
419
|
"pybabel extract -F {0} -k {1} -o {2} {3}".format(
|
|
375
|
-
config,
|
|
420
|
+
config, keywords_args, output, input
|
|
376
421
|
)
|
|
377
422
|
)
|
|
378
423
|
click.echo(click.style("Starting Update target:{0}".format(target), fg="green"))
|
|
@@ -386,9 +431,9 @@ def babel_extract(config, input, output, target, keywords):
|
|
|
386
431
|
default="app/translations",
|
|
387
432
|
help="The target directory where translations reside",
|
|
388
433
|
)
|
|
389
|
-
def babel_compile(target):
|
|
434
|
+
def babel_compile(target: str) -> None:
|
|
390
435
|
"""
|
|
391
|
-
|
|
436
|
+
Babel, Compiles all translations
|
|
392
437
|
"""
|
|
393
438
|
click.echo(click.style("Starting Compile target:{0}".format(target), fg="green"))
|
|
394
439
|
os.popen("pybabel compile -f -d {0}".format(target))
|