Skip to content

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=2000 on 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.