I am using WTForms for the first time.
Using WTForms to validate POST requests in Tornado
Below is my forms
forms.py
class UserForm(Form):
user = TextField('user', [validators.Length(min=23, max=23)])
In the tonado handler I have
def post(self):
form = UserForm(self.request.body)
The error message i get is:
formdata should be a multidict-type wrapper that supports the 'getlist' method"
How could I make this work?
wtforms-tornado 0.0.1
WTForms extensions for Tornado.
pip install wtforms-tornado
WTForms-Tornado
You'll need an object that can translate the Tornado form object into something WTForms expects. I use this:
class TornadoFormMultiDict(object):
"""Wrapper class to provide form values to wtforms.Form
This class is tightly coupled to a request handler, and more importantly one of our BaseHandlers
which has a 'context'. At least if you want to use the save/load functionality.
Some of this more difficult that it otherwise seems like it should be because of nature
of how tornado handles it's form input.
"""
def __init__(self, handler):
# We keep a weakref to prevent circular references
# This object is tightly coupled to the handler... which certainly isn't nice, but it's the
# way it's gonna have to be for now.
self.handler = weakref.ref(handler)
@property
def _arguments(self):
return self.handler().request.arguments
def __iter__(self):
return iter(self._arguments)
def __len__(self):
return len(self._arguments)
def __contains__(self, name):
# We use request.arguments because get_arguments always returns a
# value regardless of the existence of the key.
return (name in self._arguments)
def getlist(self, name):
# get_arguments by default strips whitespace from the input data,
# so we pass strip=False to stop that in case we need to validate
# on whitespace.
return self.handler().get_arguments(name, strip=False)
def __getitem__(self, name):
return self.handler().get_argument(name)
Then in your base handler, you'd do something like:
self.form = TornadoFormMultiDict(self)
Note: I think this adapted from a mailing list topic on this subject.
I didn't find replacing a basic html form contained within a Tornado template with a WTForms form as intuitive as I might have hoped.
Here's an example of a very simple form using wtforms-tornado
:
The template:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta charset="utf-8">
<title>A Simple Form</title>
<meta name="description" content="Submit Track to Infinite Glitch">
</head>
<body>
<p><h1>Submit Info</h1></p>
<form enctype="multipart/form-data" action="/simple" method="post">
{{ form.name }}<br/>
{{ form.email }}<br/>
{{ form.message }}<br/>
<input type="submit"/>
</form>
</body>
</html>
The app code:
import wtforms
from wtforms_tornado import Form
class EasyForm(Form):
name = wtforms.TextField('name', validators=[wtforms.validators.DataRequired()], default=u'test')
email = wtforms.TextField('email', validators=[wtforms.validators.Email(), wtforms.validators.DataRequired()])
message = wtforms.TextAreaField('message', validators=[wtforms.validators.DataRequired()])
class SimpleForm(tornado.web.RequestHandler):
def get(self):
form = EasyForm()
self.write(templates.load("simpleform.html").generate(compiled=compiled, form=form))
def post(self):
form = EasyForm(self.request.arguments)
details = '';
if form.validate():
for f in self.request.arguments:
details += "<hr/>" + self.get_argument(f, default=None, strip=False)
self.write(details)
else:
self.set_status(400)
self.write(form.errors)
if __name__ == "__main__":
application = tornado.web.Application(
tornadio2.TornadioRouter(SocketConnection).apply_routes([
(r"/simple", SimpleForm),
]),
)
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()