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 assafe. - 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.