The fastest way to create HTML apps - a next-generation Python web framework for building fast, scalable web applications with minimal code
—
Comprehensive form handling including data conversion, validation, file uploads, and dataclass integration.
Convert and process form data from HTTP requests into Python objects.
def form2dict(form) -> dict:
"""
Convert FormData to dictionary.
Processes multipart form data and URL-encoded form data
into Python dictionary format for easy handling.
Args:
form: FormData object from HTTP request
Returns:
dict: Converted form data with string keys and values
"""Fill forms with data objects and bind data to form fields.
def fill_form(form, obj):
"""
Fill form with data object.
Populates form fields with values from an object,
matching field names to object attributes.
Args:
form: HTML form element to populate
obj: Object with data to fill form fields
Returns:
Form element with populated values
"""
def fill_dataclass(src, dest):
"""
Fill dataclass from source object.
Populates dataclass fields from source object attributes,
useful for form data binding and validation.
Args:
src: Source object with data
dest: Destination dataclass to populate
Returns:
Populated dataclass instance
"""Find and analyze form elements for processing and validation.
def find_inputs(form):
"""
Find input elements in form.
Scans form element tree to locate all input, select,
and textarea elements for processing.
Args:
form: HTML form element to scan
Returns:
list: List of input elements found in form
"""Handle file uploads with proper encoding and validation.
def File(*c, **kw):
"""
Handle file uploads in forms.
Creates file input element with proper multipart encoding
and file handling capabilities.
Args:
*c: Child content (usually none for file inputs)
**kw: Input attributes including:
- name: Field name for form submission
- accept: Allowed file types (e.g., '.jpg,.png,.pdf')
- multiple: Allow multiple file selection
- required: Make field required
Returns:
File input element with upload handling
"""Extended form components with additional functionality and validation.
def Hidden(*c, **kw):
"""
Hidden input field.
Creates hidden input for storing form state and data
that should not be visible to users.
Args:
*c: Content (usually none)
**kw: Input attributes (name, value, etc.)
Returns:
Hidden input element
"""
def CheckboxX(*c, **kw):
"""
Enhanced checkbox with label.
Creates checkbox input with associated label
for better accessibility and styling.
Args:
*c: Label content
**kw: Input attributes and styling options
Returns:
Checkbox with label wrapper
"""from fasthtml.common import *
app, rt = fast_app()
@rt('/contact')
def contact_form():
return Titled("Contact Form",
Form(
Div(
Label("Name:", for_="name"),
Input(type="text", name="name", id="name", required=True),
cls="form-group"
),
Div(
Label("Email:", for_="email"),
Input(type="email", name="email", id="email", required=True),
cls="form-group"
),
Div(
Label("Message:", for_="message"),
Textarea(name="message", id="message", rows="5", required=True),
cls="form-group"
),
Button("Send Message", type="submit"),
method="post",
action="/contact/submit"
)
)
@rt('/contact/submit', methods=['POST'])
def submit_contact(request):
# Convert form data to dictionary
form_data = form2dict(await request.form())
# Process the form data
name = form_data.get('name', '')
email = form_data.get('email', '')
message = form_data.get('message', '')
# Validate and save (simplified)
if not all([name, email, message]):
return Div("All fields are required", style="color: red;")
# Save to database or send email here
return Div(
H2("Thank you!"),
P(f"Thanks {name}, we received your message and will respond to {email} soon."),
A("Send another message", href="/contact")
)from fasthtml.common import *
app, rt = fast_app()
@rt('/upload')
def upload_form():
return Titled("File Upload",
Form(
Div(
Label("Profile Picture:", for_="profile"),
File(
name="profile",
id="profile",
accept=".jpg,.jpeg,.png,.gif",
required=True
),
cls="form-group"
),
Div(
Label("Documents:", for_="documents"),
File(
name="documents",
id="documents",
accept=".pdf,.doc,.docx,.txt",
multiple=True
),
cls="form-group"
),
Div(
Label("Description:", for_="description"),
Textarea(name="description", id="description", rows="3"),
cls="form-group"
),
Button("Upload Files", type="submit"),
method="post",
action="/upload/process",
enctype="multipart/form-data"
)
)
@rt('/upload/process', methods=['POST'])
def process_upload(request):
form = await request.form()
form_data = form2dict(form)
# Handle uploaded files
files_info = []
# Profile picture (single file)
if 'profile' in form:
profile_file = form['profile']
if profile_file.filename:
# Save file and get info
filename = save_uploaded_file(profile_file, 'profiles')
files_info.append(f"Profile: {filename}")
# Documents (multiple files)
if 'documents' in form:
documents = form.getlist('documents')
for doc in documents:
if doc.filename:
filename = save_uploaded_file(doc, 'documents')
files_info.append(f"Document: {filename}")
return Div(
H2("Files Uploaded Successfully"),
Ul(*[Li(info) for info in files_info]),
P(f"Description: {form_data.get('description', 'None provided')}"),
A("Upload more files", href="/upload")
)
def save_uploaded_file(file, folder):
"""Helper function to save uploaded files"""
import os
import uuid
# Create unique filename
file_ext = os.path.splitext(file.filename)[1]
unique_filename = f"{uuid.uuid4()}{file_ext}"
# Save file (simplified - add proper error handling)
save_path = f"uploads/{folder}/{unique_filename}"
os.makedirs(os.path.dirname(save_path), exist_ok=True)
with open(save_path, "wb") as f:
f.write(file.file.read())
return unique_filenamefrom fasthtml.common import *
from dataclasses import dataclass
from typing import Optional
@dataclass
class UserRegistration:
username: str
email: str
password: str
confirm_password: str
newsletter: bool = False
age: Optional[int] = None
app, rt = fast_app()
@rt('/register')
def registration_form(errors: dict = None):
errors = errors or {}
return Titled("User Registration",
Form(
Div(
Label("Username:", for_="username"),
Input(
type="text",
name="username",
id="username",
required=True,
hx_post="/validate/username",
hx_target="#username-error",
hx_trigger="blur"
),
Div(id="username-error",
errors.get('username', ''),
style="color: red;" if errors.get('username') else ""
),
cls="form-group"
),
Div(
Label("Email:", for_="email"),
Input(
type="email",
name="email",
id="email",
required=True,
hx_post="/validate/email",
hx_target="#email-error",
hx_trigger="blur"
),
Div(id="email-error",
errors.get('email', ''),
style="color: red;" if errors.get('email') else ""
),
cls="form-group"
),
Div(
Label("Password:", for_="password"),
Input(type="password", name="password", id="password", required=True),
Div(id="password-error",
errors.get('password', ''),
style="color: red;" if errors.get('password') else ""
),
cls="form-group"
),
Div(
Label("Confirm Password:", for_="confirm_password"),
Input(type="password", name="confirm_password", id="confirm_password", required=True),
Div(id="confirm-password-error",
errors.get('confirm_password', ''),
style="color: red;" if errors.get('confirm_password') else ""
),
cls="form-group"
),
Div(
Label("Age (optional):", for_="age"),
Input(type="number", name="age", id="age", min="13", max="120"),
cls="form-group"
),
Div(
Label(
CheckboxX(name="newsletter", value="yes"),
" Subscribe to newsletter"
),
cls="form-group"
),
Button("Register", type="submit"),
method="post",
action="/register/submit"
)
)
@rt('/validate/username', methods=['POST'])
def validate_username(username: str):
if len(username) < 3:
return Div("Username must be at least 3 characters", style="color: red;")
elif username.lower() in ['admin', 'root', 'user', 'test']:
return Div("Username not available", style="color: red;")
else:
return Div("✓ Username available", style="color: green;")
@rt('/validate/email', methods=['POST'])
def validate_email(email: str):
import re
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, email):
return Div("Invalid email format", style="color: red;")
else:
return Div("✓ Email format valid", style="color: green;")
@rt('/register/submit', methods=['POST'])
def submit_registration(request):
form_data = form2dict(await request.form())
errors = {}
# Validation
if len(form_data.get('username', '')) < 3:
errors['username'] = "Username must be at least 3 characters"
if not form_data.get('email'):
errors['email'] = "Email is required"
password = form_data.get('password', '')
confirm_password = form_data.get('confirm_password', '')
if len(password) < 8:
errors['password'] = "Password must be at least 8 characters"
if password != confirm_password:
errors['confirm_password'] = "Passwords do not match"
# If validation fails, show form with errors
if errors:
return registration_form(errors)
# Create user registration object
registration = UserRegistration(
username=form_data['username'],
email=form_data['email'],
password=form_data['password'],
confirm_password=form_data['confirm_password'],
newsletter=form_data.get('newsletter') == 'yes',
age=int(form_data['age']) if form_data.get('age') else None
)
# Save registration (simplified)
# save_user_registration(registration)
return Div(
H2("Registration Successful!"),
P(f"Welcome {registration.username}!"),
P(f"We've sent a confirmation email to {registration.email}"),
A("Login", href="/login")
)from fasthtml.common import *
app, rt = fast_app(session_cookie='multistep')
@rt('/multistep')
def multistep_form(step: int = 1):
session = request.session
if step == 1:
return step1_form()
elif step == 2:
return step2_form(session)
elif step == 3:
return step3_form(session)
else:
return Redirect('/multistep')
def step1_form():
return Titled("Step 1: Personal Information",
Form(
H2("Personal Information"),
Div(
Label("First Name:", for_="first_name"),
Input(type="text", name="first_name", required=True),
cls="form-group"
),
Div(
Label("Last Name:", for_="last_name"),
Input(type="text", name="last_name", required=True),
cls="form-group"
),
Div(
Label("Date of Birth:", for_="birth_date"),
Input(type="date", name="birth_date", required=True),
cls="form-group"
),
Button("Next", type="submit"),
method="post",
action="/multistep/step1"
)
)
@rt('/multistep/step1', methods=['POST'])
def process_step1(request):
form_data = form2dict(await request.form())
request.session['step1'] = form_data
return Redirect('/multistep?step=2')
def step2_form(session):
return Titled("Step 2: Contact Information",
Form(
H2("Contact Information"),
P(f"Hello {session.get('step1', {}).get('first_name', 'there')}!"),
Div(
Label("Email:", for_="email"),
Input(type="email", name="email", required=True),
cls="form-group"
),
Div(
Label("Phone:", for_="phone"),
Input(type="tel", name="phone"),
cls="form-group"
),
Div(
Label("Address:", for_="address"),
Textarea(name="address", rows="3"),
cls="form-group"
),
Div(
Button("Previous", type="button", onclick="location.href='/multistep?step=1'"),
Button("Next", type="submit"),
cls="form-buttons"
),
method="post",
action="/multistep/step2"
)
)
@rt('/multistep/step2', methods=['POST'])
def process_step2(request):
form_data = form2dict(await request.form())
request.session['step2'] = form_data
return Redirect('/multistep?step=3')
def step3_form(session):
step1_data = session.get('step1', {})
step2_data = session.get('step2', {})
return Titled("Step 3: Review and Submit",
Div(
H2("Review Your Information"),
H3("Personal Information"),
P(f"Name: {step1_data.get('first_name', '')} {step1_data.get('last_name', '')}"),
P(f"Date of Birth: {step1_data.get('birth_date', '')}"),
H3("Contact Information"),
P(f"Email: {step2_data.get('email', '')}"),
P(f"Phone: {step2_data.get('phone', 'Not provided')}"),
P(f"Address: {step2_data.get('address', 'Not provided')}"),
Form(
Div(
Label(
CheckboxX(name="terms", value="agreed", required=True),
" I agree to the terms and conditions"
),
cls="form-group"
),
Div(
Button("Previous", type="button", onclick="location.href='/multistep?step=2'"),
Button("Submit", type="submit"),
cls="form-buttons"
),
method="post",
action="/multistep/submit"
)
)
)
@rt('/multistep/submit', methods=['POST'])
def submit_multistep(request):
# Combine all form data
step1_data = request.session.get('step1', {})
step2_data = request.session.get('step2', {})
# Process final submission
complete_data = {**step1_data, **step2_data}
# Save to database here
# Clear session
request.session.clear()
return Div(
H2("Submission Complete!"),
P("Thank you for completing the form."),
P("Your information has been saved successfully."),
A("Start Over", href="/multistep")
)Install with Tessl CLI
npx tessl i tessl/pypi-python-fasthtml