All checks were successful
ci/woodpecker/pr/pipeline Pipeline was successful
Error handlers had a separate Jinja2Templates instance without the _
global function, causing UndefinedError when rendering base.html
(which now calls {{ _(key, current_locale) }}).
- Register _() from translator module as Jinja2 global on error_handlers templates
- Add current_locale to get_template_context() from request.state.locale
198 lines
5.5 KiB
Python
198 lines
5.5 KiB
Python
"""Error handlers and middleware for web UI.
|
|
|
|
This module provides custom error pages and flash message middleware
|
|
for the web interface.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import HTTPException, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from app.infrastructure.i18n.translator import DEFAULT_LOCALE, _
|
|
from app.presentation.web.flash import FlashManager, get_flash_messages
|
|
|
|
templates = Jinja2Templates(directory="app/presentation/templates")
|
|
templates.env.globals["_"] = _
|
|
|
|
|
|
async def setup_flash_manager(request: Request) -> None:
|
|
"""Setup flash manager on request state.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
"""
|
|
request.state.flash_manager = FlashManager(request)
|
|
|
|
|
|
async def add_flash_to_response(request: Request, response: HTMLResponse) -> None:
|
|
"""Add flash cookie to response if needed.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
response: FastAPI response object.
|
|
"""
|
|
if hasattr(request.state, "flash_manager"):
|
|
request.state.flash_manager.set_cookie(response)
|
|
|
|
|
|
def get_template_context(request: Request) -> dict[str, Any]:
|
|
"""Get base template context with flash messages.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
|
|
Returns:
|
|
Template context dictionary.
|
|
"""
|
|
from app.presentation.web.deps import can_create_post, get_user_role
|
|
|
|
user = getattr(request.state, "user", None)
|
|
user_role = get_user_role(user)
|
|
|
|
return {
|
|
"request": request,
|
|
"user": user,
|
|
"user_role": user_role.value if user_role else None,
|
|
"can_create": can_create_post(user),
|
|
"flash_messages": get_flash_messages(request),
|
|
"current_locale": getattr(request.state, "locale", DEFAULT_LOCALE),
|
|
}
|
|
|
|
|
|
async def http_exception_handler(request: Request, exc: HTTPException) -> HTMLResponse:
|
|
"""Handle HTTP exceptions with custom error pages.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
exc: HTTPException instance.
|
|
|
|
Returns:
|
|
HTMLResponse with error page.
|
|
"""
|
|
# Handle redirects (307, 308)
|
|
if exc.status_code in (307, 308):
|
|
location = exc.headers.get("Location") if exc.headers else None
|
|
if location:
|
|
return RedirectResponse(url=location, status_code=exc.status_code) # type: ignore[return-value]
|
|
|
|
error_pages = {
|
|
403: ("Access Denied", "You don't have permission to access this page."),
|
|
404: ("Page Not Found", "The page you're looking for doesn't exist."),
|
|
500: ("Server Error", "Something went wrong on our end. Please try again later."),
|
|
}
|
|
|
|
error_title, error_message = error_pages.get(
|
|
exc.status_code, ("Error", exc.detail or "An unexpected error occurred.")
|
|
)
|
|
|
|
context = get_template_context(request)
|
|
context.update(
|
|
{
|
|
"error_code": exc.status_code,
|
|
"error_title": error_title,
|
|
"error_message": error_message,
|
|
}
|
|
)
|
|
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"pages/error.html",
|
|
context,
|
|
status_code=exc.status_code,
|
|
)
|
|
|
|
|
|
async def not_found_handler(request: Request, exc: HTTPException) -> HTMLResponse:
|
|
"""Handle 404 Not Found errors.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
exc: HTTPException instance.
|
|
|
|
Returns:
|
|
HTMLResponse with 404 page.
|
|
"""
|
|
context = get_template_context(request)
|
|
context.update(
|
|
{
|
|
"error_code": 404,
|
|
"error_title": "Page Not Found",
|
|
"error_message": "The page you're looking for doesn't exist or has been moved.",
|
|
}
|
|
)
|
|
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"pages/error.html",
|
|
context,
|
|
status_code=404,
|
|
)
|
|
|
|
|
|
async def forbidden_handler(request: Request, exc: HTTPException) -> HTMLResponse:
|
|
"""Handle 403 Forbidden errors.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
exc: HTTPException instance.
|
|
|
|
Returns:
|
|
HTMLResponse with 403 page.
|
|
"""
|
|
context = get_template_context(request)
|
|
context.update(
|
|
{
|
|
"error_code": 403,
|
|
"error_title": "Access Denied",
|
|
"error_message": "You don't have permission to access this resource. Please sign in or contact an administrator.",
|
|
}
|
|
)
|
|
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"pages/error.html",
|
|
context,
|
|
status_code=403,
|
|
)
|
|
|
|
|
|
async def server_error_handler(request: Request, exc: Exception) -> HTMLResponse:
|
|
"""Handle 500 Internal Server Error.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
exc: Exception instance.
|
|
|
|
Returns:
|
|
HTMLResponse with 500 page.
|
|
"""
|
|
context = get_template_context(request)
|
|
context.update(
|
|
{
|
|
"error_code": 500,
|
|
"error_title": "Server Error",
|
|
"error_message": "Something went wrong on our end. Please try again later or contact support if the problem persists.",
|
|
}
|
|
)
|
|
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"pages/error.html",
|
|
context,
|
|
status_code=500,
|
|
)
|
|
|
|
|
|
def register_error_handlers(app: Any) -> None:
|
|
"""Register error handlers with FastAPI app.
|
|
|
|
Args:
|
|
app: FastAPI application instance.
|
|
"""
|
|
app.add_exception_handler(404, not_found_handler)
|
|
app.add_exception_handler(403, forbidden_handler)
|
|
app.add_exception_handler(500, server_error_handler)
|
|
app.add_exception_handler(HTTPException, http_exception_handler)
|