fix(templates): replace all TemplateResponse with _render to bypass Jinja2 unhashable dict cache bug
This commit is contained in:
+44
-54
@@ -94,6 +94,18 @@ from app.i18n import get_languages as _get_languages
|
||||
templates.env.globals["get_languages"] = _get_languages
|
||||
|
||||
|
||||
def _render(request: Request, name: str, ctx: dict) -> HTMLResponse:
|
||||
"""Render a Jinja2 template and return HTMLResponse.
|
||||
|
||||
Bypasses Starlette TemplateResponse to avoid a Jinja2 cache bug where
|
||||
the context dict is passed as ``globals`` and used as an unhashable
|
||||
cache key (``TypeError: unhashable type: 'dict'``).
|
||||
"""
|
||||
ctx["request"] = request
|
||||
html = templates.get_template(name).render(ctx)
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
def _get_copyright() -> str:
|
||||
from app.models import SystemConfig as _SC
|
||||
from app.database import SessionLocal
|
||||
@@ -151,8 +163,7 @@ def index_page(request: Request, db: Session = Depends(get_db)):
|
||||
if not lang or lang not in avail:
|
||||
lang = "zh"
|
||||
|
||||
return templates.TemplateResponse("index.html", {
|
||||
"request": request, "postcards": all_postcards[:20], "lang": lang,
|
||||
return _render(request, "index.html", {"postcards": all_postcards[:20], "lang": lang,
|
||||
"has_more": len(all_postcards) > 20,
|
||||
})
|
||||
|
||||
@@ -248,7 +259,7 @@ def login_page(request: Request, db: Session = Depends(get_db)):
|
||||
user = redirect_if_not_logged_in(request, db)
|
||||
if user:
|
||||
return _redirect("/dashboard")
|
||||
return templates.TemplateResponse("login.html", {"request": request})
|
||||
return _render(request, "login.html", {"request": request})
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
@@ -263,8 +274,7 @@ def login_submit(
|
||||
cleanup_expired_sessions(db)
|
||||
user = db.query(User).filter(User.username == username).first()
|
||||
if not user or not verify_password(password, user.password_hash):
|
||||
return templates.TemplateResponse(
|
||||
"login.html", {"request": request, "error": "用户名或密码错误"}, status_code=401
|
||||
return _render(request, "login.html", {"error": "用户名或密码错误"}, status_code=401
|
||||
)
|
||||
is_remember = remember_me == "on"
|
||||
token = create_session(user.id, db, remember_me=is_remember)
|
||||
@@ -283,7 +293,7 @@ def register_page(request: Request, db: Session = Depends(get_db)):
|
||||
user = redirect_if_not_logged_in(request, db)
|
||||
if user:
|
||||
return _redirect("/dashboard")
|
||||
return templates.TemplateResponse("register.html", {"request": request})
|
||||
return _render(request, "register.html", {"request": request})
|
||||
|
||||
|
||||
@router.post("/register")
|
||||
@@ -298,30 +308,24 @@ def register_submit(
|
||||
# Validate invite code
|
||||
code_obj = db.query(InviteCode).filter(InviteCode.code == invite_code).first()
|
||||
if not code_obj:
|
||||
return templates.TemplateResponse(
|
||||
"register.html", {"request": request, "error": "邀请码无效"}, status_code=400
|
||||
return _render(request, "register.html", {"error": "邀请码无效"}, status_code=400
|
||||
)
|
||||
if code_obj.expires_at and code_obj.expires_at < datetime.utcnow():
|
||||
return templates.TemplateResponse(
|
||||
"register.html", {"request": request, "error": "邀请码已过期"}, status_code=400
|
||||
return _render(request, "register.html", {"error": "邀请码已过期"}, status_code=400
|
||||
)
|
||||
if code_obj.max_uses is not None and code_obj.use_count >= code_obj.max_uses:
|
||||
return templates.TemplateResponse(
|
||||
"register.html", {"request": request, "error": "邀请码使用次数已用尽"}, status_code=400
|
||||
return _render(request, "register.html", {"error": "邀请码使用次数已用尽"}, status_code=400
|
||||
)
|
||||
# Validate passwords
|
||||
if password != confirm_password:
|
||||
return templates.TemplateResponse(
|
||||
"register.html", {"request": request, "error": "两次输入的密码不一致"}, status_code=400
|
||||
return _render(request, "register.html", {"error": "两次输入的密码不一致"}, status_code=400
|
||||
)
|
||||
if len(password) < 6:
|
||||
return templates.TemplateResponse(
|
||||
"register.html", {"request": request, "error": "密码至少6个字符"}, status_code=400
|
||||
return _render(request, "register.html", {"error": "密码至少6个字符"}, status_code=400
|
||||
)
|
||||
# Check username
|
||||
if db.query(User).filter(User.username == username).first():
|
||||
return templates.TemplateResponse(
|
||||
"register.html", {"request": request, "error": "用户名已存在"}, status_code=400
|
||||
return _render(request, "register.html", {"error": "用户名已存在"}, status_code=400
|
||||
)
|
||||
# Create user
|
||||
new_user = User(username=username, password_hash=hash_password(password), invited_by_code_id=code_obj.id)
|
||||
@@ -359,7 +363,9 @@ def _admin_context(user, db, active_tab="invites", **extra):
|
||||
|
||||
|
||||
def _admin_render(request, user, db, active_tab="invites", **extra):
|
||||
return templates.TemplateResponse("admin.html", _admin_context(user, db, active_tab, request=request, **extra))
|
||||
ctx = _admin_context(user, db, active_tab, **extra)
|
||||
ctx["request"] = request
|
||||
return _render(request, "admin.html", ctx)
|
||||
|
||||
|
||||
@router.get("/admin", response_class=HTMLResponse)
|
||||
@@ -501,8 +507,7 @@ def settings_page(
|
||||
for p in profiles:
|
||||
profile_cards[p.id] = db.query(Postcard).filter(Postcard.profile_id == p.id).all()
|
||||
api_keys = db.query(ApiKey).filter(ApiKey.user_id == user.id).order_by(ApiKey.created_at.desc()).all()
|
||||
return templates.TemplateResponse("settings.html", {
|
||||
"request": request, "user": user, "profiles": profiles,
|
||||
return _render(request, "settings.html", {"user": user, "profiles": profiles,
|
||||
"profile_cards": profile_cards, "active_tab": active_tab,
|
||||
"api_keys": api_keys,
|
||||
})
|
||||
@@ -516,13 +521,11 @@ def change_username(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
if db.query(User).filter(User.username == new_username, User.id != user.id).first():
|
||||
return templates.TemplateResponse(
|
||||
"settings.html", {"request": request, "user": user, "error": "用户名已存在"}, status_code=400
|
||||
return _render(request, "settings.html", {"user": user, "error": "用户名已存在"}, status_code=400
|
||||
)
|
||||
user.username = new_username
|
||||
db.commit()
|
||||
return templates.TemplateResponse(
|
||||
"settings.html", {"request": request, "user": user, "success": "用户名修改成功"}
|
||||
return _render(request, "settings.html", {"user": user, "success": "用户名修改成功"}
|
||||
)
|
||||
|
||||
|
||||
@@ -536,21 +539,17 @@ def change_password(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
if not verify_password(old_password, user.password_hash):
|
||||
return templates.TemplateResponse(
|
||||
"settings.html", {"request": request, "user": user, "error": "当前密码错误"}, status_code=400
|
||||
return _render(request, "settings.html", {"user": user, "error": "当前密码错误"}, status_code=400
|
||||
)
|
||||
if new_password != confirm_password:
|
||||
return templates.TemplateResponse(
|
||||
"settings.html", {"request": request, "user": user, "error": "两次输入的新密码不一致"}, status_code=400
|
||||
return _render(request, "settings.html", {"user": user, "error": "两次输入的新密码不一致"}, status_code=400
|
||||
)
|
||||
if len(new_password) < 6:
|
||||
return templates.TemplateResponse(
|
||||
"settings.html", {"request": request, "user": user, "error": "新密码长度至少6位"}, status_code=400
|
||||
return _render(request, "settings.html", {"user": user, "error": "新密码长度至少6位"}, status_code=400
|
||||
)
|
||||
user.password_hash = hash_password(new_password)
|
||||
db.commit()
|
||||
return templates.TemplateResponse(
|
||||
"settings.html", {"request": request, "user": user, "success": "密码修改成功"}
|
||||
return _render(request, "settings.html", {"user": user, "success": "密码修改成功"}
|
||||
)
|
||||
|
||||
|
||||
@@ -626,8 +625,7 @@ def dashboard(request: Request, user: User = Depends(get_current_user_web), db:
|
||||
|
||||
from datetime import datetime, timezone as _tz
|
||||
|
||||
return templates.TemplateResponse("dashboard.html", {
|
||||
"request": request, "user": user, "profiles": profiles,
|
||||
return _render(request, "dashboard.html", {"user": user, "profiles": profiles,
|
||||
"total": total, "sent": sent, "delivered": delivered, "received": received,
|
||||
"countries": countries, "recent": recent,
|
||||
"sent_pct": sent_pct, "delivered_pct": delivered_pct, "received_pct": received_pct,
|
||||
@@ -651,7 +649,7 @@ def profile_list(request: Request, user: User = Depends(get_current_user_web), d
|
||||
"received": sum(1 for c in cards if c.status == "received"),
|
||||
"countries": len({c.country_to for c in cards if c.country_to}),
|
||||
}
|
||||
return templates.TemplateResponse("profiles.html", {"request": request, "user": user, "profiles": profiles, "extras": extras})
|
||||
return _render(request, "profiles.html", {"user": user, "profiles": profiles, "extras": extras})
|
||||
|
||||
|
||||
@router.post("/profiles")
|
||||
@@ -719,9 +717,7 @@ def postcard_list(
|
||||
sent = [pc for pc in postcards if pc.status in ("pending", "sent", "delivered")]
|
||||
received = [pc for pc in postcards if pc.status == "received"]
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"postcards.html",
|
||||
{"request": request, "user": user, "profile": profile, "sent": sent, "received": received, "countries": all_countries, "country": country},
|
||||
return _render(request, "postcards.html", {"user": user, "profile": profile, "sent": sent, "received": received, "countries": all_countries, "country": country},
|
||||
)
|
||||
|
||||
|
||||
@@ -731,7 +727,7 @@ def postcard_new(request: Request, profile_id: int, create_type: str = Query("se
|
||||
if not profile:
|
||||
return _redirect("/profiles")
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
return templates.TemplateResponse("postcard_form.html", {"request": request, "user": user, "profile": profile, "postcard": None, "create_type": create_type, "today": today})
|
||||
return _render(request, "postcard_form.html", {"user": user, "profile": profile, "postcard": None, "create_type": create_type, "today": today})
|
||||
|
||||
|
||||
@router.post("/profiles/{profile_id}/postcards")
|
||||
@@ -760,8 +756,7 @@ def postcard_create(
|
||||
Postcard.card_number == cn, Profile.user_id == user.id
|
||||
).first()
|
||||
if dup:
|
||||
return templates.TemplateResponse("postcard_form.html", {
|
||||
"request": request, "user": user, "profile": profile, "postcard": None,
|
||||
return _render(request, "postcard_form.html", {"user": user, "profile": profile, "postcard": None,
|
||||
"error": _t("pc_list.duplicate", user.language),
|
||||
})
|
||||
|
||||
@@ -805,7 +800,7 @@ def postcard_detail(request: Request, postcard_id: int, user: User = Depends(get
|
||||
)
|
||||
if not pc:
|
||||
return _redirect("/profiles")
|
||||
return templates.TemplateResponse("postcard_detail.html", {"request": request, "user": user, "postcard": pc})
|
||||
return _render(request, "postcard_detail.html", {"user": user, "postcard": pc})
|
||||
|
||||
|
||||
@router.get("/postcards/{postcard_id}/edit", response_class=HTMLResponse)
|
||||
@@ -818,7 +813,7 @@ def postcard_edit(request: Request, postcard_id: int, user: User = Depends(get_c
|
||||
)
|
||||
if not pc:
|
||||
return _redirect("/profiles")
|
||||
return templates.TemplateResponse("postcard_form.html", {"request": request, "user": user, "profile": pc.profile, "postcard": pc})
|
||||
return _render(request, "postcard_form.html", {"user": user, "profile": pc.profile, "postcard": pc})
|
||||
|
||||
|
||||
@router.post("/postcards/{postcard_id}/edit")
|
||||
@@ -853,8 +848,7 @@ def postcard_update(
|
||||
Profile.user_id == user.id
|
||||
).first()
|
||||
if dup:
|
||||
return templates.TemplateResponse("postcard_form.html", {
|
||||
"request": request, "user": user, "profile": pc.profile, "postcard": pc,
|
||||
return _render(request, "postcard_form.html", {"user": user, "profile": pc.profile, "postcard": pc,
|
||||
"error": _t("pc_list.duplicate", user.language),
|
||||
})
|
||||
|
||||
@@ -1000,8 +994,7 @@ def profile_showcase_page(
|
||||
if not p:
|
||||
return _redirect("/settings?tab=showcase")
|
||||
cards = db.query(Postcard).filter(Postcard.profile_id == profile_id).all()
|
||||
return templates.TemplateResponse("profile_showcase.html", {
|
||||
"request": request, "user": user, "profile": p, "cards": cards,
|
||||
return _render(request, "profile_showcase.html", {"user": user, "profile": p, "cards": cards,
|
||||
})
|
||||
|
||||
|
||||
@@ -1073,8 +1066,7 @@ def public_showcase(request: Request, username: str, db: Session = Depends(get_d
|
||||
profile_groups.append({"profile": p, "sent": p_sent, "received": p_received})
|
||||
sent_all.sort(key=lambda c: c.send_time or c.created_at, reverse=True)
|
||||
received_all.sort(key=lambda c: c.receive_time or c.created_at, reverse=True)
|
||||
resp = templates.TemplateResponse("showcase.html", {
|
||||
"request": request, "profile_user": user, "mode": user.showcase_mode,
|
||||
resp = _render(request, "showcase.html", {"profile_user": user, "mode": user.showcase_mode,
|
||||
"profile_groups": profile_groups, "sent_all": sent_all, "received_all": received_all,
|
||||
"lang": visitor_lang, "available_languages": _avail,
|
||||
})
|
||||
@@ -1104,8 +1096,7 @@ def apikey_create(
|
||||
db.commit()
|
||||
db.refresh(ak)
|
||||
api_keys = db.query(ApiKey).filter(ApiKey.user_id == user.id).order_by(ApiKey.created_at.desc()).all()
|
||||
return templates.TemplateResponse("settings.html", {
|
||||
"request": request, "user": user, "active_tab": "apikeys",
|
||||
return _render(request, "settings.html", {"user": user, "active_tab": "apikeys",
|
||||
"api_keys": api_keys, "new_key": ak.key, "success": "API Key created",
|
||||
})
|
||||
|
||||
@@ -1153,6 +1144,5 @@ def api_docs_page(
|
||||
user: User = Depends(get_current_user_web),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return templates.TemplateResponse("api_docs.html", {
|
||||
"request": request, "user": user,
|
||||
return _render(request, "api_docs.html", {"user": user,
|
||||
})
|
||||
Reference in New Issue
Block a user