Implementing Custom Validators and File Upload Validation with Flask-WTF

Custom Validators in Flask-WTF

Custom validators are defined using a specific method naming convention: validate_fieldname(self, field). Within this method, access the form field's data via field.data. If the validation passes, the method should return normally. To indicate a validation failure, raise a wtforms.validators.ValidationError exception with a descriptive error message.

Example: Login Form with Captcha Validation

Template (login_template.html)

<!DOCTYPE html>
<html>
<head>
    <title>User Login</title>
</head>
<body>
    <form method="POST">
        <div>
            <label>Username:</label>
            <input type="text" name="username">
        </div>
        <div>
            <label>Password:</label>
            <input type="password" name="password">
        </div>
        <div>
            <label>Security Code ({{ captcha }}):</label>
            <input type="text" name="captcha" maxlength="4">
        </div>
        <div>
            <input type="submit" value="Log In">
        </div>
    </form>
</body>
</html>

Form Class (auth_forms.py)

from flask import session
from wtforms import Form, StringField
from wtforms.validators import Length, ValidationError

class LoginForm(Form):
    captcha = StringField(validators=[Length(min=4, max=4)])
    
    def validate_captcha(self, field):
        """
        Custom validator to compare entered captcha with server session.
        """
        user_input = field.data
        server_stored = session.get('captcha_key', '')
        if user_input != str(server_stored):
            raise ValidationError('Security code mismatch. Please try again.')

Application Logic (app.py)

from flask import Flask, render_template, request, session
import secrets
from auth_forms import LoginForm

app = Flask(__name__)
app.config['SECRET_KEY'] = secrets.token_hex()

@app.route('/login', methods=['GET', 'POST'])
def user_login():
    if request.method == 'GET':
        session['captcha_key'] = secrets.randbelow(9000) + 1000
        return render_template('login_template.html', 
                               captcha=session['captcha_key'])
    
    form_data = LoginForm(request.form)
    if form_data.validate():
        return 'Authentication successful.'
    return f'Validation failed. Errors: {form_data.errors}'

if __name__ == '__main__':
    app.run(debug=True)

File Upload Validation with Flask-WTF

To validate file uploads, use the FileField type from WTForms along with validators from flask_wtf.file. The FileRequired validator ensures a file is uploaded, while FileAllowed restricts allowed file extensions. In the view, merge request.form and request.files using CombinedMultiDict before validation.

Example: Profile Picture Upload

Project Structure

project/
├── static/uploads/
├── templates/
│   └── upload_form.html
├── app.py
└── forms.py

Upload Form Template (upload_form.html)

<!DOCTYPE html>
<html>
<head>
    <title>Upload Profile Picture</title>
</head>
<body>
    <form method="POST" enctype="multipart/form-data">
        <div>
            <label>Profile Image:</label>
            <input type="file" name="profile_image">
        </div>
        <div>
            <label>Description:</label>
            <input type="text" name="image_description">
        </div>
        <div>
            <input type="submit" value="Upload">
        </div>
    </form>
</body>
</html>

Form Definition (forms.py)

from wtforms import Form, FileField, StringField
from wtforms.validators import InputRequired
from flask_wtf.file import FileRequired, FileAllowed

class ImageUploadForm(Form):
    profile_image = FileField(validators=[
        FileRequired(),
        FileAllowed(['jpg', 'jpeg', 'png', 'gif'], 'Image files only')
    ])
    image_description = StringField(validators=[InputRequired()])

Application with Upload Handler (app.py)

import os
from flask import Flask, request, render_template
from werkzeug.datastructures import CombinedMultiDict
from werkzeug.utils import secure_filename
from forms import ImageUploadForm

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.path.join('static', 'uploads')

@app.route('/upload', methods=['GET', 'POST'])
def handle_upload():
    if request.method == 'GET':
        return render_template('upload_form.html')
    
    merged_data = CombinedMultiDict([request.form, request.files])
    upload_form = ImageUploadForm(merged_data)
    
    if upload_form.validate():
        description_text = upload_form.image_description.data
        image_file = upload_form.profile_image.data
        safe_filename = secure_filename(image_file.filename)
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)
        image_file.save(file_path)
        return 'File saved successfully.'
    
    return f'Upload failed: {upload_form.errors}'

if __name__ == '__main__':
    os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
    app.run()

Tags: Flask Flask-WTF Form Validation Custom Validator File Upload

Posted on Thu, 07 May 2026 14:31:03 +0000 by crazyeight