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 TypeClient Message
validation_errorInvalid input provided
authentication_errorAuthentication required
permission_errorInsufficient permissions
not_found_errorResource not found
business_logic_errorOperation not permitted
payment_errorPayment processing failed
system_errorSystem temporarily unavailable
rate_limit_errorToo many requests. Please try again later.
security_errorSecurity 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's status_code (default 403). PaymentSecurityException, as a subclass, is caught by this branch and returns its own 400 status with the security_error message shape.
  • BusinessLogicException → 400
  • django.core.exceptions.ValidationError → 400 with SecureError shape
  • django.core.exceptions.PermissionDenied → 403
  • DRF exceptions (404, 401, 403, 429) → mapped to SecureError type
  • Unhandled exceptions → 500 with SecureError shape

@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:

ExceptionStatusShape
SecurityExceptionexc.status_codeSecureError
BusinessLogicExceptionexc.status_codeSecureError
DRFValidationError400DRF-formatted detail (see below)
django.core.exceptions.ValidationError400SecureError
Any other Exception500SecureError

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 detail key containing field names is a DRF validation error; a 400 with an error: true key 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 event
  • log_payment_security_event(event_type, transaction_id, description, request, additional_data): payment-specific event

Was this page helpful?