diff --git a/net/xeaf/rack/utils/auth/__init__.py b/net/xeaf/rack/utils/drf/__init__.py similarity index 70% rename from net/xeaf/rack/utils/auth/__init__.py rename to net/xeaf/rack/utils/drf/__init__.py index 0777579..f4bbfdf 100644 --- a/net/xeaf/rack/utils/auth/__init__.py +++ b/net/xeaf/rack/utils/drf/__init__.py @@ -5,7 +5,8 @@ # Все права защищены. Лицензия: MIT """ -Классов авторизации +Пакет классов расширения Django REST Framework """ +from .custom_exception_handler import custom_exception_handler from .expiring_token_authentication import ExpiringTokenAuthentication diff --git a/net/xeaf/rack/utils/drf/custom_exception_handler.py b/net/xeaf/rack/utils/drf/custom_exception_handler.py new file mode 100644 index 0000000..8dfe6d1 --- /dev/null +++ b/net/xeaf/rack/utils/drf/custom_exception_handler.py @@ -0,0 +1,87 @@ +# DRF Rack +# Библиотека классов расширений для Django REST Framework +# +# Автор: Николай В. Анохин +# Все права защищены. Лицензия: MIT + +""" +Описание функции custom_exception_handler +""" + +from rest_framework import status +from rest_framework.exceptions import MethodNotAllowed +from rest_framework.exceptions import NotAuthenticated +from rest_framework.exceptions import PermissionDenied +from rest_framework.exceptions import ValidationError +from rest_framework.request import Request +from rest_framework.response import Response + +from net.xeaf.rack.core import CoreException +from net.xeaf.rack.models.responses import ErrorResponse +from net.xeaf.rack.utils.exceptions import ForbiddenException +from net.xeaf.rack.utils.exceptions import NotFoundException +from net.xeaf.rack.utils.exceptions import UnauthorizedException + + +def custom_exception_handler(exc: CoreException | Exception, context: dict) -> Response: + """ + Универсальный обработчик исключений + + :param exc: Исключение + :param context: Контекст + + :return: Ответ + """ + + # Получаем информацию о запросе + request = context.get("request") + assert isinstance(request, Request) + + # Это наше исключение + if isinstance(exc, CoreException): + return ErrorResponse(status_code=exc.status_code, detail=exc.detail, meta=exc.meta) + + # Проверка на стандартное исключение валидации + if isinstance(exc, ValidationError): + return _process_validation_exception(exc) + + # Проверка на стандартное исключение авторизации + if isinstance(exc, NotAuthenticated): + return ErrorResponse(status_code=status.HTTP_401_UNAUTHORIZED, detail=UnauthorizedException.Msg.DEFAULT_MESSAGE) + + # Проверка на стандартное исключение прав доступа + if isinstance(exc, PermissionDenied): + return ErrorResponse(status_code=status.HTTP_403_FORBIDDEN, detail=ForbiddenException.Msg.DEFAULT_MESSAGE) + + # Проверка на стандартное исключение отсутствия реализации + if isinstance(exc, MethodNotAllowed): + return ErrorResponse(status_code=status.HTTP_404_NOT_FOUND, detail=NotFoundException.Msg.DEFAULT_MESSAGE) + + # Если ничего другое не прошло + return ErrorResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) + + +def _process_validation_exception(exc: ValidationError) -> ErrorResponse: + """ + Обрабатывает частный случай стандартного исключения валидации + + :param exc: Исключение + + :return: Ответ + """ + + if isinstance(exc.detail, list): + if len(exc.detail) == 1: + detail = str(exc.detail[0]) + else: + detail = exc.detail + elif isinstance(exc.detail, dict): + detail = {} + for field, error_info in exc.detail.items(): + if isinstance(error_info, list): + error = error_info[0] + detail[field] = str(error) + else: + detail = str(exc.detail) + + return ErrorResponse(status_code=status.HTTP_400_BAD_REQUEST, detail=detail) diff --git a/net/xeaf/rack/utils/auth/expiring_token_authentication.py b/net/xeaf/rack/utils/drf/expiring_token_authentication.py similarity index 100% rename from net/xeaf/rack/utils/auth/expiring_token_authentication.py rename to net/xeaf/rack/utils/drf/expiring_token_authentication.py diff --git a/net/xeaf/rack/utils/settings/configs/drf.py b/net/xeaf/rack/utils/settings/configs/drf.py index b5a33c8..5c84fce 100644 --- a/net/xeaf/rack/utils/settings/configs/drf.py +++ b/net/xeaf/rack/utils/settings/configs/drf.py @@ -37,10 +37,10 @@ REST_FRAMEWORK = { 'djangorestframework_camel_case.parser.CamelCaseJSONParser', ), 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'net.xeaf.rack.utils.auth.ExpiringTokenAuthentication', + 'net.xeaf.rack.utils.drf.ExpiringTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10, - # 'EXCEPTION_HANDLER': 'tantal.bdrive.api.utils.custom_exception_handler' + 'EXCEPTION_HANDLER': 'net.xeaf.rack.utils.drf.custom_exception_handler' } diff --git a/net/xeaf/rack/views/version_view_set.py b/net/xeaf/rack/views/version_view_set.py index eda8021..59eed40 100644 --- a/net/xeaf/rack/views/version_view_set.py +++ b/net/xeaf/rack/views/version_view_set.py @@ -11,6 +11,7 @@ from typing import Any from uuid import UUID +from rest_framework.exceptions import ValidationError from rest_framework.permissions import AllowAny from net.xeaf.rack.core import CoreModel diff --git a/wsgiapp/settings.py b/wsgiapp/settings.py index b0a4671..cfd20ef 100644 --- a/wsgiapp/settings.py +++ b/wsgiapp/settings.py @@ -21,7 +21,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent SettingUtils.initialize( base_dir=BASE_DIR, env_prefix="RACK_", - app_name="DRF Rack Demo", + app_name="DRF Rack", url_root="wsgiapp.urls", wsgi_application="wsgiapp.wsgi.application", )