Skip to content

Commit

Permalink
Forms, WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Nov 13, 2023
1 parent 4220a45 commit 4c45207
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 14 deletions.
28 changes: 23 additions & 5 deletions demo/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from pydantic import BaseModel, Field

from fastui import components as c
from fastui import FastUI, PageEvent, GoToEvent, Display, AnyComponent
from fastui import FastUI, AnyComponent
from fastui.display import Display
from fastui.events import PageEvent, GoToEvent

app = FastAPI()

Expand Down Expand Up @@ -40,7 +42,7 @@ class MyTableRow(BaseModel):


@app.get('/api/table', response_model=FastUI, response_model_exclude_none=True)
def read_foo() -> AnyComponent:
def table_view() -> AnyComponent:
return c.Page(
children=[
c.Heading(text='Table'),
Expand All @@ -51,10 +53,26 @@ def read_foo() -> AnyComponent:
MyTableRow(id=3, name='Jack', dob=date(1992, 1, 1)),
],
columns=[
c.Column(field='name', on_click=GoToEvent(url='/api/more/{id}/')),
c.Column(field='dob', display=Display.date),
c.Column(field='enabled'),
c.TableColumn(field='name', on_click=GoToEvent(url='/api/more/{id}/')),
c.TableColumn(field='dob', display=Display.date),
c.TableColumn(field='enabled'),
]
)
]
)


class MyFormModel(BaseModel):
name: str = Field(title='Name')
dob: date = Field(title='Date of Birth')
enabled: bool | None = None


@app.get('/api/form', response_model=FastUI, response_model_exclude_none=True)
def form_view() -> AnyComponent:
return c.Page(
children=[
c.Heading(text='Form'),
c.Form[MyFormModel]()
]
)
9 changes: 3 additions & 6 deletions python/fastui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from .. import events
from . import extra
from .table import Table
from .tables import Table

if typing.TYPE_CHECKING:
import pydantic.fields
Expand Down Expand Up @@ -61,7 +61,7 @@ class Col(pydantic.BaseModel):

class Button(pydantic.BaseModel):
text: str
on_click: events.Event | None = pydantic.Field(None, serialization_alias='onClick')
on_click: events.Event | None = pydantic.Field(default=None, serialization_alias='onClick')
class_name: extra.ClassName | None = None
type: typing.Literal['Button'] = 'Button'

Expand All @@ -70,15 +70,12 @@ class Modal(pydantic.BaseModel):
title: str
body: list[AnyComponent]
footer: list[AnyComponent] | None = None
open_trigger: events.PageEvent | None = pydantic.Field(None, serialization_alias='openTrigger')
open_trigger: events.PageEvent | None = pydantic.Field(default=None, serialization_alias='openTrigger')
open: bool = False
class_name: extra.ClassName | None = None
type: typing.Literal['Modal'] = 'Modal'


PydanticModel = typing.TypeVar('PydanticModel', bound=pydantic.BaseModel)


AnyComponent = typing.Annotated[
Text | Div | Page | Heading | Row | Col | Button | Modal | Table, pydantic.Field(discriminator='type')
]
44 changes: 44 additions & 0 deletions python/fastui/components/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import typing

import pydantic

from .. import events
from . import extra

FormModel = typing.TypeVar('FormModel', bound=pydantic.BaseModel)


class FormHelp(pydantic.BaseModel):
text: str
class_name: extra.ClassName | None = None


class FormError(pydantic.BaseModel):
text: str
class_name: extra.ClassName | None = None


class Form(pydantic.BaseModel, typing.Generic[FormModel]):
submit_trigger: events.PageEvent | None = pydantic.Field(default=None, serialization_alias='submitTrigger')
submit_url: str | None = pydantic.Field(default=None, serialization_alias='submitUrl')
next_url: str | None = pydantic.Field(default=None, serialization_alias='nextUrl')
title: str | None = pydantic.Field(default=None, exclude=True)
help: dict[str, FormHelp] | None = None
errors: dict[str, FormError] | None = None
class_name: extra.ClassName | None = None
type_: typing.Literal['Form'] = pydantic.Field('Form', serialization_alias='type')

@pydantic.computed_field()
def form_json_schema(self) -> dict[str, typing.Any]:
args = self.__pydantic_generic_metadata__['args']
try:
model: type[FormModel] = args[0]
except IndexError:
raise ValueError('`Form` must be parameterized with a pydantic model, i.e. `Form[MyModel]()`.')

if not issubclass(model, pydantic.BaseModel):
raise TypeError('`Form` must be parameterized with a pydantic model, i.e. `Form[MyModel]()`.')
form_schema = model.model_json_schema()
if self.title is not None:
form_schema['title'] = self.title
return form_schema
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
DataModel = typing.TypeVar('DataModel', bound=pydantic.BaseModel)


class Column(pydantic.BaseModel):
class TableColumn(pydantic.BaseModel):
"""
Description of a table column.
"""
Expand All @@ -26,7 +26,7 @@ class Column(pydantic.BaseModel):

class Table(pydantic.BaseModel, typing.Generic[DataModel]):
data: list[DataModel]
columns: list[Column] | None = None
columns: list[TableColumn] | None = None
# TODO pagination
class_name: extra.ClassName | None = None
type: typing.Literal['Table'] = 'Table'
Expand All @@ -39,7 +39,9 @@ def fill_columns(self) -> typing.Self:
return self

if self.columns is None:
self.columns = [Column(field=name, title=field.title) for name, field in data_model_0.model_fields.items()]
self.columns = [
TableColumn(field=name, title=field.title) for name, field in data_model_0.model_fields.items()
]
else:
# add pydantic titles to columns that don't have them
for column in (c for c in self.columns if c.title is None):
Expand Down

0 comments on commit 4c45207

Please sign in to comment.