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.
- 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.2.dist-info}/METADATA +36 -76
- flask_appbuilder-5.0.2.dist-info/RECORD +240 -0
- {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2.dist-info}/WHEEL +1 -1
- flask_appbuilder-5.0.2.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.2.dist-info}/LICENSE +0 -0
- {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2.dist-info}/top_level.txt +0 -0
flask_appbuilder/models/group.py
CHANGED
|
@@ -16,10 +16,10 @@ log = logging.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
def aggregate(label=""):
|
|
18
18
|
"""
|
|
19
|
-
|
|
19
|
+
Use this decorator to set a label for your aggregation functions on charts.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
:param label:
|
|
22
|
+
The label to complement with the column
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
def wrap(f):
|
|
@@ -32,8 +32,8 @@ def aggregate(label=""):
|
|
|
32
32
|
@aggregate(_("Count of"))
|
|
33
33
|
def aggregate_count(items, col):
|
|
34
34
|
"""
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
Function to use on Group by Charts.
|
|
36
|
+
accepts a list and returns the count of the list's items
|
|
37
37
|
"""
|
|
38
38
|
return len(list(items))
|
|
39
39
|
|
|
@@ -41,8 +41,8 @@ def aggregate_count(items, col):
|
|
|
41
41
|
@aggregate(_("Sum of"))
|
|
42
42
|
def aggregate_sum(items, col):
|
|
43
43
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
Function to use on Group by Charts.
|
|
45
|
+
accepts a list and returns the sum of the list's items
|
|
46
46
|
"""
|
|
47
47
|
return sum(getattr(item, col) for item in items)
|
|
48
48
|
|
|
@@ -50,8 +50,8 @@ def aggregate_sum(items, col):
|
|
|
50
50
|
@aggregate(_("Avg. of"))
|
|
51
51
|
def aggregate_avg(items, col):
|
|
52
52
|
"""
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
Function to use on Group by Charts.
|
|
54
|
+
accepts a list and returns the average of the list's items
|
|
55
55
|
"""
|
|
56
56
|
try:
|
|
57
57
|
return aggregate_sum(items, col) / aggregate_count(items, col)
|
|
@@ -70,12 +70,12 @@ class BaseGroupBy(object):
|
|
|
70
70
|
self, column_name, name, aggregate_func=aggregate_count, aggregate_col=""
|
|
71
71
|
):
|
|
72
72
|
"""
|
|
73
|
-
|
|
73
|
+
Constructor.
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
:param column_name:
|
|
76
|
+
Model field name
|
|
77
|
+
:param name:
|
|
78
|
+
The group by name
|
|
79
79
|
|
|
80
80
|
"""
|
|
81
81
|
self.column_name = column_name
|
|
@@ -85,7 +85,7 @@ class BaseGroupBy(object):
|
|
|
85
85
|
|
|
86
86
|
def apply(self, data):
|
|
87
87
|
"""
|
|
88
|
-
|
|
88
|
+
Override this to implement you own new filters
|
|
89
89
|
"""
|
|
90
90
|
pass
|
|
91
91
|
|
|
@@ -118,7 +118,7 @@ class GroupByCol(BaseGroupBy):
|
|
|
118
118
|
},
|
|
119
119
|
]
|
|
120
120
|
json_data["rows"] = []
|
|
121
|
-
for
|
|
121
|
+
for grouped, items in groupby(data, self.get_group_col):
|
|
122
122
|
aggregate_value = self.aggregate_func(items, self.aggregate_col)
|
|
123
123
|
json_data["rows"].append(
|
|
124
124
|
{
|
|
@@ -181,13 +181,13 @@ class GroupByDateMonth(BaseGroupBy):
|
|
|
181
181
|
|
|
182
182
|
class BaseProcessData(object):
|
|
183
183
|
"""
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
Base class to process data.
|
|
185
|
+
It will group data by one or many columns or functions.
|
|
186
|
+
The aggregation is made by an already defined function, or by a custom function
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
:group_bys_cols: A list of columns or functions to group data.
|
|
189
|
+
:aggr_by_cols: A list of tuples [(<AGGR FUNC>,'<COLNAME>'),...].
|
|
190
|
+
:formatter_by_cols: A dict.
|
|
191
191
|
"""
|
|
192
192
|
|
|
193
193
|
group_bys_cols = None
|
|
@@ -255,7 +255,7 @@ class BaseProcessData(object):
|
|
|
255
255
|
for group_col_data, i in zip(item[0], enumerate(item[0])):
|
|
256
256
|
row[self.group_bys_cols[i]] = str(group_col_data)
|
|
257
257
|
for col_data, i in zip(item[1:], enumerate(item[1:])):
|
|
258
|
-
log.debug("
|
|
258
|
+
log.debug("%s,%s", col_data, i)
|
|
259
259
|
key = self.aggr_by_cols[i].__name__ + self.aggr_by_cols[i]
|
|
260
260
|
if isinstance(col_data, datetime.date):
|
|
261
261
|
row[key] = str(col_data)
|
|
@@ -266,18 +266,18 @@ class BaseProcessData(object):
|
|
|
266
266
|
|
|
267
267
|
def to_json(self, data, labels=None):
|
|
268
268
|
"""
|
|
269
|
-
|
|
269
|
+
Will return a dict with Google JSON structure for charts
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
The Google structure::
|
|
272
272
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
{
|
|
274
|
+
cols: [{id:<COL_NAME>, label:<LABEL FOR COL>, type: <COL TYPE>}, ...]
|
|
275
|
+
rows: [{c: [{v: <COL VALUE}, ...], ... ]
|
|
276
|
+
}
|
|
277
277
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
:param data:
|
|
279
|
+
:param labels: dict with labels to include on Google JSON strcut
|
|
280
|
+
:return: dict with Google JSON structure
|
|
281
281
|
"""
|
|
282
282
|
labels = labels or dict()
|
|
283
283
|
json_data = dict()
|
|
@@ -331,20 +331,18 @@ class DirectProcessData(BaseProcessData):
|
|
|
331
331
|
|
|
332
332
|
class GroupByProcessData(BaseProcessData):
|
|
333
333
|
"""
|
|
334
|
-
|
|
334
|
+
Groups by data by chosen columns (property group_bys_cols).
|
|
335
335
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
336
|
+
:data: A list of objects
|
|
337
|
+
:sort: boolean, if true python will sort the data
|
|
338
|
+
:return: A List of lists with group column and aggregation
|
|
339
339
|
"""
|
|
340
340
|
|
|
341
341
|
def apply(self, data, sort=True):
|
|
342
342
|
if sort:
|
|
343
343
|
data = sorted(data, key=self.attrgetter(*self.group_bys_cols))
|
|
344
344
|
result = []
|
|
345
|
-
for
|
|
346
|
-
data, key=self.attrgetter(*self.group_bys_cols)
|
|
347
|
-
):
|
|
345
|
+
for grouped, items in groupby(data, key=self.attrgetter(*self.group_bys_cols)):
|
|
348
346
|
items = list(items)
|
|
349
347
|
result_item = [self.format_columns(grouped)]
|
|
350
348
|
for aggr_by_col in self.aggr_by_cols:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import datetime
|
|
1
|
+
from datetime import datetime
|
|
2
2
|
import logging
|
|
3
3
|
|
|
4
4
|
from flask import g
|
|
@@ -13,7 +13,7 @@ log = logging.getLogger(__name__)
|
|
|
13
13
|
|
|
14
14
|
class FileColumn(types.TypeDecorator):
|
|
15
15
|
"""
|
|
16
|
-
|
|
16
|
+
Extends SQLAlchemy to support and mostly identify a File Column
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
impl = types.Text
|
|
@@ -21,7 +21,7 @@ class FileColumn(types.TypeDecorator):
|
|
|
21
21
|
|
|
22
22
|
class ImageColumn(types.TypeDecorator):
|
|
23
23
|
"""
|
|
24
|
-
|
|
24
|
+
Extends SQLAlchemy to support and mostly identify an Image Column
|
|
25
25
|
|
|
26
26
|
"""
|
|
27
27
|
|
|
@@ -33,24 +33,24 @@ class ImageColumn(types.TypeDecorator):
|
|
|
33
33
|
self.size = size
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class AuditMixin
|
|
36
|
+
class AuditMixin:
|
|
37
37
|
"""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
AuditMixin
|
|
39
|
+
Mixin for models, adds 4 columns to stamp,
|
|
40
|
+
time and user on creation and modification
|
|
41
|
+
will create the following columns:
|
|
42
|
+
|
|
43
|
+
:created on:
|
|
44
|
+
:changed on:
|
|
45
|
+
:created by:
|
|
46
|
+
:changed by:
|
|
47
47
|
"""
|
|
48
48
|
|
|
49
|
-
created_on = Column(DateTime, default=datetime.
|
|
49
|
+
created_on = Column(DateTime, default=lambda: datetime.now(), nullable=False)
|
|
50
50
|
changed_on = Column(
|
|
51
51
|
DateTime,
|
|
52
|
-
default=datetime.
|
|
53
|
-
onupdate=datetime.
|
|
52
|
+
default=lambda: datetime.now(),
|
|
53
|
+
onupdate=lambda: datetime.now(),
|
|
54
54
|
nullable=False,
|
|
55
55
|
)
|
|
56
56
|
|
|
@@ -62,8 +62,10 @@ class AuditMixin(object):
|
|
|
62
62
|
|
|
63
63
|
@declared_attr
|
|
64
64
|
def created_by(cls):
|
|
65
|
+
from flask_appbuilder.security.sqla.models import User
|
|
66
|
+
|
|
65
67
|
return relationship(
|
|
66
|
-
|
|
68
|
+
User,
|
|
67
69
|
primaryjoin="%s.created_by_fk == User.id" % cls.__name__,
|
|
68
70
|
enable_typechecks=False,
|
|
69
71
|
)
|
|
@@ -80,8 +82,10 @@ class AuditMixin(object):
|
|
|
80
82
|
|
|
81
83
|
@declared_attr
|
|
82
84
|
def changed_by(cls):
|
|
85
|
+
from flask_appbuilder.security.sqla.models import User
|
|
86
|
+
|
|
83
87
|
return relationship(
|
|
84
|
-
|
|
88
|
+
User,
|
|
85
89
|
primaryjoin="%s.changed_by_fk == User.id" % cls.__name__,
|
|
86
90
|
enable_typechecks=False,
|
|
87
91
|
)
|
|
@@ -1,91 +1,38 @@
|
|
|
1
|
-
import
|
|
2
|
-
import logging
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
|
-
from flask_sqlalchemy import _QueryProperty, DefaultMeta, SQLAlchemy
|
|
6
|
-
|
|
7
|
-
try:
|
|
8
|
-
from sqlalchemy.ext.declarative import as_declarative
|
|
9
|
-
except ImportError:
|
|
10
|
-
from sqlalchemy.ext.declarative.api import as_declarative
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
from sqlalchemy.orm.util import identity_key # noqa
|
|
14
|
-
|
|
15
|
-
has_identity_key = True
|
|
16
|
-
except ImportError:
|
|
17
|
-
has_identity_key = False
|
|
18
|
-
|
|
19
|
-
log = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
|
-
_camelcase_re = re.compile(r"([A-Z]+)(?=[a-z0-9])")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class SQLA(SQLAlchemy):
|
|
25
|
-
"""
|
|
26
|
-
This is a child class of flask_SQLAlchemy
|
|
27
|
-
It's purpose is to override the declarative base of the original
|
|
28
|
-
package. So that it is bound to F.A.B. Model class allowing the dev
|
|
29
|
-
to be in the same namespace of the security tables (and others)
|
|
30
|
-
and can use AuditMixin class alike.
|
|
1
|
+
from __future__ import annotations
|
|
31
2
|
|
|
32
|
-
|
|
33
|
-
|
|
3
|
+
import datetime
|
|
4
|
+
from typing import Any
|
|
34
5
|
|
|
35
|
-
|
|
36
|
-
base = Model
|
|
37
|
-
base.query = _QueryProperty(self)
|
|
38
|
-
return base
|
|
6
|
+
from sqlalchemy.orm import declarative_base
|
|
39
7
|
|
|
40
|
-
|
|
41
|
-
"""Returns a list of all tables relevant for a bind."""
|
|
42
|
-
result = []
|
|
43
|
-
tables = Model.metadata.tables
|
|
44
|
-
for key in tables:
|
|
45
|
-
if tables[key].info.get("bind_key") == bind:
|
|
46
|
-
result.append(tables[key])
|
|
47
|
-
return result
|
|
8
|
+
Base = declarative_base()
|
|
48
9
|
|
|
49
10
|
|
|
50
|
-
class
|
|
51
|
-
|
|
52
|
-
Base Model declarative meta for all Models definitions.
|
|
53
|
-
Setups bind_keys to support multiple databases.
|
|
54
|
-
Setup the table name based on the class camelcase name.
|
|
11
|
+
class Model(Base):
|
|
12
|
+
__abstract__ = True
|
|
55
13
|
"""
|
|
14
|
+
Use this class has the base for your models,
|
|
15
|
+
it will define your table names automatically
|
|
16
|
+
MyModel will be called my_model on the database.
|
|
56
17
|
|
|
18
|
+
::
|
|
57
19
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"""
|
|
61
|
-
Use this class has the base for your models,
|
|
62
|
-
it will define your table names automatically
|
|
63
|
-
MyModel will be called my_model on the database.
|
|
20
|
+
from sqlalchemy import Integer, String
|
|
21
|
+
from flask_appbuilder import Model
|
|
64
22
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
from flask_appbuilder import Model
|
|
69
|
-
|
|
70
|
-
class MyModel(Model):
|
|
71
|
-
id = Column(Integer, primary_key=True)
|
|
72
|
-
name = Column(String(50), unique = True, nullable=False)
|
|
23
|
+
class MyModel(Model):
|
|
24
|
+
id = Column(Integer, primary_key=True)
|
|
25
|
+
name = Column(String(50), unique = True, nullable=False)
|
|
73
26
|
|
|
74
27
|
"""
|
|
75
28
|
|
|
76
29
|
__table_args__ = {"extend_existing": True}
|
|
77
30
|
|
|
78
|
-
def to_json(self):
|
|
79
|
-
result =
|
|
31
|
+
def to_json(self) -> dict[str, Any]:
|
|
32
|
+
result = {}
|
|
80
33
|
for key in self.__mapper__.c.keys():
|
|
81
34
|
col = getattr(self, key)
|
|
82
|
-
if isinstance(col, datetime.datetime
|
|
35
|
+
if isinstance(col, (datetime.datetime, datetime.date)):
|
|
83
36
|
col = col.isoformat()
|
|
84
37
|
result[key] = col
|
|
85
38
|
return result
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"""
|
|
89
|
-
This is for retro compatibility
|
|
90
|
-
"""
|
|
91
|
-
Base = Model
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
4
|
+
from sqlalchemy.engine import Connection, Engine
|
|
5
|
+
from sqlalchemy.sql.schema import Table
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SQLA(SQLAlchemy):
|
|
9
|
+
"""
|
|
10
|
+
This is a child class of flask_SQLAlchemy
|
|
11
|
+
It's purpose is to override the declarative base of the original
|
|
12
|
+
package. So that it is bound to F.A.B. Model class allowing the dev
|
|
13
|
+
to be in the same namespace of the security tables (and others)
|
|
14
|
+
and can use AuditMixin class alike.
|
|
15
|
+
|
|
16
|
+
Configure just like flask_sqlalchemy SQLAlchemy
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def get_tables_for_bind(bind: Engine | Connection) -> list[Table]:
|
|
21
|
+
from sqlalchemy import inspect
|
|
22
|
+
|
|
23
|
+
inspector = inspect(bind)
|
|
24
|
+
return inspector.get_table_names()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from flask_sqlalchemy import (
|
|
6
|
+
_QueryProperty,
|
|
7
|
+
DefaultMeta,
|
|
8
|
+
get_state,
|
|
9
|
+
SessionBase,
|
|
10
|
+
SignallingSession,
|
|
11
|
+
SQLAlchemy,
|
|
12
|
+
)
|
|
13
|
+
from sqlalchemy import orm
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from sqlalchemy.ext.declarative import as_declarative
|
|
17
|
+
except ImportError:
|
|
18
|
+
from sqlalchemy.ext.declarative.api import as_declarative
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from sqlalchemy.orm.util import identity_key # noqa
|
|
22
|
+
|
|
23
|
+
has_identity_key = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
has_identity_key = False
|
|
26
|
+
|
|
27
|
+
log = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_camelcase_re = re.compile(r"([A-Z]+)(?=[a-z0-9])")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CustomSignallingSession(SignallingSession):
|
|
33
|
+
"""
|
|
34
|
+
Custom Signaling Session to support SQLALchemy>=1.4 with flask-sqlalchemy 2.X
|
|
35
|
+
https://github.com/pallets/flask-sqlalchemy/issues/953
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def get_bind(self, mapper=None, *args, **kwargs):
|
|
39
|
+
"""Return the engine or connection for a given model or
|
|
40
|
+
table, using the ``__bind_key__`` if it is set.
|
|
41
|
+
|
|
42
|
+
Patch from https://github.com/pallets/flask-sqlalchemy/pull/1001
|
|
43
|
+
"""
|
|
44
|
+
# mapper is None if someone tries to just get a connection
|
|
45
|
+
if mapper is not None:
|
|
46
|
+
try:
|
|
47
|
+
# SA >= 1.3
|
|
48
|
+
persist_selectable = mapper.persist_selectable
|
|
49
|
+
except AttributeError:
|
|
50
|
+
# SA < 1.3
|
|
51
|
+
persist_selectable = mapper.mapped_table
|
|
52
|
+
info = getattr(persist_selectable, "info", {})
|
|
53
|
+
bind_key = info.get("bind_key")
|
|
54
|
+
if bind_key is not None:
|
|
55
|
+
state = get_state(self.app)
|
|
56
|
+
return state.db.get_engine(self.app, bind=bind_key)
|
|
57
|
+
return SessionBase.get_bind(self, mapper, *args, **kwargs)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class SQLA(SQLAlchemy):
|
|
61
|
+
"""
|
|
62
|
+
This is a child class of flask_SQLAlchemy
|
|
63
|
+
It's purpose is to override the declarative base of the original
|
|
64
|
+
package. So that it is bound to F.A.B. Model class allowing the dev
|
|
65
|
+
to be in the same namespace of the security tables (and others)
|
|
66
|
+
and can use AuditMixin class alike.
|
|
67
|
+
|
|
68
|
+
Use it and configure it just like flask_SQLAlchemy
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def make_declarative_base(self, model, metadata=None):
|
|
72
|
+
base = Model
|
|
73
|
+
base.query = _QueryProperty(self)
|
|
74
|
+
return base
|
|
75
|
+
|
|
76
|
+
def get_tables_for_bind(self, bind=None):
|
|
77
|
+
"""Returns a list of all tables relevant for a bind."""
|
|
78
|
+
result = []
|
|
79
|
+
tables = Model.metadata.tables
|
|
80
|
+
for key in tables:
|
|
81
|
+
if tables[key].info.get("bind_key") == bind:
|
|
82
|
+
result.append(tables[key])
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
def create_session(self, options):
|
|
86
|
+
"""
|
|
87
|
+
Custom Session factory to support SQLALchemy>=1.4 with flask-sqlalchemy 2.X
|
|
88
|
+
|
|
89
|
+
https://github.com/pallets/flask-sqlalchemy/issues/953
|
|
90
|
+
|
|
91
|
+
:param options: dict of keyword arguments passed to session class
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
return orm.sessionmaker(class_=CustomSignallingSession, db=self, **options)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class ModelDeclarativeMeta(DefaultMeta):
|
|
98
|
+
"""
|
|
99
|
+
Base Model declarative meta for all Models definitions.
|
|
100
|
+
Setups bind_keys to support multiple databases.
|
|
101
|
+
Setup the table name based on the class camelcase name.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@as_declarative(name="Model", metaclass=ModelDeclarativeMeta)
|
|
106
|
+
class Model(object):
|
|
107
|
+
"""
|
|
108
|
+
Use this class has the base for your models,
|
|
109
|
+
it will define your table names automatically
|
|
110
|
+
MyModel will be called my_model on the database.
|
|
111
|
+
|
|
112
|
+
::
|
|
113
|
+
|
|
114
|
+
from sqlalchemy import Integer, String
|
|
115
|
+
from flask_appbuilder import Model
|
|
116
|
+
|
|
117
|
+
class MyModel(Model):
|
|
118
|
+
id = Column(Integer, primary_key=True)
|
|
119
|
+
name = Column(String(50), unique = True, nullable=False)
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
__table_args__ = {"extend_existing": True}
|
|
124
|
+
|
|
125
|
+
def to_json(self):
|
|
126
|
+
result = dict()
|
|
127
|
+
for key in self.__mapper__.c.keys():
|
|
128
|
+
col = getattr(self, key)
|
|
129
|
+
if isinstance(col, datetime.datetime) or isinstance(col, datetime.date):
|
|
130
|
+
col = col.isoformat()
|
|
131
|
+
result[key] = col
|
|
132
|
+
return result
|