WTForm

Render a WTForm with declarative validation, error handling and csrf protection.

User Profile
Enter your profile information

Installation

uvx --from basic-components components add wtform
pipx run --spec basic-components components add wtform
pip install basic-components && components add wtform
  • Copy WTForm components below to your local environment
  • Place them in the components/ui/wtform directory in your project
  • Configure your jinja environment for JinjaX
  • Add the cn() helper function to your global jinja environment

Usage

<WTForm
  :form="{{ form }}"
  hx-post="/demo/wtform"
  hx_target_422="this"
>
  <Button type="submit">
    Submit
  </Button>
</WTForm>
import json

from fastapi import APIRouter, Request
from starlette import status
from starlette.responses import HTMLResponse
from starlette_wtf import StarletteForm
from wtforms import (
    StringField,
    validators,
    PasswordField,
)

from docs.templates import template, catalog


class HTMLRouter(APIRouter):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.include_in_schema = False
        self.default_response_class = HTMLResponse


router = HTMLRouter()


class SampleForm(StarletteForm):
    """
    Form object with declarative validation
    """

    username = StringField(
        "Username",
        [validators.InputRequired(), validators.Length(min=4, max=25)],
        id="username",
        render_kw={"placeholder": "username"},
        description="Enter a unique username.",
    )
    email = StringField(
        "Email",
        [validators.InputRequired(), validators.Email()],
        id="email",
        render_kw={"placeholder": "your@email.com"},
        description="Your primary email address.",
    )
    password = PasswordField(
        "Password",
        [validators.InputRequired(), validators.Length(min=6)],
        id="password",
        render_kw={"placeholder": "••••••••"},
        description="Must be at least 6 characters.",
    )


@router.get("/demo/wtform")
async def display(request: Request):
    """
    display the form on the page via a template
    """
    form = SampleForm(request)
    return template(
        request=request,
        name="examples/wtform.html",
        context={"form": form},
    )


@router.post("/demo/wtform")
async def post(request: Request):
    """
    handle the form via post
    """
    form = await SampleForm.from_formdata(request)

    if not await form.validate():
        # return with a 422 response
        return template(
            request,
            "examples/wtform.html",
            context={"form": form},
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        )

    # Form is valid, process the form data
    username = form.username.data
    email = form.email.data
    password = form.password.data
    # Log or handle the data as needed (e.g., save to a database)

    # return results to the page
    component = catalog.render(
        "FormResult",
        _content=json.dumps(
            {"username": username, "email": email, "password": password}
        ),
    )
    return HTMLResponse(component)

The WTForm component makes it very easy to render, validate and handle form processing when using a form instance from the startlette-wtf lib. Starlette-WTF adds CSRF protection and declarative validation to your forms.

With the htmx Response Targets Extension you can direct error respones to targets in the DOM. The server can return a response with an error code to display errors inline on the page. For more info, see htmx.

Code

<!-- components/ui/extended/WTForm.jinja not found -->