Skip to content

Edit Form from a Database Row

Show a saved record in a form, let the user change it, and save it back.

models.py
from datetime import date
from enum import Enum

from pydantic_schemaforms import Field, FormModel


class Status(str, Enum):
    draft = 'draft'
    active = 'active'
    archived = 'archived'


class ProductForm(FormModel):
    name: str = Field(title='Name', min_length=2)
    price: float = Field(title='Price', ge=0)
    status: Status = Field(Status.draft, title='Status')
    launch_date: date | None = Field(None, title='Launch date')
    featured: bool = Field(False, title='Featured on the home page')


PRODUCTS = {  # stands in for your database
    1: {
        'id': 1,
        'name': 'Desk lamp',
        'price': 39.5,
        'status': Status.active,
        'launch_date': date(2026, 3, 1),
        'featured': True,
        'created_by': 'ada',
    },
}
main.py
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from models import PRODUCTS, ProductForm

from pydantic_schemaforms import render_form_html_async

app = FastAPI()
ASSETS = {'include_framework_assets': True, 'asset_mode': 'cdn'}


def load_product(product_id: int) -> dict:
    if product_id not in PRODUCTS:
        raise HTTPException(status_code=404)
    return PRODUCTS[product_id]


@app.get('/products/{product_id}/edit', response_class=HTMLResponse)
async def edit_product(product_id: int):
    row = load_product(product_id)
    return await render_form_html_async(
        ProductForm,
        form_data={name: row[name] for name in ProductForm.model_fields},
        submit_url=f'/products/{product_id}/edit',
        **ASSETS,
    )


@app.post('/products/{product_id}/edit', response_class=HTMLResponse)
async def save_product(product_id: int, request: Request):
    row = load_product(product_id)
    result = ProductForm.validate(
        await request.form(), submit_url=f'/products/{product_id}/edit', **ASSETS
    )
    if not result.is_valid:
        return await result.render_with_errors_async()
    row.update(result.data)
    return RedirectResponse(f'/products/{product_id}/edit', status_code=303)
main.py
from flask import Flask, abort, redirect, request
from models import PRODUCTS, ProductForm

from pydantic_schemaforms import render_form_html

app = Flask(__name__)
ASSETS = {'include_framework_assets': True, 'asset_mode': 'cdn'}


def load_product(product_id: int) -> dict:
    if product_id not in PRODUCTS:
        abort(404)
    return PRODUCTS[product_id]


@app.get('/products/<int:product_id>/edit')
def edit_product(product_id: int):
    row = load_product(product_id)
    return render_form_html(
        ProductForm,
        form_data={name: row[name] for name in ProductForm.model_fields},
        submit_url=f'/products/{product_id}/edit',
        **ASSETS,
    )


@app.post('/products/<int:product_id>/edit')
def save_product(product_id: int):
    row = load_product(product_id)
    result = ProductForm.validate(request.form, submit_url=f'/products/{product_id}/edit', **ASSETS)
    if not result.is_valid:
        return result.render_with_errors()
    row.update(result.data)
    return redirect(f'/products/{product_id}/edit', code=303)

How it works

  • form_data= fills the form. Pass the row's values as they are: dates, Enum members and numbers are shown correctly, so there's nothing to convert.
  • Only the form's fields are passed. The row also has id and created_by, which the user must not edit. They are left out of form_data, and since they aren't fields of ProductForm, a request that sends them anyway can't change them: validate() ignores unknown keys.
  • result.data has the same keys as the model, typed (Status.archived, date(2026, 3, 1), None for a cleared date), ready to write back.

If your rows are ORM objects or Pydantic models, build the dictionary the same way: {name: getattr(row, name) for name in ProductForm.model_fields}.