# backend/error_views.py """ Custom Error Handlers — JSON + HTML (enterprise-grade) ======================================================= Registered in backend/urls.py as: handler400 = 'backend.error_views.bad_request_handler' handler403 = 'backend.error_views.forbidden_handler' handler404 = 'backend.error_views.not_found_handler' handler500 = 'backend.error_views.server_error_handler' Design decisions: - API requests (path starts with /api/ or Accept: application/json): → returns a clean JSON envelope matching ALL other API error responses - Browser requests: → returns a minimal HTML page (no template required — inline HTML avoids the risk of template rendering itself failing during a 500) - 500 errors are logged to the 'application' logger with exc_info=True for SIEM / Sentry ingestion. - ERROR_SUPPORT_URL and FRONTEND_BASE_URL are read from settings, with safe fallbacks so the handlers never themselves 500. """ import logging from django.conf import settings from django.http import JsonResponse, HttpResponse logger = logging.getLogger('application') # ── helpers ────────────────────────────────────────────────────────────────── def _is_api_request(request) -> bool: """ Detect whether the request expects a JSON response. True if: - The path starts with /api/ - OR the Accept header contains 'application/json' """ if request.path.startswith('/api/'): return True accept = request.META.get('HTTP_ACCEPT', '') return 'application/json' in accept # ── URL helpers (read lazily at request time, never at module load) ────────── def _get_support_url() -> str: """Return the support URL from settings, falling back to fashionistar.net.""" base = getattr(settings, 'FRONTEND_URL', 'https://fashionistar.net').rstrip('/') return getattr(settings, 'ERROR_SUPPORT_URL', f'{base}/support') def _get_frontend_url() -> str: """Return the frontend home URL from settings.""" return getattr(settings, 'FRONTEND_URL', 'https://fashionistar.net').rstrip('/') def _json_error(status: int, code: str, message: str, **extra) -> JsonResponse: body = { 'status': 'error', 'code': code, 'message': message, **extra, } return JsonResponse(body, status=status) def _html_error(status: int, title: str, heading: str, description: str) -> HttpResponse: # Read URLs lazily at request time so .env changes take effect frontend_url = _get_frontend_url() support_url = _get_support_url() html = f"""
{request.path}. "
f"It may have moved or the URL is incorrect.",
)
def server_error_handler(request, *args, **kwargs):
"""handler500 — 500 Internal Server Error"""
logger.error(
"💥 500 Internal Server Error: %s %s",
request.method, request.path,
exc_info=True,
)
if _is_api_request(request):
return _json_error(
500, 'internal_server_error',
'An unexpected error occurred. Our engineers have been notified.',
support_url=_get_support_url(),
)
return _html_error(
500, '500 Server Error', 'Something Went Wrong',
'An unexpected error occurred on our end. '
'Our team has been notified and is working on a fix.',
)