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")