Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Кастомизация ответов
customization

Кастомизация ответов

Renderer classes, кастомные responses, форматирование вывода

Кастомизация ответов в Django REST Framework

DRF позволяет гибко настраивать формат и структуру ответов через renderer classes, кастомные Response и content negotiation.

#Renderer Classes

Renderer преобразует данные ответа в нужный формат (JSON, HTML, XML и т.д.).

#Встроенные renderer

# settings.py REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ] }

Классы:

  • JSONRenderer — JSON формат
  • BrowsableAPIRenderer — HTML страница для браузера (включает формы)
  • AdminRenderer — формат как в Django admin
  • HTMLFormRenderer — для рендеринга форм

#Отключение BrowsableAPIRenderer

Для production часто отключают BrowsableAPIRenderer:

REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', ] }

Преимущества:

  • Меньше размер ответа
  • Быстрее рендеринг
  • Не показывает лишнюю информацию

#Кастомный JSONRenderer

from rest_framework.renderers import JSONRenderer class CustomJSONRenderer(JSONRenderer): """JSON с кастомной обёрткой ответа""" def render(self, data, accepted_media_type=None, renderer_context=None): # Обёртка всех ответов в единый формат response = { 'success': True, 'data': data, 'meta': { 'timestamp': timezone.now().isoformat(), 'version': '1.0', } } return super().render(response, accepted_media_type, renderer_context)

Ответ:

{ "success": true, "data": {...}, "meta": { "timestamp": "2024-01-01T12:00:00Z", "version": "1.0" } }

#Content Negotiation

DRF выбирает renderer на основе заголовка Accept:

# Запрос JSON GET /api/articles/ Accept: application/json # Запрос HTML (Browsable API) GET /api/articles/ Accept: text/html

#Кастомный negotiation

from rest_framework.negotiation import BaseContentNegotiation class IgnoreClientContentNegotiation(BaseContentNegotiation): """Игнорировать Accept заголовок, всегда использовать JSON""" def select_parser(self, request, parsers): return parsers[0] def select_renderer(self, request, renderers, format_suffix=None): from rest_framework.renderers import JSONRenderer return (JSONRenderer(), 'application/json') # settings.py REST_FRAMEWORK = { 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'myapp.negotiation.IgnoreClientContentNegotiation', }

#Кастомизация Response

#Стандартный Response

from rest_framework.response import Response # Простой ответ return Response(data) # Со статусом return Response(data, status=201) # С заголовками return Response(data, headers={'X-Custom': 'value'})

#Response с обёрткой

from rest_framework.response import Response from rest_framework.utils.encoders import JSONEncoder import json class APIResponse(Response): """Кастомный Response с единым форматом""" def __init__(self, data=None, status=None, message='Success', **kwargs): wrapped_data = { 'success': status is None or (200 <= status < 300), 'message': message, 'data': data, } super().__init__(data=wrapped_data, status=status, **kwargs) # Использование return APIResponse(article_data, message='Article created')

#Форматирование ошибок

#Стандартный формат ошибок

{ "title": ["This field is required."], "content": ["Ensure this field has no more than 1000 characters."] }

#Кастомный формат ошибок

from rest_framework.views import exception_handler def custom_exception_handler(exc, context): response = exception_handler(exc, context) if response is not None: # Преобразуем формат ошибок custom_response = { 'success': False, 'error': { 'code': response.status_code, 'message': response.data.get('detail', 'An error occurred'), 'fields': {} } } # Собираем ошибки по полям for key, value in response.data.items(): if key == 'detail': continue if isinstance(value, list): custom_response['error']['fields'][key] = value else: custom_response['error']['message'] = value response.data = custom_response return response # settings.py REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'myapp.utils.custom_exception_handler', }

Ответ:

{ "success": false, "error": { "code": 400, "message": "Validation failed", "fields": { "title": ["This field is required."], "email": ["Enter a valid email address."] } } }

#Кастомизация заголовков

#Добавление заголовков

from rest_framework.response import Response class ArticleViewSet(viewsets.ModelViewSet): def retrieve(self, request, *args, **kwargs): response = super().retrieve(request, *args, **kwargs) response['X-Article-Views'] = self.get_object().views response['Cache-Control'] = 'max-age=3600' return response

#CORS заголовки

from rest_framework.response import Response def add_cors_headers(response): response['Access-Control-Allow-Origin'] = '*' response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE' response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization' return response class MyView(APIView): def get(self, request): response = Response({'data': 'value'}) return add_cors_headers(response)

#Condition и ETag

#Условные запросы

from rest_framework.decorators import api_view from django.utils.cache import patch_response_headers from django.views.decorators.http import condition from datetime import datetime @api_view(['GET']) @condition(last_modified_func=lambda r, pk: Article.objects.get(pk=pk).updated_at) def article_detail(request, pk): article = Article.objects.get(pk=pk) serializer = ArticleSerializer(article) response = Response(serializer.data) patch_response_headers(response, cache_timeout=3600) return response

Логика:

  • Если клиент отправил If-Modified-Since с той же датой → возвращаем 304 Not Modified
  • Иначе → возвращаем 200 с данными

#ETag

from rest_framework.response import Response from django.utils.cache import patch_vary_headers def article_detail(request, pk): article = Article.objects.get(pk=pk) etag = f'"{article.id}-{article.updated_at.timestamp()}"' if request.META.get('HTTP_IF_NONE_MATCH') == etag: return Response(status=304) serializer = ArticleSerializer(article) response = Response(serializer.data) response['ETag'] = etag return response

#Пагинация и кастомизация

from rest_framework.pagination import PageNumberPagination from rest_framework.response import Response class CustomPagination(PageNumberPagination): page_size = 20 def get_paginated_response(self, data): return Response({ 'success': True, 'meta': { 'total': self.page.paginator.count, 'page': self.page.number, 'per_page': self.page_size, 'total_pages': self.page.paginator.num_pages, 'has_next': self.page.has_next(), 'has_prev': self.page.has_previous(), }, 'data': data })

#Выбор формата через URL

# settings.py REST_FRAMEWORK = { 'URL_FORMAT_OVERRIDE': 'format', } # Запросы: # GET /api/articles/ # JSON (по умолчанию) # GET /api/articles/?format=json # GET /api/articles/?format=html # Browsable API

#Best Practices

  1. Единый формат ответа — обёртка {success, data, error, meta}
  2. JSONRenderer для production — отключите BrowsableAPIRenderer
  3. Кастомный exception_handler — единый формат ошибок
  4. ETag/Last-Modified — для кэширования
  5. Документируйте формат — укажите структуру ответа в API docs
  6. Используйте заголовки — для метаданных (пагинация, кэш)

Далее: Тестирование DRF