winofy-backend/utils/exceptions.py
Ali Asadi 52adc732fa Initial Winofy backend: marketplace core (no payment integration)
Django 6 + DRF resource server against the Gooyal accounts OAuth2 service,
matching the Winsoo ecosystem's conventions. Covers locations, stores,
catalog, cart, checkout/orders (with the multi-store-cart split and the
status stepper), reviews, and notifications, plus a demo-data seed command.

Payment integration (wallet debits, online gateway, seller payouts) is
intentionally left out here — see feature/payment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 14:03:15 +03:30

139 lines
4.4 KiB
Python

import logging
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import IntegrityError
from django.http import JsonResponse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework import status
from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler
logger = logging.getLogger(__name__)
def _make_error_response(status_code: int, message, error_code: str) -> Response:
return Response(
{
"success": False,
"status_code": status_code,
"status_message": message,
"details": {
"message": message,
"error": error_code,
"timestamp": timezone.now().isoformat(),
},
},
status=status_code,
)
def exception_handler(exc, context):
logger.exception(exc)
response = drf_exception_handler(exc, context)
if response is not None:
response_data = {
"success": False,
"status_code": response.status_code,
"status_message": str(getattr(exc, "default_detail", exc)),
"details": getattr(exc, "detail", None),
}
if isinstance(exc, APIException):
try:
error = (
getattr(getattr(exc, "detail", None), "code", None)
or getattr(exc, "code", None)
or getattr(exc, "default_code", "")
)
except Exception:
error = ""
response_data["details"] = {
"message": exc.detail,
"error": error,
"timestamp": timezone.now().isoformat(),
}
response.data = response_data
return response
# ── Django / DB exceptions not caught by DRF ────────────────────────────
if isinstance(exc, ObjectDoesNotExist):
return _make_error_response(
status.HTTP_404_NOT_FOUND,
"مورد درخواستی یافت نشد.",
"not_found",
)
if isinstance(exc, IntegrityError):
return _make_error_response(
status.HTTP_409_CONFLICT,
"این اطلاعات قبلاً در سیستم ثبت شده است.",
"conflict",
)
if isinstance(exc, DjangoValidationError):
messages = list(exc.messages) if hasattr(exc, "messages") else [str(exc)]
detail = messages[0] if len(messages) == 1 else messages
return _make_error_response(
status.HTTP_400_BAD_REQUEST,
detail,
"validation_error",
)
if isinstance(exc, ValueError):
return _make_error_response(
status.HTTP_400_BAD_REQUEST,
"مقدار ورودی نامعتبر است.",
"invalid_value",
)
return None
class ErrorMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
logger.exception(exception)
return JsonResponse(
{
"success": False,
"status_code": 500,
"status_message": "خطای داخلی سرور. لطفاً با پشتیبانی تماس بگیرید.",
"details": {
"message": "خطای داخلی سرور.",
"error": "internal_server_error",
},
},
status=500,
)
class UnprocessableEntity(APIException):
status_code = 422
default_detail = "درخواست معتبر است اما به دلیل خطای منطقی قابل پردازش نیست."
default_code = "unprocessable_entity"
class ServiceUnavailable(APIException):
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
default_detail = "سرویس در حال حاضر در دسترس نیست. لطفاً دوباره تلاش کنید."
default_code = "service_unavailable"
class Conflict(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = "تعارض در داده‌ها."
default_code = "conflict"