-
-
Notifications
You must be signed in to change notification settings - Fork 189
/
__init__.py
289 lines (234 loc) · 10.6 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
import warnings
from flask import current_app, Blueprint, url_for
from markupsafe import Markup
from wtforms import BooleanField, HiddenField
CDN_BASE = 'https://cdn.jsdelivr.net/npm'
def is_hidden_field_filter(field):
return isinstance(field, HiddenField)
def raise_helper(message):
raise RuntimeError(message)
def get_table_titles(data, primary_key, primary_key_title):
"""Detect and build the table titles tuple from ORM object, currently only support SQLAlchemy.
.. versionadded:: 1.4.0
"""
if not data:
return []
titles = []
for k in data[0].__table__.columns.keys():
if not k.startswith('_'): # pragma: no branch
titles.append((k, k.replace('_', ' ').title()))
titles[0] = (primary_key, primary_key_title)
return titles
class _Bootstrap:
"""
Base extension class for different Bootstrap versions.
.. versionadded:: 2.0.0
"""
bootstrap_version = None
jquery_version = None
popper_version = None
bootstrap_css_integrity = None
bootstrap_js_integrity = None
jquery_integrity = None
popper_integrity = None
static_folder = None
bootstrap_css_filename = 'bootstrap.min.css'
bootstrap_js_filename = 'bootstrap.min.js'
jquery_filename = 'jquery.min.js'
popper_filename = 'popper.min.js'
def __init__(self, app=None):
if app is not None: # pragma: no branch
self.init_app(app)
def init_app(self, app):
if not hasattr(app, 'extensions'):
app.extensions = {} # pragma: no cover
app.extensions['bootstrap'] = self
blueprint = Blueprint('bootstrap', __name__, static_folder=f'static/{self.static_folder}',
static_url_path=f'/bootstrap{app.static_url_path}',
template_folder='templates')
app.register_blueprint(blueprint)
app.jinja_env.globals['bootstrap'] = self
app.jinja_env.globals['bootstrap_is_hidden_field'] = is_hidden_field_filter
app.jinja_env.globals['get_table_titles'] = get_table_titles
app.jinja_env.globals['warn'] = warnings.warn
app.jinja_env.globals['raise'] = raise_helper
app.jinja_env.add_extension('jinja2.ext.do')
# default settings
app.config.setdefault('BOOTSTRAP_SERVE_LOCAL', False)
app.config.setdefault('BOOTSTRAP_BTN_STYLE', 'primary')
app.config.setdefault('BOOTSTRAP_BTN_SIZE', 'md')
app.config.setdefault('BOOTSTRAP_BOOTSWATCH_THEME', None)
app.config.setdefault('BOOTSTRAP_ICON_SIZE', '1em')
app.config.setdefault('BOOTSTRAP_ICON_COLOR', None)
app.config.setdefault('BOOTSTRAP_MSG_CATEGORY', 'primary')
app.config.setdefault('BOOTSTRAP_TABLE_VIEW_TITLE', 'View')
app.config.setdefault('BOOTSTRAP_TABLE_EDIT_TITLE', 'Edit')
app.config.setdefault('BOOTSTRAP_TABLE_DELETE_TITLE', 'Delete')
app.config.setdefault('BOOTSTRAP_TABLE_NEW_TITLE', 'New')
app.config.setdefault('BOOTSTRAP_FORM_GROUP_CLASSES', 'mb-3') # Bootstrap 5 only
app.config.setdefault(
'BOOTSTRAP_FORM_INLINE_CLASSES',
'row row-cols-lg-auto g-3 align-items-center'
) # Bootstrap 5 only
def load_css(self, version=None, bootstrap_sri=None):
"""Load Bootstrap's css resources with given version.
.. versionadded:: 0.1.0
:param version: The version of Bootstrap.
"""
serve_local = current_app.config['BOOTSTRAP_SERVE_LOCAL']
bootswatch_theme = current_app.config['BOOTSTRAP_BOOTSWATCH_THEME']
if version is None:
version = self.bootstrap_version
bootstrap_sri = self._get_sri('bootstrap_css', version, bootstrap_sri)
if serve_local:
if not bootswatch_theme:
base_path = 'css'
else:
base_path = f'css/bootswatch/{bootswatch_theme.lower()}'
boostrap_url = url_for('bootstrap.static', filename=f'{base_path}/{self.bootstrap_css_filename}')
else:
if not bootswatch_theme:
base_path = f'{CDN_BASE}/bootstrap@{version}/dist/css'
else:
base_path = f'{CDN_BASE}/bootswatch@{version}/dist/{bootswatch_theme.lower()}'
boostrap_url = f'{base_path}/{self.bootstrap_css_filename}'
if bootstrap_sri and not bootswatch_theme:
css = f'<link rel="stylesheet" href="{boostrap_url}" integrity="{bootstrap_sri}" crossorigin="anonymous">'
else:
css = f'<link rel="stylesheet" href="{boostrap_url}">'
return Markup(css)
def _get_js_script(self, version, name, sri, nonce):
"""Get <script> tag for JavaScript resources."""
serve_local = current_app.config['BOOTSTRAP_SERVE_LOCAL']
paths = {
'bootstrap': f'js/{self.bootstrap_js_filename}',
'jquery': f'{self.jquery_filename}',
'@popperjs/core': f'umd/{self.popper_filename}',
'popper.js': f'umd/{self.popper_filename}',
}
if serve_local:
url = url_for('bootstrap.static', filename=paths[name])
else:
url = f'{CDN_BASE}/{name}@{version}/dist/{paths[name]}'
nonce_attribute = f' nonce="{nonce}"' if nonce else ''
sri_attributes = f' integrity="{sri}" crossorigin="anonymous"' if sri else ''
return f'<script src="{url}"{sri_attributes}{nonce_attribute}></script>'
def _get_sri(self, name, version, sri):
serve_local = current_app.config['BOOTSTRAP_SERVE_LOCAL']
sris = {
'bootstrap_css': self.bootstrap_css_integrity,
'bootstrap_js': self.bootstrap_js_integrity,
'jquery': self.jquery_integrity,
'popper': self.popper_integrity,
}
versions = {
'bootstrap_css': self.bootstrap_version,
'bootstrap_js': self.bootstrap_version,
'jquery': self.jquery_version,
'popper': self.popper_version
}
if sri is not None:
return sri
if version == versions[name] and serve_local is False:
return sris[name]
return None
def load_js(self, version=None, jquery_version=None, # noqa: C901
popper_version=None, with_jquery=True, with_popper=True,
bootstrap_sri=None, jquery_sri=None, popper_sri=None,
nonce=None):
"""Load Bootstrap and related library's js resources with given version.
.. versionadded:: 0.1.0
:param version: The version of Bootstrap.
:param jquery_version: The version of jQuery (only needed with Bootstrap 4).
:param popper_version: The version of Popper.js.
:param with_jquery: Include jQuery or not (only needed with Bootstrap 4).
:param with_popper: Include Popper.js or not.
:param bootstrap_sri: The integrity attribute value of Bootstrap for SRI
:param jquery_sri: The integrity attribute value of jQuery for SRI
:param popper_sri: The integrity attribute value of Popper.js for SRI
:param nonce: The nonce attribute value for use with strict CSP
"""
if version is None:
version = self.bootstrap_version
if popper_version is None:
popper_version = self.popper_version
bootstrap_sri = self._get_sri('bootstrap_js', version, bootstrap_sri)
popper_sri = self._get_sri('popper', popper_version, popper_sri)
bootstrap = self._get_js_script(version, 'bootstrap', bootstrap_sri, nonce)
popper = self._get_js_script(popper_version, self.popper_name, popper_sri, nonce) if with_popper else ''
if version.startswith('4'):
if jquery_version is None:
jquery_version = self.jquery_version
jquery_sri = self._get_sri('jquery', jquery_version, jquery_sri)
jquery = self._get_js_script(jquery_version, 'jquery', jquery_sri, nonce) if with_jquery else ''
return Markup(f'''{jquery}
{popper}
{bootstrap}''')
return Markup(f'''{popper}
{bootstrap}''')
class Bootstrap4(_Bootstrap):
"""
Extension class for Bootstrap 4.
Initialize the extension::
from flask import Flask
from flask_bootstrap import Bootstrap4
app = Flask(__name__)
bootstrap = Bootstrap4(app)
Or with the application factory::
from flask import Flask
from flask_bootstrap import Bootstrap4
bootstrap = Bootstrap4()
def create_app():
app = Flask(__name__)
bootstrap.init_app(app)
.. versionchanged:: 2.0.0
Move common logic to base class ``_Bootstrap``.
"""
bootstrap_version = '4.6.1'
jquery_version = '3.5.1'
popper_version = '1.16.1'
bootstrap_css_integrity = 'sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn'
bootstrap_js_integrity = 'sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2'
jquery_integrity = 'sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0='
popper_integrity = 'sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN'
popper_name = 'popper.js'
static_folder = 'bootstrap4'
class Bootstrap5(_Bootstrap):
"""
Base class for Bootstrap 5.
Initialize the extension::
from flask import Flask
from flask_bootstrap import Bootstrap5
app = Flask(__name__)
bootstrap = Bootstrap5(app)
Or with the application factory::
from flask import Flask
from flask_bootstrap import Bootstrap5
bootstrap = Bootstrap5()
def create_app():
app = Flask(__name__)
bootstrap.init_app(app)
.. versionadded:: 2.0.0
"""
bootstrap_version = '5.3.2'
popper_version = '2.11.8'
bootstrap_css_integrity = 'sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN'
bootstrap_js_integrity = 'sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+'
popper_integrity = 'sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r'
popper_name = '@popperjs/core'
static_folder = 'bootstrap5'
class Bootstrap(Bootstrap4):
def __init__(self, app=None):
super().__init__(app=app)
warnings.warn(
'For Bootstrap 4, please import and use "Bootstrap4" class, the "Bootstrap" class '
'is deprecated and will be removed in 3.0.',
stacklevel=2
)
class SwitchField(BooleanField):
"""
A wrapper field for ``BooleanField`` that renders as a Bootstrap switch.
.. versionadded:: 2.0.0
"""
def __init__(self, label=None, **kwargs):
super().__init__(label, **kwargs)