1Form Validation with WTForms 2============================ 3 4When you have to work with form data submitted by a browser view, code 5quickly becomes very hard to read. There are libraries out there designed 6to make this process easier to manage. One of them is `WTForms`_ which we 7will handle here. If you find yourself in the situation of having many 8forms, you might want to give it a try. 9 10When you are working with WTForms you have to define your forms as classes 11first. I recommend breaking up the application into multiple modules 12(:doc:`packages`) for that and adding a separate module for the 13forms. 14 15.. admonition:: Getting the most out of WTForms with an Extension 16 17 The `Flask-WTF`_ extension expands on this pattern and adds a 18 few little helpers that make working with forms and Flask more 19 fun. You can get it from `PyPI 20 <https://pypi.org/project/Flask-WTF/>`_. 21 22.. _Flask-WTF: https://flask-wtf.readthedocs.io/ 23 24The Forms 25--------- 26 27This is an example form for a typical registration page:: 28 29 from wtforms import Form, BooleanField, StringField, PasswordField, validators 30 31 class RegistrationForm(Form): 32 username = StringField('Username', [validators.Length(min=4, max=25)]) 33 email = StringField('Email Address', [validators.Length(min=6, max=35)]) 34 password = PasswordField('New Password', [ 35 validators.DataRequired(), 36 validators.EqualTo('confirm', message='Passwords must match') 37 ]) 38 confirm = PasswordField('Repeat Password') 39 accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()]) 40 41In the View 42----------- 43 44In the view function, the usage of this form looks like this:: 45 46 @app.route('/register', methods=['GET', 'POST']) 47 def register(): 48 form = RegistrationForm(request.form) 49 if request.method == 'POST' and form.validate(): 50 user = User(form.username.data, form.email.data, 51 form.password.data) 52 db_session.add(user) 53 flash('Thanks for registering') 54 return redirect(url_for('login')) 55 return render_template('register.html', form=form) 56 57Notice we're implying that the view is using SQLAlchemy here 58(:doc:`sqlalchemy`), but that's not a requirement, of course. Adapt 59the code as necessary. 60 61Things to remember: 62 631. create the form from the request :attr:`~flask.request.form` value if 64 the data is submitted via the HTTP ``POST`` method and 65 :attr:`~flask.request.args` if the data is submitted as ``GET``. 662. to validate the data, call the :func:`~wtforms.form.Form.validate` 67 method, which will return ``True`` if the data validates, ``False`` 68 otherwise. 693. to access individual values from the form, access `form.<NAME>.data`. 70 71Forms in Templates 72------------------ 73 74Now to the template side. When you pass the form to the templates, you can 75easily render them there. Look at the following example template to see 76how easy this is. WTForms does half the form generation for us already. 77To make it even nicer, we can write a macro that renders a field with 78label and a list of errors if there are any. 79 80Here's an example :file:`_formhelpers.html` template with such a macro: 81 82.. sourcecode:: html+jinja 83 84 {% macro render_field(field) %} 85 <dt>{{ field.label }} 86 <dd>{{ field(**kwargs)|safe }} 87 {% if field.errors %} 88 <ul class=errors> 89 {% for error in field.errors %} 90 <li>{{ error }}</li> 91 {% endfor %} 92 </ul> 93 {% endif %} 94 </dd> 95 {% endmacro %} 96 97This macro accepts a couple of keyword arguments that are forwarded to 98WTForm's field function, which renders the field for us. The keyword 99arguments will be inserted as HTML attributes. So, for example, you can 100call ``render_field(form.username, class='username')`` to add a class to 101the input element. Note that WTForms returns standard Python strings, 102so we have to tell Jinja2 that this data is already HTML-escaped with 103the ``|safe`` filter. 104 105Here is the :file:`register.html` template for the function we used above, which 106takes advantage of the :file:`_formhelpers.html` template: 107 108.. sourcecode:: html+jinja 109 110 {% from "_formhelpers.html" import render_field %} 111 <form method=post> 112 <dl> 113 {{ render_field(form.username) }} 114 {{ render_field(form.email) }} 115 {{ render_field(form.password) }} 116 {{ render_field(form.confirm) }} 117 {{ render_field(form.accept_tos) }} 118 </dl> 119 <p><input type=submit value=Register> 120 </form> 121 122For more information about WTForms, head over to the `WTForms 123website`_. 124 125.. _WTForms: https://wtforms.readthedocs.io/ 126.. _WTForms website: https://wtforms.readthedocs.io/ 127