0
# File Upload
1
2
Secure file upload handling with Werkzeug integration, providing specialized file fields and comprehensive validation including file type restrictions, size limits, and presence checking.
3
4
## Capabilities
5
6
### File Fields
7
8
Werkzeug-aware file upload fields that properly handle FileStorage objects from Flask requests.
9
10
```python { .api }
11
class FileField:
12
"""
13
Werkzeug-aware file upload field.
14
Processes FileStorage objects from Flask request.files.
15
"""
16
def process_formdata(self, valuelist):
17
"""Process form data containing FileStorage objects"""
18
19
class MultipleFileField:
20
"""
21
Multiple file upload field supporting multiple FileStorage objects.
22
23
Added in version 1.2.0
24
"""
25
def process_formdata(self, valuelist):
26
"""Process form data containing multiple FileStorage objects"""
27
```
28
29
### File Validators
30
31
Comprehensive validation for uploaded files including presence, type restrictions, and size limits.
32
33
```python { .api }
34
class FileRequired:
35
"""
36
Validates that uploaded file(s) are present and valid FileStorage objects.
37
38
Args:
39
message: Custom error message
40
"""
41
def __init__(self, message=None): ...
42
def __call__(self, form, field): ...
43
44
class FileAllowed:
45
"""
46
Validates that uploaded file(s) have allowed extensions.
47
48
Args:
49
upload_set: List of allowed extensions or Flask-Uploads UploadSet
50
message: Custom error message
51
"""
52
def __init__(self, upload_set, message=None): ...
53
def __call__(self, form, field): ...
54
55
class FileSize:
56
"""
57
Validates that uploaded file(s) are within size limits.
58
59
Args:
60
max_size: Maximum file size in bytes
61
min_size: Minimum file size in bytes (default: 0)
62
message: Custom error message
63
"""
64
def __init__(self, max_size, min_size=0, message=None): ...
65
def __call__(self, form, field): ...
66
```
67
68
### Validator Aliases
69
70
Convenient lowercase aliases for file validators.
71
72
```python { .api }
73
file_required = FileRequired
74
file_allowed = FileAllowed
75
file_size = FileSize
76
```
77
78
## Usage Examples
79
80
### Basic File Upload
81
82
```python
83
from flask_wtf import FlaskForm
84
from flask_wtf.file import FileField, FileRequired, FileAllowed
85
from wtforms import StringField, SubmitField
86
from wtforms.validators import DataRequired
87
88
class UploadForm(FlaskForm):
89
title = StringField('Title', validators=[DataRequired()])
90
file = FileField('File', validators=[
91
FileRequired(),
92
FileAllowed(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'], 'Images and documents only!')
93
])
94
submit = SubmitField('Upload')
95
96
@app.route('/upload', methods=['GET', 'POST'])
97
def upload_file():
98
form = UploadForm()
99
100
if form.validate_on_submit():
101
title = form.title.data
102
file = form.file.data # Werkzeug FileStorage object
103
104
if file:
105
filename = secure_filename(file.filename)
106
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
107
flash(f'File {filename} uploaded successfully!')
108
return redirect(url_for('upload_file'))
109
110
return render_template('upload.html', form=form)
111
```
112
113
### Multiple File Upload
114
115
```python
116
from flask_wtf.file import MultipleFileField, FileAllowed, FileSize
117
118
class MultiUploadForm(FlaskForm):
119
files = MultipleFileField('Files', validators=[
120
FileAllowed(['jpg', 'png', 'gif'], 'Images only!'),
121
FileSize(max_size=5*1024*1024, message='Files must be smaller than 5MB') # 5MB limit
122
])
123
submit = SubmitField('Upload All')
124
125
@app.route('/multi-upload', methods=['GET', 'POST'])
126
def multi_upload():
127
form = MultiUploadForm()
128
129
if form.validate_on_submit():
130
files = form.files.data # List of FileStorage objects
131
132
uploaded_files = []
133
for file in files:
134
if file:
135
filename = secure_filename(file.filename)
136
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
137
uploaded_files.append(filename)
138
139
flash(f'Uploaded {len(uploaded_files)} files: {", ".join(uploaded_files)}')
140
return redirect(url_for('multi_upload'))
141
142
return render_template('multi_upload.html', form=form)
143
```
144
145
### Advanced File Validation
146
147
```python
148
class AdvancedUploadForm(FlaskForm):
149
# Required file with extension and size validation
150
document = FileField('Document', validators=[
151
FileRequired('Please select a file'),
152
FileAllowed(['pdf', 'doc', 'docx'], 'Documents only'),
153
FileSize(max_size=10*1024*1024, min_size=1024,
154
message='File must be between 1KB and 10MB')
155
])
156
157
# Optional image with validation
158
image = FileField('Image', validators=[
159
FileAllowed(['jpg', 'jpeg', 'png', 'gif'], 'Images only'),
160
FileSize(max_size=2*1024*1024, message='Image must be smaller than 2MB')
161
])
162
163
submit = SubmitField('Upload')
164
165
# Using validator aliases
166
class SimpleUploadForm(FlaskForm):
167
file = FileField('File', validators=[
168
file_required('File is required'),
169
file_allowed(['txt', 'csv'], 'Text files only'),
170
file_size(max_size=1024*1024, message='File too large') # 1MB
171
])
172
```
173
174
### Flask-Uploads Integration
175
176
Flask-WTF integrates with Flask-Uploads for organized file handling:
177
178
```python
179
from flask_uploads import UploadSet, configure_uploads, IMAGES, DOCUMENTS
180
181
# Configure upload sets
182
photos = UploadSet('photos', IMAGES)
183
documents = UploadSet('documents', DOCUMENTS)
184
configure_uploads(app, (photos, documents))
185
186
class UploadsForm(FlaskForm):
187
# Using UploadSet with FileAllowed
188
photo = FileField('Photo', validators=[
189
FileRequired(),
190
FileAllowed(photos, 'Images only!') # Uses UploadSet
191
])
192
193
document = FileField('Document', validators=[
194
FileAllowed(documents, 'Documents only!') # Uses UploadSet
195
])
196
197
submit = SubmitField('Upload')
198
```
199
200
### Custom File Validation
201
202
```python
203
from wtforms import ValidationError
204
import imghdr
205
206
class ImageUploadForm(FlaskForm):
207
image = FileField('Image', validators=[FileRequired()])
208
209
def validate_image(self, field):
210
"""Custom validator to check if file is a valid image"""
211
if field.data:
212
# Check file header to verify it's actually an image
213
field.data.seek(0) # Reset file pointer
214
if not imghdr.what(field.data):
215
raise ValidationError('File is not a valid image')
216
field.data.seek(0) # Reset for saving
217
218
# Check image dimensions
219
from PIL import Image
220
try:
221
img = Image.open(field.data)
222
width, height = img.size
223
if width > 2000 or height > 2000:
224
raise ValidationError('Image dimensions too large (max 2000x2000)')
225
field.data.seek(0) # Reset for saving
226
except Exception:
227
raise ValidationError('Cannot process image file')
228
```
229
230
### File Processing Examples
231
232
```python
233
import os
234
from werkzeug.utils import secure_filename
235
236
@app.route('/process-upload', methods=['GET', 'POST'])
237
def process_upload():
238
form = UploadForm()
239
240
if form.validate_on_submit():
241
file = form.file.data
242
243
# File information
244
filename = file.filename
245
content_type = file.content_type
246
content_length = file.content_length
247
248
# Secure the filename
249
safe_filename = secure_filename(filename)
250
251
# Create unique filename to prevent conflicts
252
timestamp = int(time.time())
253
unique_filename = f"{timestamp}_{safe_filename}"
254
255
# Save file
256
file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
257
file.save(file_path)
258
259
# Process file content
260
if content_type.startswith('text/'):
261
with open(file_path, 'r', encoding='utf-8') as f:
262
content = f.read()
263
# Process text content
264
elif content_type.startswith('image/'):
265
from PIL import Image
266
img = Image.open(file_path)
267
# Process image
268
269
return {'status': 'success', 'filename': unique_filename}
270
271
return render_template('upload.html', form=form)
272
```
273
274
## Template Examples
275
276
### Single File Upload Template
277
278
```html
279
<form method="POST" enctype="multipart/form-data">
280
{{ form.hidden_tag() }}
281
282
<div class="form-group">
283
{{ form.title.label(class="form-label") }}
284
{{ form.title(class="form-control") }}
285
{% for error in form.title.errors %}
286
<div class="text-danger">{{ error }}</div>
287
{% endfor %}
288
</div>
289
290
<div class="form-group">
291
{{ form.file.label(class="form-label") }}
292
{{ form.file(class="form-control", accept=".txt,.pdf,.png,.jpg,.jpeg,.gif") }}
293
{% for error in form.file.errors %}
294
<div class="text-danger">{{ error }}</div>
295
{% endfor %}
296
</div>
297
298
{{ form.submit(class="btn btn-primary") }}
299
</form>
300
```
301
302
### Multiple File Upload Template
303
304
```html
305
<form method="POST" enctype="multipart/form-data">
306
{{ form.hidden_tag() }}
307
308
<div class="form-group">
309
{{ form.files.label(class="form-label") }}
310
{{ form.files(class="form-control", multiple=true, accept="image/*") }}
311
<small class="form-text text-muted">
312
Select multiple image files (max 5MB each)
313
</small>
314
{% for error in form.files.errors %}
315
<div class="text-danger">{{ error }}</div>
316
{% endfor %}
317
</div>
318
319
{{ form.submit(class="btn btn-primary") }}
320
</form>
321
```
322
323
## Configuration
324
325
### Upload Settings
326
327
```python
328
import os
329
330
# Basic upload configuration
331
app.config['UPLOAD_FOLDER'] = 'uploads'
332
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max request size
333
334
# Ensure upload directory exists
335
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
336
```
337
338
### Security Considerations
339
340
1. **Always use `secure_filename()`** to sanitize uploaded filenames
341
2. **Validate file types** using both extension and content checking
342
3. **Set reasonable size limits** to prevent resource exhaustion
343
4. **Store uploads outside the web root** when possible
344
5. **Scan uploads for viruses** in production environments
345
6. **Use unique filenames** to prevent conflicts and enumeration