Skip to content

Embedding in a Jinja Template

Real pages have a layout, navigation and their own messages. Render the form to a string and put it into your template.

templates/signup.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sign up</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body>
  <main class="container py-4" style="max-width: 40rem">
    <h1>Sign up</h1>
    {% if name %}
      <div class="alert alert-success">Welcome aboard, {{ name }}!</div>
    {% endif %}
    {{ form_html | safe }}
  </main>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
models.py
from pydantic import EmailStr

from pydantic_schemaforms import Field, FormModel


class SignupForm(FormModel):
    name: str = Field(title='Full name', min_length=2, ui_autofocus=True)
    email: EmailStr = Field(title='Email')
main.py
from pathlib import Path

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from models import SignupForm

from pydantic_schemaforms import render_form_html_async

app = FastAPI()
templates = Jinja2Templates(directory=Path(__file__).parent / 'templates')


@app.get('/signup', response_class=HTMLResponse)
async def show_signup(request: Request):
    form_html = await render_form_html_async(SignupForm, submit_url='/signup')
    return templates.TemplateResponse(request, 'signup.html', {'form_html': form_html})


@app.post('/signup', response_class=HTMLResponse)
async def signup(request: Request):
    result = SignupForm.validate(await request.form(), submit_url='/signup')
    if not result.is_valid:
        form_html = await result.render_with_errors_async()
        return templates.TemplateResponse(
            request, 'signup.html', {'form_html': form_html}, status_code=422
        )
    form_html = await render_form_html_async(SignupForm, submit_url='/signup')
    context = {'form_html': form_html, 'name': result.data['name']}
    return templates.TemplateResponse(request, 'signup.html', context)
main.py
from pathlib import Path

from flask import Flask, render_template, request
from models import SignupForm

from pydantic_schemaforms import render_form_html

app = Flask(__name__, template_folder=Path(__file__).parent / 'templates')


@app.get('/signup')
def show_signup():
    form_html = render_form_html(SignupForm, submit_url='/signup')
    return render_template('signup.html', form_html=form_html)


@app.post('/signup')
def signup():
    result = SignupForm.validate(request.form, submit_url='/signup')
    if not result.is_valid:
        return render_template('signup.html', form_html=result.render_with_errors()), 422
    form_html = render_form_html(SignupForm, submit_url='/signup')
    return render_template('signup.html', form_html=form_html, name=result.data['name'])

How it works

  • {{ form_html | safe }} inserts the form. The form is HTML the library built and escaped itself, so it must not be escaped again.
  • Everything else stays escaped. {{ name }} is the user's input, and Jinja escapes it as usual. Only ever mark the library's output as safe.
  • The template loads Bootstrap, so the form is rendered without include_framework_assets. Load the same framework you render with; see Styling.
  • A failed submit answers 422 with the form and its errors, inside the same page.