fix(views): make Restaurant JSON-safe before passing to Jinja

All four CMS pages (menu / orders / kds / settings) crashed with

    TypeError: JSONEncoder.default() missing 1 required positional
    argument: 'o'

because templates do `{{ restaurant | tojson | safe }}`. `tojson`
runs Python's stdlib `json.JSONEncoder` which can't serialize the
`datetime` on `Restaurant.time` (or any other datetime nested in
the model). The browser saw '500 / 'o'' (KeyError-flavored).

Pydantic v1's .json() handles datetime correctly (ISO 8601). New
helper _restaurant_jsonable rounds-trips the Restaurant via .json()
+ json.loads() and returns a fully JSON-safe dict, then all four
view handlers pass that instead of restaurant.dict().
This commit is contained in:
Padreug 2026-05-09 07:02:40 +02:00
commit e98f82dced

View file

@ -15,6 +15,7 @@ Pages
All pages require a logged-in LNbits user (check_user_exists). All pages require a logged-in LNbits user (check_user_exists).
""" """
import json
from http import HTTPStatus from http import HTTPStatus
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
@ -26,6 +27,7 @@ from lnbits.decorators import check_user_exists
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
from .crud import get_restaurant_by_slug from .crud import get_restaurant_by_slug
from .models import Restaurant
restaurant_generic_router = APIRouter() restaurant_generic_router = APIRouter()
@ -34,6 +36,21 @@ def restaurant_renderer():
return template_renderer(["restaurant/templates"]) return template_renderer(["restaurant/templates"])
def _restaurant_jsonable(restaurant: Restaurant) -> dict:
"""
Convert a Restaurant pydantic model to a plain JSON-serializable
dict for Jinja's `tojson` filter.
`restaurant.dict()` returns a dict with a `datetime` on `time`,
which Python's stdlib `JSONEncoder` (used by Jinja `tojson`) can't
serialize it errors out as
TypeError: JSONEncoder.default() missing 1 required positional argument: 'o'
Pydantic v1's `.json()` knows how to serialize datetime as
ISO-8601, so we round-trip via JSON to get a clean dict.
"""
return json.loads(restaurant.json())
@restaurant_generic_router.get("/", response_class=HTMLResponse) @restaurant_generic_router.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return restaurant_renderer().TemplateResponse( return restaurant_renderer().TemplateResponse(
@ -56,7 +73,7 @@ async def menu_builder(
{ {
"request": request, "request": request,
"user": user.json(), "user": user.json(),
"restaurant": restaurant.dict(), "restaurant": _restaurant_jsonable(restaurant),
}, },
) )
@ -75,7 +92,7 @@ async def orders(
{ {
"request": request, "request": request,
"user": user.json(), "user": user.json(),
"restaurant": restaurant.dict(), "restaurant": _restaurant_jsonable(restaurant),
}, },
) )
@ -94,7 +111,7 @@ async def kds(
{ {
"request": request, "request": request,
"user": user.json(), "user": user.json(),
"restaurant": restaurant.dict(), "restaurant": _restaurant_jsonable(restaurant),
}, },
) )
@ -113,6 +130,6 @@ async def settings_page(
{ {
"request": request, "request": request,
"user": user.json(), "user": user.json(),
"restaurant": restaurant.dict(), "restaurant": _restaurant_jsonable(restaurant),
}, },
) )