Добавление custom_exception_handler

This commit is contained in:
2026-07-18 22:44:06 +03:00
parent 7f87794fcc
commit a8d36b59b4
6 changed files with 93 additions and 4 deletions

View File

@@ -0,0 +1,12 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Пакет классов расширения Django REST Framework
"""
from .custom_exception_handler import custom_exception_handler
from .expiring_token_authentication import ExpiringTokenAuthentication

View File

@@ -0,0 +1,87 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: 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)

View File

@@ -0,0 +1,75 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса ExpiringTokenAuthentication
"""
from typing import Any
from rest_framework.authentication import TokenAuthentication
from rest_framework.request import Request
from net.xeaf.rack.managers import AccountManager
from net.xeaf.rack.managers import LocaleManager
from net.xeaf.rack.managers import SessionManager
from net.xeaf.rack.models import AccountModel
from net.xeaf.rack.models import SessionModel
class ExpiringTokenAuthentication(TokenAuthentication):
"""
Реализует механизм авторизации для Токенов со сроком действия
"""
AUTH_TOKEN_PARAM = "x-auth-token"
""" Идентификатор параметра токена """
def authenticate(self, request: Request) -> tuple[AccountModel | None, SessionModel | None] | tuple[Any, Any] | None:
"""
Метод авторизации
:param request: Информация о запросе
"""
LocaleManager.adjust_request_language(request)
key = request.query_params.get(self.AUTH_TOKEN_PARAM, None)
if key is not None:
return self.authenticate_credentials(key)
return super().authenticate(request)
def authenticate_credentials(self, key: str) -> tuple[AccountModel | None, SessionModel | None]:
"""
Проверка Токена авторизации
:param key: Токен авторизации
"""
session = SessionManager[SessionModel].find_by_token(token=key)
if not session or session.is_expired():
return None, None
account = session.account
if not self._check_account(account):
return None, None
SessionManager.renew_session(session)
return session.account, session
@classmethod
def _check_account(cls, account: AccountModel) -> bool:
"""
Проверка учетной записи
:param account: Учетная запись
"""
# Можно прошедшим проверку и не удаленным
reject = AccountManager.check_account_auth(account)
result = reject is None and not account.is_deleted()
return result