Короткий ответ
В DRF для этого переопределяют EXCEPTION_HANDLER в настройках, указывая собственную функцию-обработчик, которая вызывает стандартный exception_handler DRF для известных исключений и переформатирует его response.data в единую структуру, а также перехватывает непредвиденные исключения для возврата согласованного 500.
Как это работает подробнее
DRF по умолчанию вызывает функцию, заданную в REST_FRAMEWORK['EXCEPTION_HANDLER'] (по умолчанию rest_framework.views.exception_handler), для любого исключения, возникшего во view, если оно унаследовано от APIException или относится к обрабатываемым (Http404, PermissionDenied). Собственная функция может вызвать оригинальный обработчик, получить стандартный Response, а затем изменить формат response.data под принятый в проекте контракт ошибок.
Пример кода
from rest_framework.views import exception_handler as drf_exception_handler
def custom_exception_handler(exc, context):
response = drf_exception_handler(exc, context)
if response is not None:
response.data = {"error": {"code": response.status_code, "details": response.data}}
return response
# Непредвиденная ошибка - не раскрываем внутренние детали клиенту
return None
# settings.py
REST_FRAMEWORK = {"EXCEPTION_HANDLER": "myapp.exceptions.custom_exception_handler"}Что использовать на практике
- Практика. Такой единый обработчик упрощает жизнь клиентам API: независимо от того, это ошибка валидации сериализатора,
404или403, формат ответа (например,{"error": {"code": ..., "message": ...}}) остаётся одинаковым, что упрощает обработку ошибок на фронтенде.
Подводные камни
- Риск. Переопределяют обработчик, но забывают вызвать оригинальный
exception_handlerдля стандартных случаев, теряя часть автоматической логики DRF (например, корректную обработкуHttp404); не отличают ожидаемые ошибки (APIExceptionи подклассы) от непредвиденных программных ошибок, из-за чего детали внутренней ошибки (стектрейс, сообщение исключения) могут утечь клиенту.