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/baseviews.py
CHANGED
|
@@ -3,6 +3,7 @@ from inspect import isclass
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import re
|
|
6
|
+
from typing import List, Optional, TYPE_CHECKING
|
|
6
7
|
|
|
7
8
|
from flask import (
|
|
8
9
|
abort,
|
|
@@ -14,31 +15,39 @@ from flask import (
|
|
|
14
15
|
session,
|
|
15
16
|
url_for,
|
|
16
17
|
)
|
|
17
|
-
|
|
18
|
-
from .
|
|
19
|
-
from .
|
|
20
|
-
from .
|
|
21
|
-
from .
|
|
22
|
-
|
|
18
|
+
from flask_appbuilder._compat import as_unicode
|
|
19
|
+
from flask_appbuilder.actions import ActionItem
|
|
20
|
+
from flask_appbuilder.const import PERMISSION_PREFIX
|
|
21
|
+
from flask_appbuilder.forms import GeneralModelConverter
|
|
22
|
+
from flask_appbuilder.hooks import (
|
|
23
|
+
get_before_request_hooks,
|
|
24
|
+
wrap_route_handler_with_hooks,
|
|
25
|
+
)
|
|
26
|
+
from flask_appbuilder.urltools import (
|
|
23
27
|
get_filter_args,
|
|
24
28
|
get_order_args,
|
|
25
29
|
get_page_args,
|
|
26
30
|
get_page_size_args,
|
|
27
31
|
Stack,
|
|
28
32
|
)
|
|
29
|
-
from .widgets import FormWidget, ListWidget, SearchWidget, ShowWidget
|
|
33
|
+
from flask_appbuilder.widgets import FormWidget, ListWidget, SearchWidget, ShowWidget
|
|
34
|
+
from flask_babel import lazy_gettext
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from flask_appbuilder.base import AppBuilder
|
|
38
|
+
|
|
30
39
|
|
|
31
40
|
log = logging.getLogger(__name__)
|
|
32
41
|
|
|
33
42
|
|
|
34
43
|
def expose(url="/", methods=("GET",)):
|
|
35
44
|
"""
|
|
36
|
-
|
|
45
|
+
Use this decorator to expose views on your view classes.
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
:param url:
|
|
48
|
+
Relative URL for the view
|
|
49
|
+
:param methods:
|
|
50
|
+
Allowed HTTP methods. By default only GET is allowed.
|
|
42
51
|
"""
|
|
43
52
|
|
|
44
53
|
def wrap(f):
|
|
@@ -64,14 +73,43 @@ def expose_api(name="", url="", methods=("GET",), description=""):
|
|
|
64
73
|
return wrap
|
|
65
74
|
|
|
66
75
|
|
|
67
|
-
class
|
|
76
|
+
class AbstractViewApi:
|
|
77
|
+
appbuilder: "AppBuilder"
|
|
78
|
+
base_permissions: Optional[List[str]]
|
|
79
|
+
class_permission_name: Optional[str]
|
|
80
|
+
endpoint: Optional[str]
|
|
81
|
+
default_view: str
|
|
82
|
+
|
|
83
|
+
def create_blueprint(
|
|
84
|
+
self,
|
|
85
|
+
appbuilder: "AppBuilder",
|
|
86
|
+
endpoint: Optional[str] = None,
|
|
87
|
+
static_folder: Optional[str] = None,
|
|
88
|
+
):
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def get_uninit_inner_views(self):
|
|
92
|
+
"""
|
|
93
|
+
Will return a list with views that need to be initialized.
|
|
94
|
+
Normally related_views from ModelView
|
|
95
|
+
"""
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
def get_init_inner_views(self):
|
|
99
|
+
"""
|
|
100
|
+
Sets initialized inner views
|
|
101
|
+
"""
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class BaseView(AbstractViewApi):
|
|
68
106
|
"""
|
|
69
|
-
|
|
70
|
-
|
|
107
|
+
All views inherit from this class.
|
|
108
|
+
it's constructor will register your exposed urls on flask as a Blueprint.
|
|
71
109
|
|
|
72
|
-
|
|
110
|
+
This class does not expose any urls, but provides a common base for all views.
|
|
73
111
|
|
|
74
|
-
|
|
112
|
+
Extend this class if you want to expose methods for your own templates
|
|
75
113
|
"""
|
|
76
114
|
|
|
77
115
|
appbuilder = None
|
|
@@ -145,16 +183,28 @@ class BaseView(object):
|
|
|
145
183
|
default_view = "list"
|
|
146
184
|
""" the default view for this BaseView, to be used with url_for (method name) """
|
|
147
185
|
extra_args = None
|
|
148
|
-
|
|
149
186
|
""" dictionary for injecting extra arguments into template """
|
|
187
|
+
|
|
188
|
+
limits = None
|
|
189
|
+
"""
|
|
190
|
+
List of limits for this view.
|
|
191
|
+
|
|
192
|
+
Use it like this if you want to restrict the rate of requests to a view:
|
|
193
|
+
|
|
194
|
+
class MyView(ModelView):
|
|
195
|
+
limits = [Limit("2 per 5 second")]
|
|
196
|
+
|
|
197
|
+
or use the decorator @limit.
|
|
198
|
+
"""
|
|
199
|
+
|
|
150
200
|
_apis = None
|
|
151
201
|
|
|
152
202
|
def __init__(self):
|
|
153
203
|
"""
|
|
154
|
-
|
|
155
|
-
|
|
204
|
+
Initialization of base permissions
|
|
205
|
+
based on exposed methods and actions
|
|
156
206
|
|
|
157
|
-
|
|
207
|
+
Initialization of extra args
|
|
158
208
|
"""
|
|
159
209
|
# Init class permission override attrs
|
|
160
210
|
if not self.previous_class_permission_name and self.class_permission_name:
|
|
@@ -176,6 +226,9 @@ class BaseView(object):
|
|
|
176
226
|
self.base_permissions = set()
|
|
177
227
|
is_add_base_permissions = True
|
|
178
228
|
|
|
229
|
+
if self.limits is None:
|
|
230
|
+
self.limits = []
|
|
231
|
+
|
|
179
232
|
for attr_name in dir(self):
|
|
180
233
|
# If include_route_methods is not None white list
|
|
181
234
|
if (
|
|
@@ -203,19 +256,21 @@ class BaseView(object):
|
|
|
203
256
|
_extra = getattr(getattr(self, attr_name), "_extra")
|
|
204
257
|
for key in _extra:
|
|
205
258
|
self._apis[key] = _extra[key]
|
|
259
|
+
if hasattr(getattr(self, attr_name), "_limit"):
|
|
260
|
+
self.limits.append(getattr(getattr(self, attr_name), "_limit"))
|
|
206
261
|
|
|
207
262
|
def create_blueprint(self, appbuilder, endpoint=None, static_folder=None):
|
|
208
263
|
"""
|
|
209
|
-
|
|
264
|
+
Create Flask blueprint. You will generally not use it
|
|
210
265
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
266
|
+
:param appbuilder:
|
|
267
|
+
the AppBuilder object
|
|
268
|
+
:param endpoint:
|
|
269
|
+
endpoint override for this blueprint,
|
|
270
|
+
will assume class name if not provided
|
|
271
|
+
:param static_folder:
|
|
272
|
+
the relative override for static folder,
|
|
273
|
+
if omitted application will use the appbuilder static
|
|
219
274
|
"""
|
|
220
275
|
# Store appbuilder instance
|
|
221
276
|
self.appbuilder = appbuilder
|
|
@@ -247,6 +302,7 @@ class BaseView(object):
|
|
|
247
302
|
return self.blueprint
|
|
248
303
|
|
|
249
304
|
def _register_urls(self):
|
|
305
|
+
before_request_hooks = get_before_request_hooks(self)
|
|
250
306
|
for attr_name in dir(self):
|
|
251
307
|
if (
|
|
252
308
|
self.include_route_methods is not None
|
|
@@ -254,59 +310,69 @@ class BaseView(object):
|
|
|
254
310
|
):
|
|
255
311
|
continue
|
|
256
312
|
if attr_name in self.exclude_route_methods:
|
|
257
|
-
log.
|
|
258
|
-
|
|
259
|
-
|
|
313
|
+
log.debug(
|
|
314
|
+
"Not registering route for method %s.%s",
|
|
315
|
+
self.__class__.__name__,
|
|
316
|
+
attr_name,
|
|
260
317
|
)
|
|
261
318
|
continue
|
|
262
319
|
attr = getattr(self, attr_name)
|
|
263
320
|
if hasattr(attr, "_urls"):
|
|
264
321
|
for url, methods in attr._urls:
|
|
265
|
-
log.
|
|
266
|
-
|
|
322
|
+
log.debug(
|
|
323
|
+
"Registering route %s%s %s",
|
|
324
|
+
self.blueprint.url_prefix,
|
|
325
|
+
url,
|
|
326
|
+
methods,
|
|
327
|
+
)
|
|
328
|
+
route_handler = wrap_route_handler_with_hooks(
|
|
329
|
+
attr_name, attr, before_request_hooks
|
|
330
|
+
)
|
|
331
|
+
self.blueprint.add_url_rule(
|
|
332
|
+
url, attr_name, route_handler, methods=methods
|
|
267
333
|
)
|
|
268
|
-
self.blueprint.add_url_rule(url, attr_name, attr, methods=methods)
|
|
269
334
|
|
|
270
335
|
def render_template(self, template, **kwargs):
|
|
271
336
|
"""
|
|
272
|
-
|
|
273
|
-
|
|
337
|
+
Use this method on your own endpoints, will pass the extra_args
|
|
338
|
+
to the templates.
|
|
274
339
|
|
|
275
|
-
|
|
276
|
-
|
|
340
|
+
:param template: The template relative path
|
|
341
|
+
:param kwargs: arguments to be passed to the template
|
|
277
342
|
"""
|
|
278
343
|
kwargs["base_template"] = self.appbuilder.base_template
|
|
279
344
|
kwargs["appbuilder"] = self.appbuilder
|
|
345
|
+
kwargs["current_app"] = current_app
|
|
280
346
|
return render_template(
|
|
281
347
|
template, **dict(list(kwargs.items()) + list(self.extra_args.items()))
|
|
282
348
|
)
|
|
283
349
|
|
|
284
350
|
def _prettify_name(self, name):
|
|
285
351
|
"""
|
|
286
|
-
|
|
352
|
+
Prettify pythonic variable name.
|
|
287
353
|
|
|
288
|
-
|
|
354
|
+
For example, 'HelloWorld' will be converted to 'Hello World'
|
|
289
355
|
|
|
290
|
-
|
|
291
|
-
|
|
356
|
+
:param name:
|
|
357
|
+
Name to prettify.
|
|
292
358
|
"""
|
|
293
359
|
return re.sub(r"(?<=.)([A-Z])", r" \1", name)
|
|
294
360
|
|
|
295
361
|
def _prettify_column(self, name):
|
|
296
362
|
"""
|
|
297
|
-
|
|
363
|
+
Prettify pythonic variable name.
|
|
298
364
|
|
|
299
|
-
|
|
365
|
+
For example, 'hello_world' will be converted to 'Hello World'
|
|
300
366
|
|
|
301
|
-
|
|
302
|
-
|
|
367
|
+
:param name:
|
|
368
|
+
Name to prettify.
|
|
303
369
|
"""
|
|
304
370
|
return re.sub("[._]", " ", name).title()
|
|
305
371
|
|
|
306
372
|
def update_redirect(self):
|
|
307
373
|
"""
|
|
308
|
-
|
|
309
|
-
|
|
374
|
+
Call it on your own endpoint's to update the back history navigation.
|
|
375
|
+
If you bypass it, the next submit or back will go over it.
|
|
310
376
|
"""
|
|
311
377
|
page_history = Stack(session.get("page_history", []))
|
|
312
378
|
page_history.push(request.url)
|
|
@@ -314,7 +380,7 @@ class BaseView(object):
|
|
|
314
380
|
|
|
315
381
|
def get_redirect(self):
|
|
316
382
|
"""
|
|
317
|
-
|
|
383
|
+
Returns the previous url.
|
|
318
384
|
"""
|
|
319
385
|
index_url = self.appbuilder.get_url_for_index
|
|
320
386
|
page_history = Stack(session.get("page_history", []))
|
|
@@ -328,26 +394,25 @@ class BaseView(object):
|
|
|
328
394
|
@classmethod
|
|
329
395
|
def get_default_url(cls, **kwargs):
|
|
330
396
|
"""
|
|
331
|
-
|
|
397
|
+
Returns the url for this class default endpoint
|
|
332
398
|
"""
|
|
333
399
|
return url_for(cls.__name__ + "." + cls.default_view, **kwargs)
|
|
334
400
|
|
|
335
401
|
def get_uninit_inner_views(self):
|
|
336
402
|
"""
|
|
337
|
-
|
|
338
|
-
|
|
403
|
+
Will return a list with views that need to be initialized.
|
|
404
|
+
Normally related_views from ModelView
|
|
339
405
|
"""
|
|
340
406
|
return []
|
|
341
407
|
|
|
342
|
-
def get_init_inner_views(self
|
|
408
|
+
def get_init_inner_views(self):
|
|
343
409
|
"""
|
|
344
|
-
|
|
410
|
+
Sets initialized inner views
|
|
345
411
|
"""
|
|
346
|
-
pass
|
|
347
412
|
|
|
348
413
|
def get_method_permission(self, method_name: str) -> str:
|
|
349
414
|
"""
|
|
350
|
-
|
|
415
|
+
Returns the permission name for a method
|
|
351
416
|
"""
|
|
352
417
|
permission = self.method_permission_name.get(method_name)
|
|
353
418
|
if permission:
|
|
@@ -358,7 +423,7 @@ class BaseView(object):
|
|
|
358
423
|
|
|
359
424
|
class BaseFormView(BaseView):
|
|
360
425
|
"""
|
|
361
|
-
|
|
426
|
+
Base class FormView's
|
|
362
427
|
"""
|
|
363
428
|
|
|
364
429
|
form_template = "appbuilder/general/model/edit.html"
|
|
@@ -392,20 +457,18 @@ class BaseFormView(BaseView):
|
|
|
392
457
|
|
|
393
458
|
def form_get(self, form):
|
|
394
459
|
"""
|
|
395
|
-
|
|
460
|
+
Override this method to implement your form processing
|
|
396
461
|
"""
|
|
397
|
-
pass
|
|
398
462
|
|
|
399
463
|
def form_post(self, form):
|
|
400
464
|
"""
|
|
401
|
-
|
|
465
|
+
Override this method to implement your form processing
|
|
402
466
|
|
|
403
|
-
|
|
467
|
+
:param form: WTForm form
|
|
404
468
|
|
|
405
|
-
|
|
406
|
-
|
|
469
|
+
Return None or a flask response to render
|
|
470
|
+
a custom template or redirect the user
|
|
407
471
|
"""
|
|
408
|
-
pass
|
|
409
472
|
|
|
410
473
|
def _get_edit_widget(self, form=None, exclude_cols=None, widgets=None):
|
|
411
474
|
exclude_cols = exclude_cols or []
|
|
@@ -422,10 +485,10 @@ class BaseFormView(BaseView):
|
|
|
422
485
|
|
|
423
486
|
class BaseModelView(BaseView):
|
|
424
487
|
"""
|
|
425
|
-
|
|
426
|
-
|
|
488
|
+
The base class of ModelView and ChartView, all properties are inherited
|
|
489
|
+
Customize ModelView and ChartView overriding this properties
|
|
427
490
|
|
|
428
|
-
|
|
491
|
+
This class supports all the basics for query
|
|
429
492
|
"""
|
|
430
493
|
|
|
431
494
|
datamodel = None
|
|
@@ -458,7 +521,7 @@ class BaseModelView(BaseView):
|
|
|
458
521
|
search_form_extra_fields = None
|
|
459
522
|
"""
|
|
460
523
|
A dictionary containing column names and a WTForm
|
|
461
|
-
Form fields to be added to the
|
|
524
|
+
Form fields to be added to the search form, these fields do not
|
|
462
525
|
exist on the model itself ex::
|
|
463
526
|
|
|
464
527
|
search_form_extra_fields = {'some_col':BooleanField('Some Col', default=False)}
|
|
@@ -475,7 +538,7 @@ class BaseModelView(BaseView):
|
|
|
475
538
|
|
|
476
539
|
class ContactModelView(ModelView):
|
|
477
540
|
datamodel = SQLAModel(Contact, db.session)
|
|
478
|
-
search_form_query_rel_fields =
|
|
541
|
+
search_form_query_rel_fields = {'group':[['name',FilterStartsWith,'W']]}
|
|
479
542
|
|
|
480
543
|
"""
|
|
481
544
|
|
|
@@ -530,7 +593,7 @@ class BaseModelView(BaseView):
|
|
|
530
593
|
|
|
531
594
|
def __init__(self, **kwargs):
|
|
532
595
|
"""
|
|
533
|
-
|
|
596
|
+
Constructor
|
|
534
597
|
"""
|
|
535
598
|
datamodel = kwargs.get("datamodel", None)
|
|
536
599
|
if datamodel:
|
|
@@ -542,7 +605,7 @@ class BaseModelView(BaseView):
|
|
|
542
605
|
|
|
543
606
|
def _gen_labels_columns(self, list_columns):
|
|
544
607
|
"""
|
|
545
|
-
|
|
608
|
+
Auto generates pretty label_columns from list of columns
|
|
546
609
|
"""
|
|
547
610
|
for col in list_columns:
|
|
548
611
|
if not self.label_columns.get(col):
|
|
@@ -594,7 +657,7 @@ class BaseModelView(BaseView):
|
|
|
594
657
|
|
|
595
658
|
def _label_columns_json(self):
|
|
596
659
|
"""
|
|
597
|
-
|
|
660
|
+
Prepares dict with labels to be JSON serializable
|
|
598
661
|
"""
|
|
599
662
|
ret = {}
|
|
600
663
|
for key, value in list(self.label_columns.items()):
|
|
@@ -604,9 +667,24 @@ class BaseModelView(BaseView):
|
|
|
604
667
|
|
|
605
668
|
class BaseCRUDView(BaseModelView):
|
|
606
669
|
"""
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
"""
|
|
670
|
+
The base class for ModelView, all properties are inherited
|
|
671
|
+
Customize ModelView overriding this properties
|
|
672
|
+
"""
|
|
673
|
+
|
|
674
|
+
""" Messages to display on CRUD Events """
|
|
675
|
+
add_row_message = lazy_gettext("Added Row")
|
|
676
|
+
edit_row_message = lazy_gettext("Changed Row")
|
|
677
|
+
delete_row_message = lazy_gettext("Deleted Row")
|
|
678
|
+
delete_integrity_error_message = lazy_gettext(
|
|
679
|
+
"Associated data exists, please delete them first"
|
|
680
|
+
)
|
|
681
|
+
add_integrity_error_message = lazy_gettext(
|
|
682
|
+
"Integrity error, probably unique constraint"
|
|
683
|
+
)
|
|
684
|
+
edit_integrity_error_message = lazy_gettext(
|
|
685
|
+
"Integrity error, probably unique constraint"
|
|
686
|
+
)
|
|
687
|
+
database_error_message = lazy_gettext("Database Error")
|
|
610
688
|
|
|
611
689
|
related_views = None
|
|
612
690
|
"""
|
|
@@ -821,7 +899,7 @@ class BaseCRUDView(BaseModelView):
|
|
|
821
899
|
|
|
822
900
|
def _init_forms(self):
|
|
823
901
|
"""
|
|
824
|
-
|
|
902
|
+
Init forms for Add and Edit
|
|
825
903
|
"""
|
|
826
904
|
super(BaseCRUDView, self)._init_forms()
|
|
827
905
|
conv = GeneralModelConverter(self.datamodel)
|
|
@@ -846,7 +924,7 @@ class BaseCRUDView(BaseModelView):
|
|
|
846
924
|
|
|
847
925
|
def _init_titles(self):
|
|
848
926
|
"""
|
|
849
|
-
|
|
927
|
+
Init Titles if not defined
|
|
850
928
|
"""
|
|
851
929
|
super(BaseCRUDView, self)._init_titles()
|
|
852
930
|
class_name = self.datamodel.model_name
|
|
@@ -862,7 +940,7 @@ class BaseCRUDView(BaseModelView):
|
|
|
862
940
|
|
|
863
941
|
def _init_properties(self):
|
|
864
942
|
"""
|
|
865
|
-
|
|
943
|
+
Init Properties
|
|
866
944
|
"""
|
|
867
945
|
super(BaseCRUDView, self)._init_properties()
|
|
868
946
|
# Reset init props
|
|
@@ -933,7 +1011,6 @@ class BaseCRUDView(BaseModelView):
|
|
|
933
1011
|
page=None,
|
|
934
1012
|
page_size=None,
|
|
935
1013
|
):
|
|
936
|
-
|
|
937
1014
|
fk = related_view.datamodel.get_related_fk(self.datamodel.obj)
|
|
938
1015
|
filters = related_view.datamodel.get_filters()
|
|
939
1016
|
# Check if it's a many to one model relation
|
|
@@ -955,7 +1032,7 @@ class BaseCRUDView(BaseModelView):
|
|
|
955
1032
|
name = related_view.__name__
|
|
956
1033
|
else:
|
|
957
1034
|
name = related_view.__class__.__name__
|
|
958
|
-
log.error("Can't find relation on related view
|
|
1035
|
+
log.error("Can't find relation on related view %s", name)
|
|
959
1036
|
return None
|
|
960
1037
|
return related_view._get_view_widget(
|
|
961
1038
|
filters=filters,
|
|
@@ -969,9 +1046,9 @@ class BaseCRUDView(BaseModelView):
|
|
|
969
1046
|
self, item, orders=None, pages=None, page_sizes=None, widgets=None, **args
|
|
970
1047
|
):
|
|
971
1048
|
"""
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1049
|
+
:return:
|
|
1050
|
+
Returns a dict with 'related_views' key with a list of
|
|
1051
|
+
Model View widgets
|
|
975
1052
|
"""
|
|
976
1053
|
widgets = widgets or {}
|
|
977
1054
|
widgets["related_views"] = []
|
|
@@ -994,8 +1071,8 @@ class BaseCRUDView(BaseModelView):
|
|
|
994
1071
|
|
|
995
1072
|
def _get_view_widget(self, **kwargs):
|
|
996
1073
|
"""
|
|
997
|
-
|
|
998
|
-
|
|
1074
|
+
:return:
|
|
1075
|
+
Returns a Model View widget
|
|
999
1076
|
"""
|
|
1000
1077
|
return self._get_list_widget(**kwargs).get("list")
|
|
1001
1078
|
|
|
@@ -1008,10 +1085,9 @@ class BaseCRUDView(BaseModelView):
|
|
|
1008
1085
|
page=None,
|
|
1009
1086
|
page_size=None,
|
|
1010
1087
|
widgets=None,
|
|
1011
|
-
**
|
|
1088
|
+
**kwargs,
|
|
1012
1089
|
):
|
|
1013
|
-
|
|
1014
|
-
""" get joined base filter and current active filter for query """
|
|
1090
|
+
"""get joined base filter and current active filter for query"""
|
|
1015
1091
|
widgets = widgets or {}
|
|
1016
1092
|
actions = actions or self.actions
|
|
1017
1093
|
page_size = page_size or self.page_size
|
|
@@ -1043,6 +1119,7 @@ class BaseCRUDView(BaseModelView):
|
|
|
1043
1119
|
actions=actions,
|
|
1044
1120
|
filters=filters,
|
|
1045
1121
|
modelview_name=self.__class__.__name__,
|
|
1122
|
+
**kwargs,
|
|
1046
1123
|
)
|
|
1047
1124
|
return widgets
|
|
1048
1125
|
|
|
@@ -1088,14 +1165,14 @@ class BaseCRUDView(BaseModelView):
|
|
|
1088
1165
|
|
|
1089
1166
|
def get_uninit_inner_views(self):
|
|
1090
1167
|
"""
|
|
1091
|
-
|
|
1092
|
-
|
|
1168
|
+
Will return a list with views that need to be initialized.
|
|
1169
|
+
Normally related_views from ModelView
|
|
1093
1170
|
"""
|
|
1094
1171
|
return self.related_views
|
|
1095
1172
|
|
|
1096
1173
|
def get_init_inner_views(self):
|
|
1097
1174
|
"""
|
|
1098
|
-
|
|
1175
|
+
Get the list of related ModelViews after they have been initialized
|
|
1099
1176
|
"""
|
|
1100
1177
|
return self._related_views
|
|
1101
1178
|
|
|
@@ -1105,10 +1182,10 @@ class BaseCRUDView(BaseModelView):
|
|
|
1105
1182
|
-----------------------------------------------------
|
|
1106
1183
|
"""
|
|
1107
1184
|
|
|
1108
|
-
def _list(self):
|
|
1185
|
+
def _list(self, **kwargs):
|
|
1109
1186
|
"""
|
|
1110
|
-
|
|
1111
|
-
|
|
1187
|
+
list function logic, override to implement different logic
|
|
1188
|
+
returns list and search widget
|
|
1112
1189
|
"""
|
|
1113
1190
|
if get_order_args().get(self.__class__.__name__):
|
|
1114
1191
|
order_column, order_direction = get_order_args().get(
|
|
@@ -1125,6 +1202,7 @@ class BaseCRUDView(BaseModelView):
|
|
|
1125
1202
|
order_direction=order_direction,
|
|
1126
1203
|
page=page,
|
|
1127
1204
|
page_size=page_size,
|
|
1205
|
+
**kwargs,
|
|
1128
1206
|
)
|
|
1129
1207
|
form = self.search_form.refresh()
|
|
1130
1208
|
self.update_redirect()
|
|
@@ -1132,8 +1210,8 @@ class BaseCRUDView(BaseModelView):
|
|
|
1132
1210
|
|
|
1133
1211
|
def _show(self, pk):
|
|
1134
1212
|
"""
|
|
1135
|
-
|
|
1136
|
-
|
|
1213
|
+
show function logic, override to implement different logic
|
|
1214
|
+
returns show and related list widget
|
|
1137
1215
|
"""
|
|
1138
1216
|
pages = get_page_args()
|
|
1139
1217
|
page_sizes = get_page_size_args()
|
|
@@ -1150,11 +1228,11 @@ class BaseCRUDView(BaseModelView):
|
|
|
1150
1228
|
|
|
1151
1229
|
def _add(self):
|
|
1152
1230
|
"""
|
|
1153
|
-
|
|
1154
|
-
|
|
1231
|
+
Add function logic, override to implement different logic
|
|
1232
|
+
returns add widget or None
|
|
1155
1233
|
"""
|
|
1156
1234
|
is_valid_form = True
|
|
1157
|
-
get_filter_args(self._filters)
|
|
1235
|
+
get_filter_args(self._filters, disallow_if_not_in_search=False)
|
|
1158
1236
|
exclude_cols = self._filters.get_relation_cols()
|
|
1159
1237
|
form = self.add_form.refresh()
|
|
1160
1238
|
|
|
@@ -1170,9 +1248,12 @@ class BaseCRUDView(BaseModelView):
|
|
|
1170
1248
|
except Exception as e:
|
|
1171
1249
|
flash(str(e), "danger")
|
|
1172
1250
|
else:
|
|
1173
|
-
|
|
1251
|
+
try:
|
|
1252
|
+
self.datamodel.add(item)
|
|
1174
1253
|
self.post_add(item)
|
|
1175
|
-
|
|
1254
|
+
flash(self.add_row_message, "success")
|
|
1255
|
+
except Exception as e:
|
|
1256
|
+
flash(str(e))
|
|
1176
1257
|
finally:
|
|
1177
1258
|
return None
|
|
1178
1259
|
else:
|
|
@@ -1183,14 +1264,14 @@ class BaseCRUDView(BaseModelView):
|
|
|
1183
1264
|
|
|
1184
1265
|
def _edit(self, pk):
|
|
1185
1266
|
"""
|
|
1186
|
-
|
|
1187
|
-
|
|
1267
|
+
Edit function logic, override to implement different logic
|
|
1268
|
+
returns Edit widget and related list or None
|
|
1188
1269
|
"""
|
|
1189
1270
|
is_valid_form = True
|
|
1190
1271
|
pages = get_page_args()
|
|
1191
1272
|
page_sizes = get_page_size_args()
|
|
1192
1273
|
orders = get_order_args()
|
|
1193
|
-
get_filter_args(self._filters)
|
|
1274
|
+
get_filter_args(self._filters, disallow_if_not_in_search=False)
|
|
1194
1275
|
exclude_cols = self._filters.get_relation_cols()
|
|
1195
1276
|
|
|
1196
1277
|
item = self.datamodel.get(pk, self._base_filters)
|
|
@@ -1214,9 +1295,12 @@ class BaseCRUDView(BaseModelView):
|
|
|
1214
1295
|
except Exception as e:
|
|
1215
1296
|
flash(str(e), "danger")
|
|
1216
1297
|
else:
|
|
1217
|
-
|
|
1298
|
+
try:
|
|
1299
|
+
self.datamodel.edit(item)
|
|
1218
1300
|
self.post_update(item)
|
|
1219
|
-
|
|
1301
|
+
flash(self.edit_row_message, "success")
|
|
1302
|
+
except Exception:
|
|
1303
|
+
flash(self.database_error_message, "danger")
|
|
1220
1304
|
finally:
|
|
1221
1305
|
return None
|
|
1222
1306
|
else:
|
|
@@ -1242,11 +1326,11 @@ class BaseCRUDView(BaseModelView):
|
|
|
1242
1326
|
|
|
1243
1327
|
def _delete(self, pk):
|
|
1244
1328
|
"""
|
|
1245
|
-
|
|
1246
|
-
|
|
1329
|
+
Delete function logic, override to implement different logic
|
|
1330
|
+
deletes the record with primary_key = pk
|
|
1247
1331
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1332
|
+
:param pk:
|
|
1333
|
+
record primary key to delete
|
|
1250
1334
|
"""
|
|
1251
1335
|
item = self.datamodel.get(pk, self._base_filters)
|
|
1252
1336
|
if not item:
|
|
@@ -1256,9 +1340,12 @@ class BaseCRUDView(BaseModelView):
|
|
|
1256
1340
|
except Exception as e:
|
|
1257
1341
|
flash(str(e), "danger")
|
|
1258
1342
|
else:
|
|
1259
|
-
|
|
1343
|
+
try:
|
|
1344
|
+
self.datamodel.delete(item)
|
|
1260
1345
|
self.post_delete(item)
|
|
1261
|
-
|
|
1346
|
+
flash(self.delete_row_message, "success")
|
|
1347
|
+
except Exception as e:
|
|
1348
|
+
flash(str(e))
|
|
1262
1349
|
self.update_redirect()
|
|
1263
1350
|
|
|
1264
1351
|
"""
|
|
@@ -1303,7 +1390,7 @@ class BaseCRUDView(BaseModelView):
|
|
|
1303
1390
|
|
|
1304
1391
|
def _fill_form_exclude_cols(self, exclude_cols, form):
|
|
1305
1392
|
"""
|
|
1306
|
-
|
|
1393
|
+
fill the form with the suppressed cols, generated from exclude_cols
|
|
1307
1394
|
"""
|
|
1308
1395
|
for filter_key in exclude_cols:
|
|
1309
1396
|
filter_value = self._filters.get_filter_value(filter_key)
|
|
@@ -1314,93 +1401,83 @@ class BaseCRUDView(BaseModelView):
|
|
|
1314
1401
|
|
|
1315
1402
|
def is_get_mutation_allowed(self) -> bool:
|
|
1316
1403
|
"""
|
|
1317
|
-
|
|
1318
|
-
|
|
1404
|
+
Check is mutations on HTTP GET methods are allowed.
|
|
1405
|
+
Always called on a request
|
|
1319
1406
|
"""
|
|
1320
1407
|
if current_app.config.get("FAB_ALLOW_GET_UNSAFE_MUTATIONS", False):
|
|
1321
1408
|
return True
|
|
1322
|
-
return not (
|
|
1323
|
-
request.method == "GET" and self.appbuilder.app.extensions.get("csrf")
|
|
1324
|
-
)
|
|
1409
|
+
return not (request.method == "GET" and current_app.extensions.get("csrf"))
|
|
1325
1410
|
|
|
1326
1411
|
def prefill_form(self, form, pk):
|
|
1327
1412
|
"""
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1413
|
+
Override this, will be called only if the current action is rendering
|
|
1414
|
+
an edit form (a GET request), and is used to perform additional action to
|
|
1415
|
+
prefill the form.
|
|
1331
1416
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1417
|
+
This is useful when you have added custom fields that depend on the
|
|
1418
|
+
database contents. Fields that were added by name of a normal column
|
|
1419
|
+
or relationship should work out of the box.
|
|
1335
1420
|
|
|
1336
|
-
|
|
1421
|
+
example::
|
|
1337
1422
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1423
|
+
def prefill_form(self, form, pk):
|
|
1424
|
+
if form.email.data:
|
|
1425
|
+
form.email_confirmation.data = form.email.data
|
|
1341
1426
|
"""
|
|
1342
|
-
pass
|
|
1343
1427
|
|
|
1344
1428
|
def process_form(self, form, is_created):
|
|
1345
1429
|
"""
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1430
|
+
Override this, will be called only if the current action is submitting
|
|
1431
|
+
a create/edit form (a POST request), and is used to perform additional
|
|
1432
|
+
action before the form is used to populate the item.
|
|
1349
1433
|
|
|
1350
|
-
|
|
1434
|
+
By default does nothing.
|
|
1351
1435
|
|
|
1352
|
-
|
|
1436
|
+
example::
|
|
1353
1437
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1438
|
+
def process_form(self, form, is_created):
|
|
1439
|
+
if not form.owner:
|
|
1440
|
+
form.owner.data = 'n/a'
|
|
1357
1441
|
"""
|
|
1358
|
-
pass
|
|
1359
1442
|
|
|
1360
1443
|
def pre_update(self, item):
|
|
1361
1444
|
"""
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1445
|
+
Override this, this method is called before the update takes place.
|
|
1446
|
+
If an exception is raised by this method,
|
|
1447
|
+
the message is shown to the user and the update operation is
|
|
1448
|
+
aborted. Because of this behavior, it can be used as a way to
|
|
1449
|
+
implement more complex logic around updates. For instance
|
|
1450
|
+
allowing only the original creator of the object to update it.
|
|
1368
1451
|
"""
|
|
1369
|
-
pass
|
|
1370
1452
|
|
|
1371
1453
|
def post_update(self, item):
|
|
1372
1454
|
"""
|
|
1373
|
-
|
|
1455
|
+
Override this, will be called after update
|
|
1374
1456
|
"""
|
|
1375
|
-
pass
|
|
1376
1457
|
|
|
1377
1458
|
def pre_add(self, item):
|
|
1378
1459
|
"""
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1460
|
+
Override this, will be called before add.
|
|
1461
|
+
If an exception is raised by this method,
|
|
1462
|
+
the message is shown to the user and the add operation is aborted.
|
|
1382
1463
|
"""
|
|
1383
|
-
pass
|
|
1384
1464
|
|
|
1385
1465
|
def post_add(self, item):
|
|
1386
1466
|
"""
|
|
1387
|
-
|
|
1467
|
+
Override this, will be called after update
|
|
1388
1468
|
"""
|
|
1389
|
-
pass
|
|
1390
1469
|
|
|
1391
1470
|
def pre_delete(self, item):
|
|
1392
1471
|
"""
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1472
|
+
Override this, will be called before delete
|
|
1473
|
+
If an exception is raised by this method,
|
|
1474
|
+
the message is shown to the user and the delete operation is
|
|
1475
|
+
aborted. Because of this behavior, it can be used as a way to
|
|
1476
|
+
implement more complex logic around deletes. For instance
|
|
1477
|
+
allowing only the original creator of the object to delete it.
|
|
1399
1478
|
"""
|
|
1400
|
-
pass
|
|
1401
1479
|
|
|
1402
1480
|
def post_delete(self, item):
|
|
1403
1481
|
"""
|
|
1404
|
-
|
|
1482
|
+
Override this, will be called after delete
|
|
1405
1483
|
"""
|
|
1406
|
-
pass
|