# 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""" {title} — Fashionistar
{status}

{heading}

{description}

← Back to Home

Need help? Contact Support
""" return HttpResponse(html, status=status, content_type='text/html; charset=utf-8') # ── handlers ───────────────────────────────────────────────────────────────── def bad_request_handler(request, exception=None): """handler400 — 400 Bad Request""" if _is_api_request(request): return _json_error( 400, 'bad_request', 'The request was malformed or contained invalid parameters.', ) return _html_error(400, '400 Bad Request', 'Bad Request', 'The request was malformed. Please check your input and try again.') def forbidden_handler(request, exception=None): """handler403 — 403 Forbidden""" if _is_api_request(request): msg = str(exception) if exception else 'You do not have permission to access this resource.' return _json_error(403, 'permission_denied', msg, support_url=_get_support_url()) return _html_error(403, '403 Forbidden', 'Access Denied', 'You do not have permission to view this page.') def not_found_handler(request, exception=None): """handler404 — 404 Not Found""" if _is_api_request(request): return _json_error( 404, 'not_found', f"The endpoint '{request.path}' was not found on this server.", path=request.path, ) return _html_error( 404, '404 Not Found', 'Page Not Found', f"We could not find {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.', )