Core - Exception Handling
Source: api/nextango/apps/core/exceptions.py
Last Updated: 2026-07-09
Overview
The Core exception handling module provides a consistent, secure error response layer across all API views. It defines custom exception classes, a DRF-compatible exception handler, and the @handle_view_exception view decorator.
All error responses use the SecureError shape to avoid leaking internal details to clients. Detailed error information is written to the nextango.errors logger for server-side debugging.
Exception Classes
SecurityException
Base exception for security-related errors.
class SecurityException(Exception):
def __init__(self, message: str, error_code: str = None, status_code: int = 403):
...
Attributes: message, error_code, status_code
Default status: 403 Forbidden
PaymentSecurityException
Exception for payment security violations. Subclass of SecurityException.
class PaymentSecurityException(SecurityException):
def __init__(self, message: str, error_code: str = None):
super().__init__(message, error_code, status_code=400)
Default status: 400 Bad Request
BusinessLogicException
Exception for business rule violations.
class BusinessLogicException(Exception):
def __init__(self, message: str, error_code: str = None, status_code: int = 400):
...
Default status: 400 Bad Request
TransactionException
Subclass of BusinessLogicException for transaction-specific errors.
InventoryException
Subclass of BusinessLogicException for inventory-related errors.
SecureError
SecureError converts internal error details into client-safe responses. Internal details are logged; clients receive only generic messages keyed by error type.
Generic messages:
| Error Type | Client Message |
|---|---|
validation_error | Invalid input provided |
authentication_error | Authentication required |
permission_error | Insufficient permissions |
not_found_error | Resource not found |
business_logic_error | Operation not permitted |
payment_error | Payment processing failed |
system_error | System temporarily unavailable |
rate_limit_error | Too many requests. Please try again later. |
security_error | Security validation failed |
Response shape:
{
"error": true,
"message": "<generic client message>",
"request_id": "<8-char UUID prefix>",
"error_code": "<optional machine-readable code>"
}
secure_exception_handler
DRF custom exception handler registered in REST_FRAMEWORK['EXCEPTION_HANDLER'].
Handles:
SecurityException→ the exception'sstatus_code(default 403).PaymentSecurityException, as a subclass, is caught by this branch and returns its own 400 status with thesecurity_errormessage shape.BusinessLogicException→ 400django.core.exceptions.ValidationError→ 400 withSecureErrorshapedjango.core.exceptions.PermissionDenied→ 403- DRF exceptions (404, 401, 403, 429) → mapped to SecureError type
- Unhandled exceptions → 500 with
SecureErrorshape
@handle_view_exception Decorator
Applied to ViewSet methods and custom actions to provide consistent, secure error responses per view call. Uses functools.wraps to preserve method signatures (required by DRF for action routing).
Handles these exception types in order:
| Exception | Status | Shape |
|---|---|---|
SecurityException | exc.status_code | SecureError |
BusinessLogicException | exc.status_code | SecureError |
DRFValidationError | 400 | DRF-formatted detail (see below) |
django.core.exceptions.ValidationError | 400 | SecureError |
Any other Exception | 500 | SecureError |
DRF ValidationError: 400 with field detail
Before the fix, serializer validation errors (raised by raise_exception=True on serializer.is_valid()) propagated as DRFValidationError and were caught by the generic Exception handler, producing a 500 with the generic SecureError shape.
After the fix, DRFValidationError is caught explicitly and returned as a 400 with the DRF-formatted detail payload:
except DRFValidationError as e:
return Response(e.detail, status=e.status_code)
Before (incorrect):
{
"error": true,
"message": "System temporarily unavailable",
"request_id": "a1b2c3d4"
}
HTTP 500
After (correct):
{
"field_name": ["This field is required."],
"another_field": ["Enter a valid email address."]
}
HTTP 400
Affected endpoints: All POST, PUT, and PATCH endpoints decorated with @handle_view_exception that use serializer.is_valid(raise_exception=True).
Client guidance: Treat HTTP 400 as "fix your request input". Treat HTTP 500 as "server problem, retry or contact support". A 400 with a
detailkey containing field names is a DRF validation error; a 400 with anerror: truekey is a business rule or security error.
ErrorReportingMiddleware
Adds a unique request_id (8-char UUID prefix) and client_ip to each request object. Writes X-Request-ID header to responses for end-to-end request tracing.
SecurityEventLogger
Utility for logging security and payment security events to the nextango.security logger.
Methods:
log_suspicious_activity(event_type, description, request, severity, additional_data): general security eventlog_payment_security_event(event_type, transaction_id, description, request, additional_data): payment-specific event