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

ModelForm submit_on_change does not always work when coupled with tabs #330

Open
tim-x-y-z opened this issue Jun 3, 2024 · 0 comments
Open

Comments

@tim-x-y-z
Copy link
Contributor

I am trying to combine using tabs and a model form to vary 2 variables and when switching tabs the submit on change of the model form seems to stop working. But then, refreshing the page works well but swtiching tabs again breaks it again. see the video below for a more "ui" explanation of the issue.
Let me know if i should try to explain more; also included a code snippet to reproduce.
Also, sorry if i used one of the component in a wrong way and it forces this error.

video

Screen.Recording.2024-06-03.at.11.01.38.mov

reproducible example uvicorn main:router --reload:

import typing as _t
from datetime import date, timedelta

from fastapi import FastAPI, Depends
from fastapi.responses import HTMLResponse
from fastui import FastUI, prebuilt_html
from fastui import components as c
from fastui.events import PageEvent
from pydantic import BaseModel
from enum import Enum


class ErcotMarket(Enum):
    RTM = "rtm"
    DAM = "dam"


class Nodes(Enum):
    HB_NORTH = "HB_NORTH"
    HB_SOUTH = "HB_SOUTH"


class HourlyMarketPriceModel(BaseModel):
    delivery_date: date
    delivery_hour_ending: int
    market: ErcotMarket
    node: str
    price_per_mwh: float


async def get_market_prices_from_db(delivery_date, market, node):
    return [
        HourlyMarketPriceModel(
            delivery_date=delivery_date,
            delivery_hour_ending=i,
            market=market,
            node=node,
            price_per_mwh=i * 10.0 + 10 * (1 if market == ErcotMarket.RTM else 0),
        )
        for i in range(1, 25)
    ]


router = FastAPI()

ErcotMarketLiteral: _t.TypeAlias = _t.Literal["rtm", "dam", "dart"]


class NodesFormModel(BaseModel):
    node: Nodes


@router.get("/api/{ercot_market}", response_model=FastUI, response_model_exclude_none=True)
async def market(
    ercot_market: ErcotMarketLiteral,
    node: str = "HB_NORTH",
):
    return [
        c.Page(
            components=[
                c.LinkList(
                    links=[
                        c.Link(
                            components=[c.Text(text="Real Time Market")],
                            on_click=PageEvent(
                                name="change-market",
                                push_path=f"/rtm?node={node}",
                                context={"ercot_market": "rtm", "node": node},
                            ),
                            active="/rtm",
                        ),
                        c.Link(
                            components=[c.Text(text="Day Ahead Market")],
                            on_click=PageEvent(
                                name="change-market",
                                push_path=f"/dam?node={node}",
                                context={"ercot_market": "dam", "node": node},
                            ),
                            active="/dam",
                        ),
                        c.Link(
                            components=[c.Text(text="DA-RT")],
                            on_click=PageEvent(
                                name="change-market",
                                push_path=f"/dart?node={node}",
                                context={"ercot_market": "dart", "node": node},
                            ),
                            active="/dart",
                        ),
                    ],
                    mode="tabs",
                    class_name="+ mb-4",
                ),
                c.ModelForm(
                    model=NodesFormModel,
                    submit_url=".",
                    initial={"node": Nodes.HB_NORTH},
                    method="GOTO",
                    submit_on_change=True,
                    display_mode="inline",
                ),
                c.ServerLoad(
                    path="/content/{ercot_market}?node={node}",
                    load_trigger=PageEvent(name="change-market"),
                    components=await market_content(ercot_market, node),
                ),
            ],
        )
    ]


@router.get(
    "/api/content/{ercot_market}",
    response_model=FastUI,
    response_model_exclude_none=True,
)
async def market_content(ercot_market: ErcotMarketLiteral, node: str) -> list[c.AnyComponent]:
    yesterday = date.today() - timedelta(days=1)
    match ercot_market:
        case "rtm":
            market_prices = await get_market_prices_from_db(
                delivery_date=yesterday,
                market=ErcotMarket.RTM,
                node=Nodes(node.upper()),
            )
        case "dam":
            market_prices = await get_market_prices_from_db(
                delivery_date=yesterday,
                market=ErcotMarket.DAM,
                node=Nodes(node.upper()),
            )
        case "dart":
            dam_market_prices = await get_market_prices_from_db(
                delivery_date=yesterday,
                market=ErcotMarket.DAM,
                node=Nodes(node.upper()),
            )
            rtm_market_prices = await get_market_prices_from_db(
                delivery_date=yesterday,
                market=ErcotMarket.RTM,
                node=Nodes(node.upper()),
            )
            dam_prices = {(p.delivery_date, p.delivery_hour_ending, p.node): p for p in dam_market_prices}
            rtm_prices = {(p.delivery_date, p.delivery_hour_ending, p.node): p for p in rtm_market_prices}

            market_prices = []
            for key, dam_price in dam_prices.items():
                rtm_price = rtm_prices.get(key)
                if rtm_price:
                    dart_price = dam_price.price_per_mwh - rtm_price.price_per_mwh
                    market_prices.append(HourlyMarketPriceModel(**{**rtm_price.dict(), "price_per_mwh": dart_price}))
        case _:
            raise ValueError("Invalid market")

    return [
        c.Table(
            data=market_prices,
            data_model=HourlyMarketPriceModel,
            columns=[
                c.display.DisplayLookup(
                    field="delivery_date",
                    table_width_percent=10,
                    mode=c.display.DisplayMode.date,
                ),
                c.display.DisplayLookup(field="delivery_hour_ending", table_width_percent=10),
                c.display.DisplayLookup(field="node", table_width_percent=20),
                c.display.DisplayLookup(field="price_per_mwh", table_width_percent=20),
            ],
        )
    ]


@router.get("/{path:path}")
async def html_landing() -> HTMLResponse:
    """Simple HTML page which serves the React app, comes last as it matches all paths."""
    return HTMLResponse(prebuilt_html(title="FastUI Demo"))
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