Contact Form¶
A contact form with a topic, a message and a hidden trap for spam bots.
models.py
from typing import Literal
from pydantic import EmailStr
from pydantic_schemaforms import Field, FormModel
class ContactForm(FormModel):
name: str = Field(title='Your name', min_length=2, ui_autofocus=True)
email: EmailStr = Field(title='Email')
topic: Literal['Question', 'Feedback', 'Bug report'] = Field('Question', title='Topic')
message: str = Field(title='Message', min_length=10, max_length=2000, ui_element='textarea')
website: str = Field('', ui_element='honeypot') # hidden; only bots fill it in
main.py
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from models import ContactForm
from pydantic_schemaforms import html, render_form_html_async
app = FastAPI()
FORM_OPTIONS = {'submit_url': '/contact', 'include_framework_assets': True, 'asset_mode': 'cdn'}
def send_message(data: dict) -> None: ... # email it, store it, open a ticket
@app.get('/contact', response_class=HTMLResponse)
async def show_contact():
return await render_form_html_async(ContactForm, **FORM_OPTIONS)
@app.post('/contact', response_class=HTMLResponse)
async def submit_contact(request: Request):
result = ContactForm.validate(await request.form(), **FORM_OPTIONS)
if not result.is_valid:
return await result.render_with_errors_async()
if not result.data['website']:
send_message(result.data)
name = result.data['name']
return html(t'<p>Thanks, {name}. We will get back to you soon.</p>')
main.py
from flask import Flask, request
from models import ContactForm
from pydantic_schemaforms import html, render_form_html
app = Flask(__name__)
FORM_OPTIONS = {'submit_url': '/contact', 'include_framework_assets': True, 'asset_mode': 'cdn'}
def send_message(data: dict) -> None: ... # email it, store it, open a ticket
@app.get('/contact')
def show_contact():
return render_form_html(ContactForm, **FORM_OPTIONS)
@app.post('/contact')
def submit_contact():
result = ContactForm.validate(request.form, **FORM_OPTIONS)
if not result.is_valid:
return result.render_with_errors()
if not result.data['website']:
send_message(result.data)
name = result.data['name']
return html(t'<p>Thanks, {name}. We will get back to you soon.</p>')
How it works¶
topic: Literal[...]becomes a dropdown of the three topics, with "Question" selected by default.max_length=2000on the message limits it in the browser and on the server.ui_element="honeypot"renders a text box that people can't see or tab to, but bots that fill in every input will. When it comes back filled in, the route answers as if the message was sent and drops it, so the bot learns nothing.
A honeypot stops simple bots. For more, add a captcha field; see
Input Types.