🎯 Flask Web Forms: Your Friendly Form Butler
Imagine you’re running a fancy restaurant. Guests fill out order slips (forms), and you need a butler to:
- Hand out the right slips 📝
- Check if orders make sense ✅
- Protect against sneaky imposters 🛡️
Flask-WTF is that butler. Let’s meet him!
🏠 Chapter 1: Meet Flask-WTF (Your Form Butler)
What is Flask-WTF?
Think of Flask-WTF as a helper robot that makes forms easy and safe.
Without it, you’d have to:
- Write lots of boring HTML by hand
- Check every input yourself
- Worry about bad guys tricking your forms
With Flask-WTF, the robot does it all!
Installing Your Butler
pip install flask-wtf
Setting Up (Give Your Butler a Secret Key)
Your butler needs a secret password to protect forms:
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'my-super-secret'
🔑 Why a secret? It’s like a special stamp only YOU know. Stops bad guys from faking forms!
📝 Chapter 2: Creating Forms with WTForms
Your First Form Class
Instead of writing messy HTML, you describe forms in Python:
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
class GreetingForm(FlaskForm):
name = StringField('Your Name')
submit = SubmitField('Say Hello')
What just happened?
FlaskForm= the blueprint makerStringField= a text boxSubmitField= a button
Real Life: Like ordering pizza by checking boxes instead of writing a letter!
graph TD A[You describe form<br>in Python] --> B[Flask-WTF builds<br>HTML for you] B --> C[User fills it out] C --> D[Flask-WTF checks<br>everything]
🧰 Chapter 3: Form Field Types (Your Toolbox)
Your butler has MANY types of input boxes. Pick the right one!
Text Fields
| Field | What It Does | Example Use |
|---|---|---|
StringField |
Short text | Username |
TextAreaField |
Long text | Bio, Comments |
PasswordField |
Hidden dots | Passwords |
from wtforms import (
StringField,
TextAreaField,
PasswordField
)
class ProfileForm(FlaskForm):
username = StringField('Username')
bio = TextAreaField('About You')
password = PasswordField('Password')
Choice Fields
| Field | What It Does | Example Use |
|---|---|---|
SelectField |
Dropdown menu | Country picker |
RadioField |
Pick ONE circle | Gender |
BooleanField |
Checkbox | “I agree” |
from wtforms import (
SelectField,
RadioField,
BooleanField
)
class PrefsForm(FlaskForm):
color = SelectField(
'Favorite Color',
choices=[('r','Red'), ('b','Blue')]
)
agree = BooleanField('I Accept Terms')
Special Fields
| Field | What It Does |
|---|---|
IntegerField |
Numbers only |
FloatField |
Decimal numbers |
DateField |
Calendar dates |
EmailField |
Email addresses |
FileField |
Upload files |
🎨 Chapter 4: Rendering Forms in Templates
The Magic: {{ form.field }}
Your butler can paint forms in HTML automatically!
In your route:
@app.route('/greet', methods=['GET','POST'])
def greet():
form = GreetingForm()
return render_template('greet.html', form=form)
In your template (greet.html):
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }}
{{ form.name() }}
{{ form.submit() }}
</form>
What Does Each Part Do?
| Code | What It Does |
|---|---|
form.hidden_tag() |
Adds secret protection (CSRF) |
form.name.label |
Shows “Your Name” text |
form.name() |
Creates the input box |
Adding Styles (Make It Pretty!)
{{ form.name(class="my-input", placeholder="Enter name") }}
This creates:
<input class="my-input" placeholder="Enter name" ...>
graph TD A[Python Form Class] --> B[Pass to Template] B --> C["{{ form.field }}"] C --> D[Beautiful HTML<br>Input Box]
✅ Chapter 5: Form Validation Rules
Why Validate?
Imagine someone orders “pizza” but writes “🐱” in the quantity box.
Validation = Making sure inputs make sense!
Built-in Validators
from wtforms.validators import (
DataRequired,
Length,
Email,
EqualTo,
NumberRange
)
class SignupForm(FlaskForm):
username = StringField('Username', validators=[
DataRequired(),
Length(min=3, max=20)
])
email = StringField('Email', validators=[
DataRequired(),
Email()
])
password = PasswordField('Password', validators=[
DataRequired(),
Length(min=8)
])
confirm = PasswordField('Confirm', validators=[
EqualTo('password', message='Must match!')
])
age = IntegerField('Age', validators=[
NumberRange(min=13, max=120)
])
Validator Cheat Table
| Validator | What It Checks |
|---|---|
DataRequired() |
Can’t be empty |
Length(min, max) |
Text length limits |
Email() |
Valid email format |
EqualTo('field') |
Matches another field |
NumberRange(min, max) |
Number between limits |
Regexp(pattern) |
Matches a pattern |
URL() |
Valid web address |
Checking if Form is Valid
@app.route('/signup', methods=['GET','POST'])
def signup():
form = SignupForm()
if form.validate_on_submit():
# All checks passed! 🎉
username = form.username.data
# Save to database...
return redirect('/success')
# Show form (with errors if any)
return render_template('signup.html', form=form)
Showing Errors in Template
{{ form.username.label }}
{{ form.username() }}
{% for error in form.username.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
🔧 Chapter 6: Custom Validators
When Built-in Isn’t Enough
Sometimes you need special rules. Make your own!
Method 1: Inline Validator
Add a method starting with validate_:
from wtforms import ValidationError
class SignupForm(FlaskForm):
username = StringField('Username')
def validate_username(self, field):
if field.data.lower() == 'admin':
raise ValidationError(
'Cannot use "admin" as username!'
)
How it works:
- Name it
validate_+ field name - Check the
field.data - Raise
ValidationErrorif bad
Method 2: Reusable Validator Function
def no_bad_words(form, field):
bad = ['spam', 'fake', 'test']
if field.data.lower() in bad:
raise ValidationError(
'Please use a real value!'
)
class CommentForm(FlaskForm):
text = TextAreaField('Comment', validators=[
DataRequired(),
no_bad_words # Our custom one!
])
Method 3: Factory (With Parameters)
def must_contain(word):
def _validator(form, field):
if word not in field.data:
raise ValidationError(
f'Must contain "{word}"'
)
return _validator
class Form(FlaskForm):
code = StringField('Code', validators=[
must_contain('ABC')
])
graph TD A[User Submits Form] --> B{Built-in Validators} B -->|Pass| C{Custom Validators} B -->|Fail| E[Show Error] C -->|Pass| D[Success! ✅] C -->|Fail| E
🛡️ Chapter 7: CSRF Protection
The Sneaky Attack
Imagine a bad guy creates a fake website with a hidden form. When you visit, it secretly submits to YOUR bank!
This is CSRF (Cross-Site Request Forgery).
How Flask-WTF Stops It
- Secret Token: Your butler generates a unique “ticket”
- Hidden in Form: Added to every form you create
- Checked on Submit: If ticket is wrong = REJECTED!
The Magic Happens Automatically
When you use FlaskForm + form.hidden_tag():
<form method="POST">
{{ form.hidden_tag() }}
<!-- This adds a hidden input like: -->
<!-- <input type="hidden" name="csrf_token" value="abc123..."> -->
</form>
The Flow
graph TD A[Your Server] -->|Gives Secret Token| B[Your Form] B -->|User Submits| C{Token Matches?} C -->|Yes ✅| D[Process Form] C -->|No ❌| E[Reject! Bad Guy Detected]
What You Must Do
- Set SECRET_KEY:
app.config['SECRET_KEY'] = 'hard-to-guess-string'
- Always use hidden_tag():
{{ form.hidden_tag() }}
- That’s it! Flask-WTF handles the rest.
For AJAX/API Forms
If you send forms with JavaScript:
<meta name="csrf-token" content="{{ csrf_token() }}">
// Include in your fetch headers:
headers: {
'X-CSRFToken': document.querySelector(
'meta[name="csrf-token"]'
).content
}
🎁 Putting It All Together
Here’s a complete example combining everything:
from flask import Flask, render_template, redirect
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length
app = Flask(__name__)
app.config['SECRET_KEY'] = 'super-secret-key'
class LoginForm(FlaskForm):
email = StringField('Email', validators=[
DataRequired(),
Email()
])
password = PasswordField('Password', validators=[
DataRequired(),
Length(min=8)
])
submit = SubmitField('Log In')
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# Form is valid!
email = form.email.data
# Check password, log in user...
return redirect('/dashboard')
return render_template('login.html', form=form)
Template (login.html):
<form method="POST">
{{ form.hidden_tag() }}
<div>
{{ form.email.label }}
{{ form.email(class="input-box") }}
{% for e in form.email.errors %}
<span class="error">{{ e }}</span>
{% endfor %}
</div>
<div>
{{ form.password.label }}
{{ form.password(class="input-box") }}
{% for e in form.password.errors %}
<span class="error">{{ e }}</span>
{% endfor %}
</div>
{{ form.submit(class="btn") }}
</form>
🚀 Quick Recap
| Concept | One-Liner |
|---|---|
| Flask-WTF | Your form butler robot |
| WTForms | Describe forms in Python |
| Field Types | String, Password, Select, etc. |
| Rendering | {{ form.field }} in templates |
| Validators | Rules like Required, Email, Length |
| Custom Validators | validate_fieldname() method |
| CSRF | Secret tokens stop fake form attacks |
🎉 You Did It!
You now have a friendly butler handling all your forms:
- No messy HTML writing ✅
- Automatic input checking ✅
- Protection from bad guys ✅
Go build something awesome! 🚀