Integrating WTForms for Validation in Tornado Applications

Prerequisites and Installation

To begin implementing form validation, install the necessary packages. The core library is WTForms, but since we are integrating with Tornado, we also need the adapter bridge.

pip install wtforms wtforms-tornado peewee pymysql

Database Configuration and Model

We will define a UserFeedback model using the Peewee ORM to store submissions in a MySQL database.

from peewee import *
from datetime import datetime

db_config = MySQLDatabase('feedback_db', host='localhost', port=3306, user='dev_user', password='secret_password')

class UserFeedback(Model):
    uid = AutoField()
    username = CharField(max_length=50)
    email_addr = CharField(max_length=100)
    content = TextField()
    created_at = DateTimeField(default=datetime.now)

    class Meta:
        database = db_config
        table_name = 'entries'

if __name__ == '__main__':
    db_config.create_tables([UserFeedback])

Form Schema Definition

Using wttforms_tornado, we define the validation rules. This class will handle input sanitization and error generation.

from wtforms.fields import StringField, TextAreaField
from wtforms_tornado import Form
from wtforms.validators import DataRequired, Length, Email

class FeedbackForm(Form):
    username = StringField(
        "User Name", 
        validators=[DataRequired(message="Name is required"), Length(min=3, max=20)]
    )
    email_addr = StringField(
        "Email Address", 
        validators=[DataRequired(message="Email is required"), Email(message="Invalid email format")]
    )
    content = TextAreaField(
        "Feedback Content", 
        validators=[DataRequired(message="Content cannot be empty"), Length(max=500)]
    )

HTML Template

The template iterates through the form fields. It automatically injects error messages if validation fails during the POST request.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Submit Feedback</title>
    <link rel="stylesheet" href="{{ static_url('layout.css') }}">
</head>
<body>
    <form action="/" method="post" class="clean-layout">
        <h1>Contact Form
            <span>We value your opinion.</span>
        </h1>
        
        {% autoescape None %}
        {% for field in feedback_form %}
            <label>
                <span>{{ field.label.text }}</span>
                {{ field(class_='input-control', placeholder=field.label.text) }}
                
                {% if field.errors %}
                    <div class="validation-error">
                        {% for err in field.errors %}{{ err }}{% end %}
                    </div>
                {% end %}
            </label>
        {% end %}
        
        <label>
            <span>&nbsp;</span>
            <input type="submit" class="btn-submit" value="Send Message"/>
        </label>
    </form>
</body>
</html>

Styling (CSS)

Basic styling to center the form and highlight validation errors.

.clean-layout {
    width: 450px;
    margin: 50px auto;
    background-color: #fff;
    padding: 30px;
    border-radius: 8px;
    box-shadow: 0 4px 10px rgba(0,0,0,0.1);
    font-family: sans-serif;
}

.clean-layout h1 {
    background-color: #4A90E2;
    color: white;
    padding: 15px 20px;
    margin: -30px -30px 30px -30px;
    border-radius: 8px 8px 0 0;
}

.clean-layout label span {
    display: block;
    margin-bottom: 5px;
    color: #333;
    font-weight: bold;
}

.input-control {
    width: 100%;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    box-sizing: border-box;
    margin-bottom: 15px;
}

.btn-submit {
    background-color: #4A90E2;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
    width: 100%;
}

.btn-submit:hover {
    background-color: #357ABD;
}

.validation-error {
    color: #D0021B;
    font-size: 0.85em;
    margin-top: -10px;
    margin-bottom: 10px;
}

Server Implementation

The Tornado application initializes the database and defines the request handler. The handler distinguishes between GET (display form) and POST (process and validate) requests.

from tornado.web import Application, RequestHandler
from tornado.ioloop import IOLoop
from models import db_config, UserFeedback
from validators import FeedbackForm

class SubmissionHandler(RequestHandler):
    
    def get(self):
        form = FeedbackForm()
        self.render("submission.html", feedback_form=form)

    def post(self):
        # Populate form with request data
        form = FeedbackForm(self.request.arguments)
        
        if form.validate():
            # Extract and save data
            UserFeedback.create(
                username=form.username.data,
                email_addr=form.email_addr.data,
                content=form.content.data
            )
            self.write("<h3 style='text-align:center;color:green'>Feedback saved successfully!</h3>")
        else:
            # Re-render form with errors
            self.render("submission.html", feedback_form=form)

settings = {
    "static_path": "static",
    "template_path": "templates",
    "debug": True
}

if __name__ == "__main__":
    # Ensure database connection is open
    db_config.connect()
    
    app = Application([
        (r"/", SubmissionHandler),
    ], **settings)
    
    print("Server running on http://127.0.0.1:8080")
    app.listen(8080)
    IOLoop.current().start()

Tags: python tornado wtforms web-development form-validation

Posted on Wed, 30 Sep 2026 16:56:13 +0000 by Rayhan Muktader