Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select form does not show PydanticCustomError on page #270

Open
vcvandersluis opened this issue Apr 11, 2024 · 0 comments
Open

Select form does not show PydanticCustomError on page #270

vcvandersluis opened this issue Apr 11, 2024 · 0 comments

Comments

@vcvandersluis
Copy link

In the forms demo (https://fastui-demo.onrender.com/forms/big).
The name input should start with a capital; otherwise, it will display the PydanticCustomError in red.

I want to implement this PydanticCustomError behavior for the multi-select component, where selecting both A and B together is not allowed.

My code:

# %% command
# uvicorn forms_standalone:router --reload --reload-dir . --port 8003

# %% imports
from __future__ import annotations as _annotations

import enum
from contextlib import asynccontextmanager
from typing import Annotated, ClassVar, List, Literal, TypeAlias, Union
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, PlainTextResponse
from fastui import AnyComponent, FastUI
from fastui import components as c
from fastui import prebuilt_html
from fastui.auth import fastapi_auth_exception_handling
from fastui.dev import dev_fastapi_app
from fastui.events import AuthEvent, GoToEvent, PageEvent
from fastui.forms import FormFile, SelectSearchResponse, Textarea, fastui_form
from httpx import AsyncClient
from pydantic import BaseModel, EmailStr, Field, SecretStr, field_validator
from pydantic_core import PydanticCustomError
# from shared import demo_page


@asynccontextmanager
async def lifespan(app_: FastAPI):
    async with AsyncClient() as client:
        app_.state.httpx_client = client
        yield

router =  dev_fastapi_app(lifespan=lifespan)
fastapi_auth_exception_handling(router)

FormKind: TypeAlias = Literal[ 'big']

def demo_page(*components: AnyComponent, title: str | None = None) -> list[AnyComponent]:
    return [
        c.PageTitle(text=f'FastUI Demo — {title}' if title else 'FastUI Demo'),
        c.Navbar(
            title='FastUI Demo',
            title_event=GoToEvent(url='/'),
            start_links=[
                c.Link(
                    components=[c.Text(text='Components')],
                    on_click=GoToEvent(url='/components'),
                    active='startswith:/components',
                ),
                c.Link(
                    components=[c.Text(text='Tables')],
                    on_click=GoToEvent(url='/table/cities'),
                    active='startswith:/table',
                ),
                c.Link(
                    components=[c.Text(text='Auth')],
                    on_click=GoToEvent(url='/auth/login/password'),
                    active='startswith:/auth',
                ),
                c.Link(
                    components=[c.Text(text='Forms')],
                    on_click=GoToEvent(url='/forms/login'),
                    active='startswith:/forms',
                ),
            ],
        ),
        c.Page(
            components=[
                *((c.Heading(text=title),) if title else ()),
                *components,
            ],
        ),
        c.Footer(
            extra_text='FastUI Demo',
            links=[
                c.Link(
                    components=[c.Text(text='Github')], on_click=GoToEvent(url='https://github.com/pydantic/FastUI')
                ),
                c.Link(components=[c.Text(text='PyPI')], on_click=GoToEvent(url='https://pypi.org/project/fastui/')),
                c.Link(components=[c.Text(text='NPM')], on_click=GoToEvent(url='https://www.npmjs.com/org/pydantic/')),
            ],
        ),
    ]



# %% Form class
class ClassesEnum(str, enum.Enum):
    a = 'A'
    b = 'B'
    c = 'C'
     
class BigModel(BaseModel):
    name: str | None = Field(None, description='This field is not required, it must start with a capital letter if provided')
    select_multiple_or_one: list[ClassesEnum] = Field(title='Select class(es)', description="test")

    @field_validator('name')
    def name_validator(cls, v: str | None) -> str:
        if v and v[0].islower():
            raise PydanticCustomError('lower', 'Name must start with a capital letter')
        return v
    
    @field_validator('select_multiple_or_one', mode='before')
    def correct_list_fields(cls, value: List[str] | str) -> List[str]:
        print(value)  
        if isinstance(value, list):
            if ClassesEnum.a in value and ClassesEnum.b in value :
                raise PydanticCustomError('AB error', 'A and B can not be selectected together')
        else:
            return [value]
        return value

# %% functions
@router.get('/api/content/{kind}', response_model=FastUI, response_model_exclude_none=True)
def forms_view(kind: FormKind) -> list[AnyComponent]:
    return demo_page(
        c.ServerLoad(
            path='/forms/content/{kind}',
            load_trigger=PageEvent(name='change-form'),
            components=form_content(kind),
        ),
        title='Forms',
    )

@router.post('/api/forms_submit/big', response_model=FastUI, response_model_exclude_none=True)
async def big_form_post(form: Annotated[BigModel, fastui_form(BigModel)]):
    print(form)
    return [c.FireEvent(event=GoToEvent(url='/'))] 
    
@router.get('/content/big', response_model=FastUI, response_model_exclude_none=True)
def form_content(kind: FormKind):
    return [
                c.Heading(text='Large Form', level=2),
                c.Paragraph(text='Form with a lot of fields.'),
                c.ModelForm(model=BigModel,
                             display_mode='page',
                               submit_url='/api/forms_submit/big'),
            ]

@router.get('/api/', response_model=FastUI, response_model_exclude_none=True)
def api_index() -> list[AnyComponent]:
    # language=markdown
    markdown = """\
* `ModelForm` — See [forms](/content/big)
"""
    return demo_page(c.Markdown(text=markdown))

@router.get('/{path:path}')
async def html_landing() -> HTMLResponse:
    return HTMLResponse(prebuilt_html(title='FastUI Demo'))

I noticed that the PydanticCustomError is present on the page. However, the message isn't being displayed.

<div class="invalid-feedback">A and B can not be selectected together</div>

See:

invalid_feedback not showen

Is it possible to display this PydanticCustomError on the page?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant