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>
139 lines
4.4 KiB
Python
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"
|