or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-routing.mdauthentication.mdcss-styling.mddevelopment-tools.mdform-handling.mdhtml-components.mdhtmx-integration.mdindex.mdjavascript-integration.mdnotifications.mdsvg-components.md

form-handling.mddocs/

0

# Form Handling and Data Processing

1

2

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

3

4

## Capabilities

5

6

### Form Data Processing

7

8

Convert and process form data from HTTP requests into Python objects.

9

10

```python { .api }

11

def form2dict(form) -> dict:

12

"""

13

Convert FormData to dictionary.

14

15

Processes multipart form data and URL-encoded form data

16

into Python dictionary format for easy handling.

17

18

Args:

19

form: FormData object from HTTP request

20

21

Returns:

22

dict: Converted form data with string keys and values

23

"""

24

```

25

26

### Form Population and Data Binding

27

28

Fill forms with data objects and bind data to form fields.

29

30

```python { .api }

31

def fill_form(form, obj):

32

"""

33

Fill form with data object.

34

35

Populates form fields with values from an object,

36

matching field names to object attributes.

37

38

Args:

39

form: HTML form element to populate

40

obj: Object with data to fill form fields

41

42

Returns:

43

Form element with populated values

44

"""

45

46

def fill_dataclass(src, dest):

47

"""

48

Fill dataclass from source object.

49

50

Populates dataclass fields from source object attributes,

51

useful for form data binding and validation.

52

53

Args:

54

src: Source object with data

55

dest: Destination dataclass to populate

56

57

Returns:

58

Populated dataclass instance

59

"""

60

```

61

62

### Form Element Discovery

63

64

Find and analyze form elements for processing and validation.

65

66

```python { .api }

67

def find_inputs(form):

68

"""

69

Find input elements in form.

70

71

Scans form element tree to locate all input, select,

72

and textarea elements for processing.

73

74

Args:

75

form: HTML form element to scan

76

77

Returns:

78

list: List of input elements found in form

79

"""

80

```

81

82

### File Upload Handling

83

84

Handle file uploads with proper encoding and validation.

85

86

```python { .api }

87

def File(*c, **kw):

88

"""

89

Handle file uploads in forms.

90

91

Creates file input element with proper multipart encoding

92

and file handling capabilities.

93

94

Args:

95

*c: Child content (usually none for file inputs)

96

**kw: Input attributes including:

97

- name: Field name for form submission

98

- accept: Allowed file types (e.g., '.jpg,.png,.pdf')

99

- multiple: Allow multiple file selection

100

- required: Make field required

101

102

Returns:

103

File input element with upload handling

104

"""

105

```

106

107

### Enhanced Form Components

108

109

Extended form components with additional functionality and validation.

110

111

```python { .api }

112

def Hidden(*c, **kw):

113

"""

114

Hidden input field.

115

116

Creates hidden input for storing form state and data

117

that should not be visible to users.

118

119

Args:

120

*c: Content (usually none)

121

**kw: Input attributes (name, value, etc.)

122

123

Returns:

124

Hidden input element

125

"""

126

127

def CheckboxX(*c, **kw):

128

"""

129

Enhanced checkbox with label.

130

131

Creates checkbox input with associated label

132

for better accessibility and styling.

133

134

Args:

135

*c: Label content

136

**kw: Input attributes and styling options

137

138

Returns:

139

Checkbox with label wrapper

140

"""

141

```

142

143

## Usage Examples

144

145

### Basic Form Processing

146

147

```python

148

from fasthtml.common import *

149

150

app, rt = fast_app()

151

152

@rt('/contact')

153

def contact_form():

154

return Titled("Contact Form",

155

Form(

156

Div(

157

Label("Name:", for_="name"),

158

Input(type="text", name="name", id="name", required=True),

159

cls="form-group"

160

),

161

Div(

162

Label("Email:", for_="email"),

163

Input(type="email", name="email", id="email", required=True),

164

cls="form-group"

165

),

166

Div(

167

Label("Message:", for_="message"),

168

Textarea(name="message", id="message", rows="5", required=True),

169

cls="form-group"

170

),

171

Button("Send Message", type="submit"),

172

method="post",

173

action="/contact/submit"

174

)

175

)

176

177

@rt('/contact/submit', methods=['POST'])

178

def submit_contact(request):

179

# Convert form data to dictionary

180

form_data = form2dict(await request.form())

181

182

# Process the form data

183

name = form_data.get('name', '')

184

email = form_data.get('email', '')

185

message = form_data.get('message', '')

186

187

# Validate and save (simplified)

188

if not all([name, email, message]):

189

return Div("All fields are required", style="color: red;")

190

191

# Save to database or send email here

192

193

return Div(

194

H2("Thank you!"),

195

P(f"Thanks {name}, we received your message and will respond to {email} soon."),

196

A("Send another message", href="/contact")

197

)

198

```

199

200

### File Upload Form

201

202

```python

203

from fasthtml.common import *

204

205

app, rt = fast_app()

206

207

@rt('/upload')

208

def upload_form():

209

return Titled("File Upload",

210

Form(

211

Div(

212

Label("Profile Picture:", for_="profile"),

213

File(

214

name="profile",

215

id="profile",

216

accept=".jpg,.jpeg,.png,.gif",

217

required=True

218

),

219

cls="form-group"

220

),

221

Div(

222

Label("Documents:", for_="documents"),

223

File(

224

name="documents",

225

id="documents",

226

accept=".pdf,.doc,.docx,.txt",

227

multiple=True

228

),

229

cls="form-group"

230

),

231

Div(

232

Label("Description:", for_="description"),

233

Textarea(name="description", id="description", rows="3"),

234

cls="form-group"

235

),

236

Button("Upload Files", type="submit"),

237

method="post",

238

action="/upload/process",

239

enctype="multipart/form-data"

240

)

241

)

242

243

@rt('/upload/process', methods=['POST'])

244

def process_upload(request):

245

form = await request.form()

246

form_data = form2dict(form)

247

248

# Handle uploaded files

249

files_info = []

250

251

# Profile picture (single file)

252

if 'profile' in form:

253

profile_file = form['profile']

254

if profile_file.filename:

255

# Save file and get info

256

filename = save_uploaded_file(profile_file, 'profiles')

257

files_info.append(f"Profile: {filename}")

258

259

# Documents (multiple files)

260

if 'documents' in form:

261

documents = form.getlist('documents')

262

for doc in documents:

263

if doc.filename:

264

filename = save_uploaded_file(doc, 'documents')

265

files_info.append(f"Document: {filename}")

266

267

return Div(

268

H2("Files Uploaded Successfully"),

269

Ul(*[Li(info) for info in files_info]),

270

P(f"Description: {form_data.get('description', 'None provided')}"),

271

A("Upload more files", href="/upload")

272

)

273

274

def save_uploaded_file(file, folder):

275

"""Helper function to save uploaded files"""

276

import os

277

import uuid

278

279

# Create unique filename

280

file_ext = os.path.splitext(file.filename)[1]

281

unique_filename = f"{uuid.uuid4()}{file_ext}"

282

283

# Save file (simplified - add proper error handling)

284

save_path = f"uploads/{folder}/{unique_filename}"

285

os.makedirs(os.path.dirname(save_path), exist_ok=True)

286

287

with open(save_path, "wb") as f:

288

f.write(file.file.read())

289

290

return unique_filename

291

```

292

293

### Dynamic Form with Validation

294

295

```python

296

from fasthtml.common import *

297

from dataclasses import dataclass

298

from typing import Optional

299

300

@dataclass

301

class UserRegistration:

302

username: str

303

email: str

304

password: str

305

confirm_password: str

306

newsletter: bool = False

307

age: Optional[int] = None

308

309

app, rt = fast_app()

310

311

@rt('/register')

312

def registration_form(errors: dict = None):

313

errors = errors or {}

314

315

return Titled("User Registration",

316

Form(

317

Div(

318

Label("Username:", for_="username"),

319

Input(

320

type="text",

321

name="username",

322

id="username",

323

required=True,

324

hx_post="/validate/username",

325

hx_target="#username-error",

326

hx_trigger="blur"

327

),

328

Div(id="username-error",

329

errors.get('username', ''),

330

style="color: red;" if errors.get('username') else ""

331

),

332

cls="form-group"

333

),

334

Div(

335

Label("Email:", for_="email"),

336

Input(

337

type="email",

338

name="email",

339

id="email",

340

required=True,

341

hx_post="/validate/email",

342

hx_target="#email-error",

343

hx_trigger="blur"

344

),

345

Div(id="email-error",

346

errors.get('email', ''),

347

style="color: red;" if errors.get('email') else ""

348

),

349

cls="form-group"

350

),

351

Div(

352

Label("Password:", for_="password"),

353

Input(type="password", name="password", id="password", required=True),

354

Div(id="password-error",

355

errors.get('password', ''),

356

style="color: red;" if errors.get('password') else ""

357

),

358

cls="form-group"

359

),

360

Div(

361

Label("Confirm Password:", for_="confirm_password"),

362

Input(type="password", name="confirm_password", id="confirm_password", required=True),

363

Div(id="confirm-password-error",

364

errors.get('confirm_password', ''),

365

style="color: red;" if errors.get('confirm_password') else ""

366

),

367

cls="form-group"

368

),

369

Div(

370

Label("Age (optional):", for_="age"),

371

Input(type="number", name="age", id="age", min="13", max="120"),

372

cls="form-group"

373

),

374

Div(

375

Label(

376

CheckboxX(name="newsletter", value="yes"),

377

" Subscribe to newsletter"

378

),

379

cls="form-group"

380

),

381

Button("Register", type="submit"),

382

method="post",

383

action="/register/submit"

384

)

385

)

386

387

@rt('/validate/username', methods=['POST'])

388

def validate_username(username: str):

389

if len(username) < 3:

390

return Div("Username must be at least 3 characters", style="color: red;")

391

elif username.lower() in ['admin', 'root', 'user', 'test']:

392

return Div("Username not available", style="color: red;")

393

else:

394

return Div("✓ Username available", style="color: green;")

395

396

@rt('/validate/email', methods=['POST'])

397

def validate_email(email: str):

398

import re

399

email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'

400

401

if not re.match(email_pattern, email):

402

return Div("Invalid email format", style="color: red;")

403

else:

404

return Div("✓ Email format valid", style="color: green;")

405

406

@rt('/register/submit', methods=['POST'])

407

def submit_registration(request):

408

form_data = form2dict(await request.form())

409

errors = {}

410

411

# Validation

412

if len(form_data.get('username', '')) < 3:

413

errors['username'] = "Username must be at least 3 characters"

414

415

if not form_data.get('email'):

416

errors['email'] = "Email is required"

417

418

password = form_data.get('password', '')

419

confirm_password = form_data.get('confirm_password', '')

420

421

if len(password) < 8:

422

errors['password'] = "Password must be at least 8 characters"

423

424

if password != confirm_password:

425

errors['confirm_password'] = "Passwords do not match"

426

427

# If validation fails, show form with errors

428

if errors:

429

return registration_form(errors)

430

431

# Create user registration object

432

registration = UserRegistration(

433

username=form_data['username'],

434

email=form_data['email'],

435

password=form_data['password'],

436

confirm_password=form_data['confirm_password'],

437

newsletter=form_data.get('newsletter') == 'yes',

438

age=int(form_data['age']) if form_data.get('age') else None

439

)

440

441

# Save registration (simplified)

442

# save_user_registration(registration)

443

444

return Div(

445

H2("Registration Successful!"),

446

P(f"Welcome {registration.username}!"),

447

P(f"We've sent a confirmation email to {registration.email}"),

448

A("Login", href="/login")

449

)

450

```

451

452

### Multi-Step Form

453

454

```python

455

from fasthtml.common import *

456

457

app, rt = fast_app(session_cookie='multistep')

458

459

@rt('/multistep')

460

def multistep_form(step: int = 1):

461

session = request.session

462

463

if step == 1:

464

return step1_form()

465

elif step == 2:

466

return step2_form(session)

467

elif step == 3:

468

return step3_form(session)

469

else:

470

return Redirect('/multistep')

471

472

def step1_form():

473

return Titled("Step 1: Personal Information",

474

Form(

475

H2("Personal Information"),

476

Div(

477

Label("First Name:", for_="first_name"),

478

Input(type="text", name="first_name", required=True),

479

cls="form-group"

480

),

481

Div(

482

Label("Last Name:", for_="last_name"),

483

Input(type="text", name="last_name", required=True),

484

cls="form-group"

485

),

486

Div(

487

Label("Date of Birth:", for_="birth_date"),

488

Input(type="date", name="birth_date", required=True),

489

cls="form-group"

490

),

491

Button("Next", type="submit"),

492

method="post",

493

action="/multistep/step1"

494

)

495

)

496

497

@rt('/multistep/step1', methods=['POST'])

498

def process_step1(request):

499

form_data = form2dict(await request.form())

500

request.session['step1'] = form_data

501

return Redirect('/multistep?step=2')

502

503

def step2_form(session):

504

return Titled("Step 2: Contact Information",

505

Form(

506

H2("Contact Information"),

507

P(f"Hello {session.get('step1', {}).get('first_name', 'there')}!"),

508

Div(

509

Label("Email:", for_="email"),

510

Input(type="email", name="email", required=True),

511

cls="form-group"

512

),

513

Div(

514

Label("Phone:", for_="phone"),

515

Input(type="tel", name="phone"),

516

cls="form-group"

517

),

518

Div(

519

Label("Address:", for_="address"),

520

Textarea(name="address", rows="3"),

521

cls="form-group"

522

),

523

Div(

524

Button("Previous", type="button", onclick="location.href='/multistep?step=1'"),

525

Button("Next", type="submit"),

526

cls="form-buttons"

527

),

528

method="post",

529

action="/multistep/step2"

530

)

531

)

532

533

@rt('/multistep/step2', methods=['POST'])

534

def process_step2(request):

535

form_data = form2dict(await request.form())

536

request.session['step2'] = form_data

537

return Redirect('/multistep?step=3')

538

539

def step3_form(session):

540

step1_data = session.get('step1', {})

541

step2_data = session.get('step2', {})

542

543

return Titled("Step 3: Review and Submit",

544

Div(

545

H2("Review Your Information"),

546

547

H3("Personal Information"),

548

P(f"Name: {step1_data.get('first_name', '')} {step1_data.get('last_name', '')}"),

549

P(f"Date of Birth: {step1_data.get('birth_date', '')}"),

550

551

H3("Contact Information"),

552

P(f"Email: {step2_data.get('email', '')}"),

553

P(f"Phone: {step2_data.get('phone', 'Not provided')}"),

554

P(f"Address: {step2_data.get('address', 'Not provided')}"),

555

556

Form(

557

Div(

558

Label(

559

CheckboxX(name="terms", value="agreed", required=True),

560

" I agree to the terms and conditions"

561

),

562

cls="form-group"

563

),

564

Div(

565

Button("Previous", type="button", onclick="location.href='/multistep?step=2'"),

566

Button("Submit", type="submit"),

567

cls="form-buttons"

568

),

569

method="post",

570

action="/multistep/submit"

571

)

572

)

573

)

574

575

@rt('/multistep/submit', methods=['POST'])

576

def submit_multistep(request):

577

# Combine all form data

578

step1_data = request.session.get('step1', {})

579

step2_data = request.session.get('step2', {})

580

581

# Process final submission

582

complete_data = {**step1_data, **step2_data}

583

584

# Save to database here

585

586

# Clear session

587

request.session.clear()

588

589

return Div(

590

H2("Submission Complete!"),

591

P("Thank you for completing the form."),

592

P("Your information has been saved successfully."),

593

A("Start Over", href="/multistep")

594

)

595

```