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> </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()