WTForms integration
Get started
1. Installation
# Install the WTForms version with Poetry
poetry add tna-frontend-jinja[wtforms]
# Install the WTForms version with pip
pip install tna-frontend-jinja[wtforms]
The dependencies added for WTForms are done through Flask-WTF.
Flask-WTF includes some helpful extras to WTForms, like CSRF and file upload. Both are maintained as part of the Pallets Ecosystem.
tna-frontend-jinja depends on the email version of Flask-WTF so that email-validator is also included, allowing you to validate email addresses.
The dependency chain looks like:
tna-frontend-jinja[wtforms]
↳ Flask-WTF[email]
↳ Flask
↳ WTForms
↳ email_validator
↳ ...
2. Add the TNA Frontend Jinja WTForms helpers
from flask import Flask
from tna_frontend_jinja.wtforms.helpers import WTFormsHelpers
app = Flask(__name__)
WTFormsHelpers(app)
3. Form setup
3.1. Create a form with fields
from flask_wtf import FlaskForm
from wtforms import (
EmailField,
StringField,
SubmitField,
)
class TextInputForm(FlaskForm):
username = StringField(
"Username",
description="This will be used to log in",
)
email = EmailField(
"Email address",
)
submit = SubmitField(
"Continue",
)
3.2. Add some validation
from flask_wtf import FlaskForm
from wtforms import (
EmailField,
StringField,
SubmitField,
+ validators,
)
class TextInputForm(FlaskForm):
username = StringField(
"Username",
description="This will be used to log in",
+ validators=[
+ validators.InputRequired(message="Enter a username"),
+ validators.Length(
+ max=32, message="Usernames must be 32 characters or fewer"
+ ),
+ ],
)
email = EmailField(
"Email address",
+ validators=[
+ validators.InputRequired(message="Enter an email address"),
+ validators.Email(message="Enter a valid email address"),
+ ],
)
submit = SubmitField(
"Continue",
)
3.3. Use the TNA widgets
Use render_kw to set defaults for the attributes on the field. These can be overwritten by params in the template.
The available options can be found under the "Nunjucks options" dropdown of the Nunjucks examples in the components section of the National Archives Design System.
from flask_wtf import FlaskForm
+ from tna_frontend_jinja.wtforms import (
+ TnaEmailInputWidget,
+ TnaTextInputWidget,
+ )
from wtforms import (
EmailField,
StringField,
validators,
)
class TextInputForm(FlaskForm):
username = StringField(
"Username",
description="This will be used to log in",
validators=[
validators.InputRequired(message="Enter a username"),
validators.Length(
max=32, message="Usernames must be 32 characters or fewer"
),
],
+ widget=TnaTextInputWidget(),
)
email = EmailField(
"Email address",
validators=[
validators.InputRequired(message="Enter an email address"),
validators.Email(message="Enter a valid email address"),
],
+ widget=TnaEmailInputWidget(),
+ render_kw={"size": "m"},
)
submit = SubmitField(
"Continue",
+ widget=TnaSubmitWidget(),
)
4. Routing
Create a route to display your form and accept POST requests and another route to handle the success state.
This allows us to use the Post/Redirect/Get pattern.
# Accept GET and POST requests
@app.route("/my-form/", methods=["GET", "POST"])
def my_form():
# Instantiate a form
form = MyForm()
# flask_wtf provides a validate_on_submit which runs on POST and runs the validation
if form.validate_on_submit():
# Redirect to the success page
return redirect(url_for("success"))
# If the request is a GET or validation is not successful, render the form
return render_template("my-form.html", form=form)
@app.route("/success/")
def success():
# Render the success/next page
return render_template("success.html")
5. Templates
Add a my-form.html template to render the form.
Output the form fields in whatever order or structure you need.
Customise the components by adding a params parameter to the field constructor. These can be any documented in the National Archives Design System for that component.
{%- from 'components/error-summary/macro.html' import tnaErrorSummary -%}
{% if form.errors %}
{{ tnaErrorSummary(wtforms_errors(form)) }}
{% endif %}
<h1 class="tna-heading-xl">My form</h1>
<form action="{{ url_for('my_form') }}" method="post" novalidate>
{{ form.csrf_token }}
<!-- Add the username field -->
{{ form.username }}
<!-- Add the email field and customise it with parameters -->
{{ form.email(params={'headingLevel': 2}) }}
<div class="tna-button-group">
{{ form.submit }}
</div>
</form>
Supported fields and widgets
| WTForms field | TNA widget(s) |
|---|---|
BooleanField |
✅ TnaCheckboxWidget |
ColorField |
🔧 [not yet supported] |
DateField |
❌ [not supported] - use TnaDateField |
DateTimeField |
🔧 [not yet supported] |
DateTimeLocalField |
🔧 [not yet supported] |
DecimalField |
✅ TnaNumberInputWidget |
DecimalRangeField |
🔧 [not yet supported] |
EmailField |
✅ TnaEmailInputWidget |
FieldList |
🔧 [not yet supported] |
FileField |
✅ TnaFileInputWidget or TnaDroppableFileInputWidget |
FloatField |
✅ TnaNumberInputWidget |
FormField |
✅ TnaFieldsetWidget (optional) |
HiddenField |
✅ [none needed] |
IntegerField |
✅ TnaNumberInputWidget |
IntegerRangeField |
🔧 [not yet supported] |
MultipleFileField |
✅ TnaFilesInputWidget or TnaDroppableFilesInputWidget |
MonthField |
❌ [not supported] - use TnaMonthField |
PasswordField |
✅ TnaPasswordWidget |
RadioField |
✅ TnaRadiosWidget |
SelectField |
✅ TnaSelectWidget |
SearchField |
✅ TnaSearchFieldWidget |
SelectMultipleField |
✅ TnaCheckboxesWidget |
SubmitField |
✅ TnaSubmitWidget |
StringField |
✅ TnaTextInputWidget |
TelField |
✅ TnaTelInputWidget |
TimeField |
🔧 [not yet supported] |
TextAreaField |
✅ TnaTextareaWidget |
URLField |
✅ TnaUrlInputWidget |