Form rendering, validation, and CSRF protection for Flask with WTForms.
72
Secure file upload handling with Werkzeug integration, providing specialized file fields and comprehensive validation including file type restrictions, size limits, and presence checking.
Werkzeug-aware file upload fields that properly handle FileStorage objects from Flask requests.
class FileField:
"""
Werkzeug-aware file upload field.
Processes FileStorage objects from Flask request.files.
"""
def process_formdata(self, valuelist):
"""Process form data containing FileStorage objects"""
class MultipleFileField:
"""
Multiple file upload field supporting multiple FileStorage objects.
Added in version 1.2.0
"""
def process_formdata(self, valuelist):
"""Process form data containing multiple FileStorage objects"""Comprehensive validation for uploaded files including presence, type restrictions, and size limits.
class FileRequired:
"""
Validates that uploaded file(s) are present and valid FileStorage objects.
Args:
message: Custom error message
"""
def __init__(self, message=None): ...
def __call__(self, form, field): ...
class FileAllowed:
"""
Validates that uploaded file(s) have allowed extensions.
Args:
upload_set: List of allowed extensions or Flask-Uploads UploadSet
message: Custom error message
"""
def __init__(self, upload_set, message=None): ...
def __call__(self, form, field): ...
class FileSize:
"""
Validates that uploaded file(s) are within size limits.
Args:
max_size: Maximum file size in bytes
min_size: Minimum file size in bytes (default: 0)
message: Custom error message
"""
def __init__(self, max_size, min_size=0, message=None): ...
def __call__(self, form, field): ...Convenient lowercase aliases for file validators.
file_required = FileRequired
file_allowed = FileAllowed
file_size = FileSizefrom flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired, FileAllowed
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class UploadForm(FlaskForm):
title = StringField('Title', validators=[DataRequired()])
file = FileField('File', validators=[
FileRequired(),
FileAllowed(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'], 'Images and documents only!')
])
submit = SubmitField('Upload')
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
form = UploadForm()
if form.validate_on_submit():
title = form.title.data
file = form.file.data # Werkzeug FileStorage object
if file:
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
flash(f'File {filename} uploaded successfully!')
return redirect(url_for('upload_file'))
return render_template('upload.html', form=form)from flask_wtf.file import MultipleFileField, FileAllowed, FileSize
class MultiUploadForm(FlaskForm):
files = MultipleFileField('Files', validators=[
FileAllowed(['jpg', 'png', 'gif'], 'Images only!'),
FileSize(max_size=5*1024*1024, message='Files must be smaller than 5MB') # 5MB limit
])
submit = SubmitField('Upload All')
@app.route('/multi-upload', methods=['GET', 'POST'])
def multi_upload():
form = MultiUploadForm()
if form.validate_on_submit():
files = form.files.data # List of FileStorage objects
uploaded_files = []
for file in files:
if file:
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
uploaded_files.append(filename)
flash(f'Uploaded {len(uploaded_files)} files: {", ".join(uploaded_files)}')
return redirect(url_for('multi_upload'))
return render_template('multi_upload.html', form=form)class AdvancedUploadForm(FlaskForm):
# Required file with extension and size validation
document = FileField('Document', validators=[
FileRequired('Please select a file'),
FileAllowed(['pdf', 'doc', 'docx'], 'Documents only'),
FileSize(max_size=10*1024*1024, min_size=1024,
message='File must be between 1KB and 10MB')
])
# Optional image with validation
image = FileField('Image', validators=[
FileAllowed(['jpg', 'jpeg', 'png', 'gif'], 'Images only'),
FileSize(max_size=2*1024*1024, message='Image must be smaller than 2MB')
])
submit = SubmitField('Upload')
# Using validator aliases
class SimpleUploadForm(FlaskForm):
file = FileField('File', validators=[
file_required('File is required'),
file_allowed(['txt', 'csv'], 'Text files only'),
file_size(max_size=1024*1024, message='File too large') # 1MB
])Flask-WTF integrates with Flask-Uploads for organized file handling:
from flask_uploads import UploadSet, configure_uploads, IMAGES, DOCUMENTS
# Configure upload sets
photos = UploadSet('photos', IMAGES)
documents = UploadSet('documents', DOCUMENTS)
configure_uploads(app, (photos, documents))
class UploadsForm(FlaskForm):
# Using UploadSet with FileAllowed
photo = FileField('Photo', validators=[
FileRequired(),
FileAllowed(photos, 'Images only!') # Uses UploadSet
])
document = FileField('Document', validators=[
FileAllowed(documents, 'Documents only!') # Uses UploadSet
])
submit = SubmitField('Upload')from wtforms import ValidationError
import imghdr
class ImageUploadForm(FlaskForm):
image = FileField('Image', validators=[FileRequired()])
def validate_image(self, field):
"""Custom validator to check if file is a valid image"""
if field.data:
# Check file header to verify it's actually an image
field.data.seek(0) # Reset file pointer
if not imghdr.what(field.data):
raise ValidationError('File is not a valid image')
field.data.seek(0) # Reset for saving
# Check image dimensions
from PIL import Image
try:
img = Image.open(field.data)
width, height = img.size
if width > 2000 or height > 2000:
raise ValidationError('Image dimensions too large (max 2000x2000)')
field.data.seek(0) # Reset for saving
except Exception:
raise ValidationError('Cannot process image file')import os
from werkzeug.utils import secure_filename
@app.route('/process-upload', methods=['GET', 'POST'])
def process_upload():
form = UploadForm()
if form.validate_on_submit():
file = form.file.data
# File information
filename = file.filename
content_type = file.content_type
content_length = file.content_length
# Secure the filename
safe_filename = secure_filename(filename)
# Create unique filename to prevent conflicts
timestamp = int(time.time())
unique_filename = f"{timestamp}_{safe_filename}"
# Save file
file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
file.save(file_path)
# Process file content
if content_type.startswith('text/'):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Process text content
elif content_type.startswith('image/'):
from PIL import Image
img = Image.open(file_path)
# Process image
return {'status': 'success', 'filename': unique_filename}
return render_template('upload.html', form=form)<form method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.title.label(class="form-label") }}
{{ form.title(class="form-control") }}
{% for error in form.title.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
{{ form.file.label(class="form-label") }}
{{ form.file(class="form-control", accept=".txt,.pdf,.png,.jpg,.jpeg,.gif") }}
{% for error in form.file.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
{{ form.submit(class="btn btn-primary") }}
</form><form method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.files.label(class="form-label") }}
{{ form.files(class="form-control", multiple=true, accept="image/*") }}
<small class="form-text text-muted">
Select multiple image files (max 5MB each)
</small>
{% for error in form.files.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>import os
# Basic upload configuration
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max request size
# Ensure upload directory exists
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)secure_filename() to sanitize uploaded filenamesInstall with Tessl CLI
npx tessl i tessl/pypi-flask-wtfevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10