Enhance API response handling and implement unified API standard across multiple modules. Added feature flags for unified exception handling and debug throttling in settings. Updated pagination and response formats in various viewsets to align with the new standard. Improved error handling and response validation in frontend components for better user feedback.

This commit is contained in:
IGNY8 VPS (Salman)
2025-11-15 20:18:42 +00:00
parent 94f243f4a2
commit a75ebf2584
18 changed files with 1974 additions and 642 deletions

View File

@@ -0,0 +1,176 @@
"""
Centralized Exception Handler
Wraps all exceptions in unified format
"""
import logging
from rest_framework.views import exception_handler
from rest_framework import status
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from .response import get_request_id, error_response
logger = logging.getLogger(__name__)
def custom_exception_handler(exc, context):
"""
Custom exception handler that wraps all errors in unified format
Args:
exc: The exception that was raised
context: Dictionary containing request, view, args, kwargs
Returns:
Response object with unified error format
"""
# Get request from context
request = context.get('request')
# Get request ID
request_id = get_request_id(request) if request else None
# Call DRF's default exception handler first
response = exception_handler(exc, context)
# If DRF handled it, wrap it in unified format
if response is not None:
# Extract error details from DRF response
error_message = None
errors = None
status_code = response.status_code
# Try to extract error message from response data
if hasattr(response, 'data'):
if isinstance(response.data, dict):
# DRF validation errors
if 'detail' in response.data:
error_message = str(response.data['detail'])
elif 'non_field_errors' in response.data:
error_message = str(response.data['non_field_errors'][0]) if response.data['non_field_errors'] else None
errors = response.data
else:
# Field-specific errors
errors = response.data
# Create top-level error message
if errors:
first_error = list(errors.values())[0] if errors else None
if first_error and isinstance(first_error, list) and len(first_error) > 0:
error_message = str(first_error[0])
elif first_error:
error_message = str(first_error)
else:
error_message = 'Validation failed'
elif isinstance(response.data, list):
# List of errors
error_message = str(response.data[0]) if response.data else 'Validation failed'
else:
error_message = str(response.data)
# Map status codes to appropriate error messages
if not error_message:
if status_code == status.HTTP_400_BAD_REQUEST:
error_message = 'Bad request'
elif status_code == status.HTTP_401_UNAUTHORIZED:
error_message = 'Authentication required'
elif status_code == status.HTTP_403_FORBIDDEN:
error_message = 'Permission denied'
elif status_code == status.HTTP_404_NOT_FOUND:
error_message = 'Resource not found'
elif status_code == status.HTTP_409_CONFLICT:
error_message = 'Conflict'
elif status_code == status.HTTP_422_UNPROCESSABLE_ENTITY:
error_message = 'Validation failed'
elif status_code == status.HTTP_429_TOO_MANY_REQUESTS:
error_message = 'Rate limit exceeded'
elif status_code >= 500:
error_message = 'Internal server error'
else:
error_message = 'An error occurred'
# Prepare debug info (only in DEBUG mode)
debug_info = None
if settings.DEBUG:
debug_info = {
'exception_type': type(exc).__name__,
'exception_message': str(exc),
'view': context.get('view').__class__.__name__ if context.get('view') else None,
'path': request.path if request else None,
'method': request.method if request else None,
}
# Include traceback in debug mode
import traceback
debug_info['traceback'] = traceback.format_exc()
# Log the error
if status_code >= 500:
logger.error(
f"Server error: {error_message}",
extra={
'request_id': request_id,
'endpoint': request.path if request else None,
'method': request.method if request else None,
'user_id': request.user.id if request and request.user and request.user.is_authenticated else None,
'account_id': request.account.id if request and hasattr(request, 'account') and request.account else None,
'status_code': status_code,
'exception_type': type(exc).__name__,
},
exc_info=True
)
elif status_code >= 400:
logger.warning(
f"Client error: {error_message}",
extra={
'request_id': request_id,
'endpoint': request.path if request else None,
'method': request.method if request else None,
'user_id': request.user.id if request and request.user and request.user.is_authenticated else None,
'account_id': request.account.id if request and hasattr(request, 'account') and request.account else None,
'status_code': status_code,
}
)
# Return unified error response
return error_response(
error=error_message,
errors=errors,
status_code=status_code,
request=request,
debug_info=debug_info
)
# If DRF didn't handle it, it's an unhandled exception
# Log it and return unified error response
logger.error(
f"Unhandled exception: {type(exc).__name__}: {str(exc)}",
extra={
'request_id': request_id,
'endpoint': request.path if request else None,
'method': request.method if request else None,
'user_id': request.user.id if request and request.user and request.user.is_authenticated else None,
'account_id': request.account.id if request and hasattr(request, 'account') and request.account else None,
},
exc_info=True
)
# Prepare debug info
debug_info = None
if settings.DEBUG:
import traceback
debug_info = {
'exception_type': type(exc).__name__,
'exception_message': str(exc),
'view': context.get('view').__class__.__name__ if context.get('view') else None,
'path': request.path if request else None,
'method': request.method if request else None,
'traceback': traceback.format_exc()
}
return error_response(
error='Internal server error',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request,
debug_info=debug_info
)