CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-python-fasthtml

The fastest way to create HTML apps - a next-generation Python web framework for building fast, scalable web applications with minimal code

Pending
Overview
Eval results
Files

form-handling.mddocs/

Form Handling and Data Processing

Comprehensive form handling including data conversion, validation, file uploads, and dataclass integration.

Capabilities

Form Data Processing

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
    """

Form Population and Data Binding

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
    """

Form Element Discovery

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
    """

File Upload Handling

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
    """

Enhanced Form Components

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
    """

Usage Examples

Basic Form Processing

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")
    )

File Upload Form

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_filename

Dynamic Form with Validation

from 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")
    )

Multi-Step Form

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

docs

application-routing.md

authentication.md

css-styling.md

development-tools.md

form-handling.md

html-components.md

htmx-integration.md

index.md

javascript-integration.md

notifications.md

svg-components.md

tile.json