0
# Python Utilities
1
2
Helper functions for form field detection, table title generation, template error handling, and custom field components that extend Bootstrap-Flask functionality.
3
4
## Capabilities
5
6
### Form Field Detection
7
8
Utility function for identifying hidden form fields in templates.
9
10
```python { .api }
11
def is_hidden_field_filter(field) -> bool:
12
"""
13
Check if a WTForms field is a hidden field.
14
15
Args:
16
field: WTForms field object
17
18
Returns:
19
bool: True if field is an instance of HiddenField, False otherwise
20
21
Usage:
22
- Used as Jinja filter: bootstrap_is_hidden_field
23
- Registered globally in Jinja environment
24
- Helps templates conditionally render hidden fields
25
26
Example:
27
{% for field in form %}
28
{% if not bootstrap_is_hidden_field(field) %}
29
{{ render_field(field) }}
30
{% endif %}
31
{% endfor %}
32
"""
33
```
34
35
### Table Title Generation
36
37
Automatically generate human-readable table column titles from SQLAlchemy model metadata.
38
39
```python { .api }
40
def get_table_titles(data, primary_key, primary_key_title) -> list:
41
"""
42
Detect and build table titles from SQLAlchemy ORM objects.
43
44
Args:
45
data (list): List of SQLAlchemy model instances
46
primary_key (str): Primary key field name
47
primary_key_title (str): Display title for primary key column
48
49
Returns:
50
list: List of (field_name, display_title) tuples
51
52
Behavior:
53
- Extracts column names from first object's __table__.columns
54
- Converts snake_case to Title Case (user_name → User Name)
55
- Excludes fields starting with underscore (private fields)
56
- Sets primary key column to specified title
57
- Returns empty list if data is empty
58
59
Requirements:
60
- Only works with SQLAlchemy ORM objects
61
- Objects must have __table__ attribute with columns
62
63
Example:
64
User model columns: id, first_name, last_name, email, _password_hash
65
Returns: [('id', 'User ID'), ('first_name', 'First Name'),
66
('last_name', 'Last Name'), ('email', 'Email')]
67
"""
68
```
69
70
### Template Error Handling
71
72
Helper function for raising runtime errors from within Jinja templates.
73
74
```python { .api }
75
def raise_helper(message) -> None:
76
"""
77
Raise a RuntimeError from within templates.
78
79
Args:
80
message (str): Error message to display
81
82
Raises:
83
RuntimeError: Always raises with provided message
84
85
Usage:
86
- Registered as 'raise' in Jinja globals
87
- Allows templates to throw errors for debugging
88
- Useful for template development and error handling
89
90
Template Usage:
91
{% if not required_variable %}
92
{{ raise('Required variable is missing') }}
93
{% endif %}
94
"""
95
```
96
97
### Switch Field Component
98
99
Custom WTForms field that renders as a Bootstrap switch instead of a checkbox.
100
101
```python { .api }
102
class SwitchField(BooleanField):
103
"""
104
A wrapper field for BooleanField that renders as a Bootstrap switch.
105
106
Inherits from WTForms BooleanField with identical functionality
107
but renders with Bootstrap switch styling instead of checkbox.
108
109
Attributes:
110
All BooleanField attributes and methods available
111
112
Bootstrap Versions:
113
- Bootstrap 4: Uses custom-switch classes
114
- Bootstrap 5: Uses form-switch classes
115
"""
116
117
def __init__(self, label=None, **kwargs):
118
"""
119
Initialize switch field.
120
121
Args:
122
label (str, optional): Field label text
123
**kwargs: Additional arguments passed to BooleanField
124
125
Supported kwargs:
126
- validators: List of validation functions
127
- default: Default value (True/False)
128
- description: Help text for field
129
- render_kw: Additional HTML attributes
130
"""
131
```
132
133
### Global Template Variables
134
135
Bootstrap-Flask registers several Python utilities as Jinja global variables during extension initialization.
136
137
```python { .api }
138
# Available in all templates after Bootstrap extension initialization
139
bootstrap_is_hidden_field # is_hidden_field_filter function
140
get_table_titles # Table title generation function
141
warn # warnings.warn function for deprecation notices
142
raise # raise_helper function for template errors
143
bootstrap # Extension instance with load_css(), load_js(), etc.
144
```
145
146
## Usage Examples
147
148
### Hidden Field Detection
149
150
```python
151
# Form definition
152
class ContactForm(FlaskForm):
153
name = StringField('Name', validators=[DataRequired()])
154
email = StringField('Email', validators=[DataRequired(), Email()])
155
csrf_token = HiddenField() # CSRF protection
156
honeypot = HiddenField() # Bot detection
157
message = TextAreaField('Message', validators=[DataRequired()])
158
submit = SubmitField('Send Message')
159
```
160
161
```html
162
<!-- Template - render only visible fields -->
163
<form method="post">
164
{% for field in form %}
165
{% if not bootstrap_is_hidden_field(field) and field.type != 'SubmitField' %}
166
<div class="mb-3">
167
{{ field.label(class="form-label") }}
168
{{ field(class="form-control") }}
169
{% if field.errors %}
170
{% for error in field.errors %}
171
<div class="text-danger">{{ error }}</div>
172
{% endfor %}
173
{% endif %}
174
</div>
175
{% endif %}
176
{% endfor %}
177
178
<!-- Render hidden fields separately -->
179
{% for field in form %}
180
{% if bootstrap_is_hidden_field(field) %}
181
{{ field() }}
182
{% endif %}
183
{% endfor %}
184
185
{{ form.submit(class="btn btn-primary") }}
186
</form>
187
```
188
189
### Automatic Table Titles
190
191
```python
192
# SQLAlchemy model
193
class Product(db.Model):
194
id = db.Column(db.Integer, primary_key=True)
195
product_name = db.Column(db.String(100), nullable=False)
196
unit_price = db.Column(db.Numeric(10, 2))
197
in_stock = db.Column(db.Boolean, default=True)
198
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
199
created_at = db.Column(db.DateTime, default=datetime.utcnow)
200
_internal_code = db.Column(db.String(50)) # Excluded (starts with _)
201
202
# View function
203
@app.route('/products')
204
def products():
205
products = Product.query.all()
206
207
# Automatically generate titles
208
titles = get_table_titles(products, 'id', 'Product ID')
209
# Returns: [('id', 'Product ID'), ('product_name', 'Product Name'),
210
# ('unit_price', 'Unit Price'), ('in_stock', 'In Stock'),
211
# ('category_id', 'Category Id'), ('created_at', 'Created At')]
212
213
return render_template('products.html', products=products, titles=titles)
214
```
215
216
```html
217
<!-- Template with auto-generated titles -->
218
{% from 'base/table.html' import render_table %}
219
220
{{ render_table(products, titles=titles) }}
221
```
222
223
### Custom Table Titles
224
225
```python
226
# Override auto-generated titles with custom ones
227
@app.route('/products')
228
def products():
229
products = Product.query.all()
230
231
# Custom titles for better display
232
titles = [
233
('id', '#'),
234
('product_name', 'Product'),
235
('unit_price', 'Price ($)'),
236
('in_stock', 'Available'),
237
('created_at', 'Date Added')
238
]
239
240
return render_template('products.html', products=products, titles=titles)
241
```
242
243
### Switch Field Usage
244
245
```python
246
# Form with switch fields
247
class UserPreferencesForm(FlaskForm):
248
email_notifications = SwitchField('Email Notifications')
249
sms_notifications = SwitchField('SMS Notifications')
250
dark_mode = SwitchField('Dark Mode')
251
auto_save = SwitchField('Auto-save Documents')
252
public_profile = SwitchField('Public Profile')
253
submit = SubmitField('Save Preferences')
254
```
255
256
```html
257
<!-- Template rendering switches -->
258
{% from 'bootstrap5/form.html' import render_field %}
259
260
<form method="post">
261
{{ form.hidden_tag() }}
262
263
<div class="mb-4">
264
<h5>Notification Settings</h5>
265
{{ render_field(form.email_notifications) }}
266
{{ render_field(form.sms_notifications) }}
267
</div>
268
269
<div class="mb-4">
270
<h5>Interface Settings</h5>
271
{{ render_field(form.dark_mode) }}
272
{{ render_field(form.auto_save) }}
273
</div>
274
275
<div class="mb-4">
276
<h5>Privacy Settings</h5>
277
{{ render_field(form.public_profile) }}
278
</div>
279
280
{{ render_field(form.submit) }}
281
</form>
282
```
283
284
### Template Error Handling
285
286
```html
287
<!-- Debug template with error checking -->
288
{% if not user %}
289
{{ raise('User object is required but not provided') }}
290
{% endif %}
291
292
{% if not user.permissions %}
293
{{ raise('User permissions not loaded') }}
294
{% endif %}
295
296
<!-- Development mode template validation -->
297
{% if config.DEBUG %}
298
{% if not posts %}
299
{{ raise('Posts data missing in template context') }}
300
{% endif %}
301
302
{% if posts|length == 0 %}
303
<!-- This won't raise an error, just show message -->
304
<p>No posts available</p>
305
{% endif %}
306
{% endif %}
307
```
308
309
### Complex Field Detection
310
311
```python
312
# Custom form field detection logic
313
class AdvancedForm(FlaskForm):
314
# Regular fields
315
title = StringField('Title')
316
content = TextAreaField('Content')
317
318
# Hidden fields
319
csrf_token = HiddenField()
320
user_id = HiddenField()
321
322
# Special fields
323
remember_me = BooleanField('Remember Me')
324
auto_publish = SwitchField('Auto-publish')
325
326
submit = SubmitField('Save')
327
```
328
329
```html
330
<!-- Template with advanced field handling -->
331
<form method="post">
332
<!-- Hidden fields first -->
333
{% for field in form if bootstrap_is_hidden_field(field) %}
334
{{ field() }}
335
{% endfor %}
336
337
<!-- Regular input fields -->
338
{% for field in form if not bootstrap_is_hidden_field(field) and field.type not in ['BooleanField', 'SwitchField', 'SubmitField'] %}
339
{{ render_field(field) }}
340
{% endfor %}
341
342
<!-- Boolean and switch fields in a group -->
343
<div class="mb-3">
344
<label class="form-label">Options</label>
345
{% for field in form if field.type in ['BooleanField', 'SwitchField'] %}
346
{{ render_field(field) }}
347
{% endfor %}
348
</div>
349
350
<!-- Submit button -->
351
{% for field in form if field.type == 'SubmitField' %}
352
{{ render_field(field) }}
353
{% endfor %}
354
</form>
355
```
356
357
### Utility Function Chaining
358
359
```python
360
# View combining multiple utilities
361
@app.route('/admin/users')
362
def admin_users():
363
users = User.query.all()
364
365
# Generate titles and customize
366
titles = get_table_titles(users, 'id', '#')
367
368
# Customize specific titles
369
title_dict = dict(titles)
370
title_dict['is_active'] = 'Status'
371
title_dict['last_login'] = 'Last Seen'
372
titles = list(title_dict.items())
373
374
return render_template('admin/users.html', users=users, titles=titles)
375
```
376
377
### Integration with Flask-Admin
378
379
```python
380
# Using utilities with Flask-Admin
381
from flask_admin.contrib.sqla import ModelView
382
383
class UserAdmin(ModelView):
384
def on_model_change(self, form, model, is_created):
385
# Use raise_helper for validation in admin
386
if not model.email:
387
raise_helper('Email is required for user accounts')
388
389
super().on_model_change(form, model, is_created)
390
391
def scaffold_list_columns(self):
392
# Use get_table_titles for admin list view
393
columns = super().scaffold_list_columns()
394
# Customize column display based on auto-generated titles
395
return columns
396
```
397
398
### Form Validation with Utilities
399
400
```python
401
# Custom validator using utilities
402
def validate_required_fields(form):
403
"""Validate that all non-hidden fields have values"""
404
errors = []
405
406
for field in form:
407
if not is_hidden_field_filter(field) and hasattr(field, 'data'):
408
if field.data is None or field.data == '':
409
errors.append(f'{field.label.text} is required')
410
411
return errors
412
413
# Usage in view
414
@app.route('/submit', methods=['POST'])
415
def submit_form():
416
form = MyForm()
417
418
if form.validate_on_submit():
419
# Additional validation
420
validation_errors = validate_required_fields(form)
421
if validation_errors:
422
for error in validation_errors:
423
flash(error, 'error')
424
return render_template('form.html', form=form)
425
426
# Process form
427
return redirect(url_for('success'))
428
429
return render_template('form.html', form=form)
430
```
431
432
## Best Practices
433
434
### Field Detection
435
- Use `bootstrap_is_hidden_field` consistently for hidden field detection
436
- Always render hidden fields separately from visible fields
437
- Include CSRF tokens and other security fields as hidden
438
439
### Table Titles
440
- Use `get_table_titles` for initial title generation
441
- Customize generated titles for better user experience
442
- Consider internationalization when overriding titles
443
444
### Switch Fields
445
- Use for boolean settings and preferences
446
- Prefer switches over checkboxes for on/off toggles
447
- Group related switches together in forms
448
449
### Error Handling
450
- Use `raise_helper` for development debugging only
451
- Remove debug raises before production deployment
452
- Consider logging instead of raising in production templates