Skip to content

WTForms integration

Get started

1. Install WTForms

poetry add Flask-WTF

2. Add the TNA Frontend Jinja WTForms helpers

from flask import Flask
from tna_frontend_jinja.wtforms.helpers import WTFormsHelpers


def create_app():
    app = Flask(__name__)

    # ...application setup...

    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

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

    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': 3 }) }}

  <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 🔧 [not yet supported]
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