Ludic Logo
Edit

Cascading Selects (FastAPI)

The cascading selects example uses FastAPI to implement two selects. First one is a simple select of a car's manufacturer. Second select's values are based on the option chosen by the first one.

Demo

#

Implementation

#

In this example, we are using FastAPI instead of Starlette. So we need to install ludic with the fastapi extra:

pip install ludic[fastapi]

Next step is to import and set up all the necessary classes:

1   from fastapi import FastAPI
2   from ludic.contrib.fastapi import LudicRoute
3   
4   app = FastAPI()
5   app.router.route_class = LudicRoute

Now let us set up a database. In this case, we are using a fake database of data classes. In real example, you might want to use e.g. SQLAlchemy ORM session. Anyway, here is what it might look like:

1   def get_session(request: Request) -> Database:
2       return request.scope["db"]  # this is a fake database

Next step is to create our select boxes. The first one will be selecting manufacturers, while the second car's models:

 1   from typing import override
 2   
 3   from ludic.catalog.forms import Option, SelectField, SelectFieldAttrs
 4   from ludic.catalog.layouts import Stack
 5   from ludic.components import Component
 6   
 7   class CarSelect(Component[str, SelectFieldAttrs]):
 8       """Ludic element representing car select."""
 9   
10       @override
11       def render(self) -> SelectField:
12           return SelectField(
13               *[
14                   Option(child, value=child.lower())
15                   for child in self.children
16               ],
17               label=self.attrs.pop("label", "Car Manufacturer"),
18               name="manufacturer",
19               **self.attrs,
20           )
21   
22   
23   class CarModelsSelect(Component[str, SelectFieldAttrs]):
24       """Ludic element representing car models select."""
25   
26       @override
27       def render(self) -> SelectField:
28           return SelectField(
29               *[
30                   Option(child, value=child.lower())
31                   for child in self.children
32               ],
33               label=self.attrs.pop("label", "Car Model"),
34               id="models",
35               **self.attrs,
36           )

Finally, we can implement the FastAPI view which renders the initial page containing two select boxes:

 1   from fastapi import Depends
 2   from ludic.web import Request  # FastAPI is configured to use our custom
 3                                  # Request object
 4   
 5   from my_app import Database, get_db
 6   from my_app.components import CarSelect, CarModelsSelect
 7   
 8   @app.get("/")
 9   def index(request: Request, db: Database = Depends(get_db)) -> Stack:
10       return Stack(
11           CarSelect(
12               *db.find_all_cars_names(),
13               hx_get=request.url_for(models),
14               hx_target="#models",
15           ),
16           CarModelsSelect(*db.find_first_car().models),
17       )

We also need to implement the view that returns the car models options:

 1   from ludic.web.exceptions import NotFoundError
 2   # ... already imported stuff skipped
 3   
 4   @app.get("/models/")
 5   def models(
 6       manufacturer: str | None = None, db: Database = Depends(get_db)
 7   ) -> CarModelsSelect:
 8       if car := db.find_car_by_name(manufacturer):
 9           return CarModelsSelect(*car.models, label=None)
10       else:
11           raise NotFoundError("Car could not be found")
Made with Ludic and HTMX and 🐍 • DiscordGitHub