drf-spectacular: рабочая настройка OpenAPI-схемы в DRF
Как подключить drf-spectacular к Django REST Framework, получить корректную схему OpenAPI 3 и починить типичные места, где генератор угадывает неправильно.

Django REST Framework умеет строить схему сам, но с OpenAPI 2 и множеством белых пятен: ViewSet с фильтрами, кастомные сериализаторы, поля-перечисления. drf-spectacular генерирует полноценную схему OpenAPI 3, из которой Swagger UI и Redoc рисуют документацию без ручной правки YAML. В этом материале разбираем не «десять способов задокументировать API», а конкретные места, где generator угадывает тип неправильно, и как ему помочь через декораторы и настройки.
Ориентируемся на drf-spectacular 0.27+ и Django REST Framework 3.14+. Синтаксис декораторов между версиями библиотеки почти не менялся, но набор настроек в SPECTACULAR_SETTINGS растёт, поэтому точный список полей сверяйте с документацией вашей версии.
Что даёт schema-first и почему стандартный генератор DRF мешает
DRF из коробки отдаёт схему через rest_framework.schemas, но она уровня OpenAPI 2 и плохо переваривает generic-вьюхи с несколькими сериализаторами на разных методах. Типичная боль: у вас один ViewSet, где list отдаёт краткий сериализатор, а retrieve — полный. Стандартный генератор покажет один и тот же ответ для обоих. drf-spectacular решает это через @extend_schema, который навешивается на метод и переопределяет то, что автогенератор вывел неверно.
Генератор схемы — не магия. Он читает аннотации типов,serializer_classиqueryset. Всё, что вы задаёте динамически в рантайме, он вынужден угадывать — и именно там появляются ошибки в схеме.
Базовая установка
Что нужно иметь до начала: работающий проект на Django с DRF, доступ к settings.py и urls.py.
- Установите пакет:
pip install drf-spectacular - Добавьте приложение в
INSTALLED_APPSи укажите класс генератора в настройках DRF:# settings.py INSTALLED_APPS = [ # ... 'rest_framework', 'drf_spectacular', ] REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', } SPECTACULAR_SETTINGS = { 'TITLE': 'Shop API', 'DESCRIPTION': 'Внутренний API магазина', 'VERSION': '1.0.0', # схему самого эндпоинта в неё не включаем 'SERVE_INCLUDE_SCHEMA': False, } - Подключите три URL: сырую схему и два UI:
# urls.py from django.urls import path from drf_spectacular.views import ( SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView, ) urlpatterns = [ # выдаёт schema.yaml path('api/schema/', SpectacularAPIView.as_view(), name='schema'), # Swagger UI path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), # Redoc path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), ]
Откройте /api/docs/ — должен отрисоваться Swagger UI со всеми вашими эндпоинтами. Если страница пустая или ругается на схему, сначала проверьте сырой вывод.
Проверка схемы из консоли
drf-spectacular добавляет management-команду, которая выводит схему и, что важнее, показывает предупреждения генератора:
# сохранить схему в файл
python manage.py spectacular --file schema.yaml
# показать все предупреждения о неоднозначностях
python manage.py spectacular --validate --fail-on-warnФлаг --fail-on-warn завершает команду с ненулевым кодом при любом предупреждении. Это то, что стоит поставить в CI: схема, которую генератор построил «с оговорками», через месяц разойдётся с реальным API.
Где генератор угадывает неправильно
Дальше — четыре ситуации, в которых автовывод даёт неверную или неполную схему, и точечная правка через декораторы.
1. Разные сериализаторы на list и retrieve
Навешиваем @extend_schema на конкретные действия ViewSet через @extend_schema_view:
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import viewsets
@extend_schema_view(
list=extend_schema(responses=ProductListSerializer),
retrieve=extend_schema(responses=ProductDetailSerializer),
)
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductDetailSerializerТеперь в схеме GET /products/ и GET /products/{id}/ ссылаются на разные компоненты.
2. Ответ не совпадает с сериализатором запроса
Частый случай: на вход принимаете один набор полей, на выход отдаёте другой. Задайте request и responses раздельно, а коды статусов — ключами словаря:
from drf_spectacular.utils import extend_schema, OpenApiResponse
class LoginView(APIView):
@extend_schema(
request=LoginSerializer,
responses={
200: TokenSerializer,
401: OpenApiResponse(description='Неверные учётные данные'),
},
)
def post(self, request):
...3. Параметры запроса, которых генератор не видит
Фильтры, которые вы читаете напрямую из request.query_params, генератор не найдёт — их нет в сериализаторе. Опишите их явно:
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
@extend_schema(
parameters=[
OpenApiParameter(
name='category',
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
description='Слаг категории для фильтрации',
),
OpenApiParameter(
name='in_stock',
type=OpenApiTypes.BOOL,
location=OpenApiParameter.QUERY,
),
],
)
def get(self, request):
...Если фильтрация построена на django-filter через DjangoFilterBackend, отдельного описания параметров не нужно: drf-spectacular читает FilterSet и добавляет их в схему сам.
4. SerializerMethodField с непонятным типом
Для SerializerMethodField генератор выводит тип string по умолчанию, потому что не может проверить, что реально возвращает метод. Подскажите ему через @extend_schema_field:
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes
from rest_framework import serializers
class OrderSerializer(serializers.ModelSerializer):
total = serializers.SerializerMethodField()
@extend_schema_field(OpenApiTypes.DECIMAL)
def get_total(self, obj):
return obj.calculate_total()Сравнение способов задать ответ
| Способ | Когда применять | Что теряете |
|---|---|---|
Автовывод по serializer_class | Один сериализатор на все методы | Не работает при разных ответах на разные action |
@extend_schema(responses=...) | Ответ отличается от запроса или зависит от метода | Нужно поддерживать вручную при изменении вьюхи |
inline_serializer | Разовый ответ без отдельного класса сериализатора | Не переиспользуется, компонент не именуется осмысленно |
OpenApiResponse с описанием | Ошибочные коды без тела (401, 403, 404) | Не описывает структуру тела ответа |
Разовый ответ без отдельного класса
Когда заводить полноценный сериализатор ради одного эндпоинта не хочется, есть inline_serializer — он создаёт схему на лету:
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework import serializers
@extend_schema(
responses=inline_serializer(
name='HealthCheckResponse',
fields={
'status': serializers.CharField(),
'uptime_seconds': serializers.IntegerField(),
},
)
)
def healthcheck(request):
...Имя из name станет именем компонента в схеме, поэтому делайте его уникальным — при совпадении имён компоненты склеятся.
Если не получилось
- «could not resolve serializer for X. Defaulting to generic» в выводе
spectacular --validate— генератор не смог определить сериализатор для вьюхи. Обычно это generic-вью безserializer_classлибо метод без@extend_schema. Навесьте декоратор явно. - «unable to guess serializer. This is graceful fallback» — то же самое для
APIView, где нет ниserializer_class, ни аннотаций. Опишитеrequestиresponsesруками. - Swagger UI пустой, а
/api/schema/отдаёт YAML — проверьте, чтоurl_name='schema'в UI-вью совпадает сname='schema'в маршруте схемы. Опечатка в имени разрывает связь. - Дублирующиеся
operationId— предупреждение появляется, когда два эндпоинта получают одинаковый идентификатор операции. Задайте уникальный через@extend_schema(operation_id='...'). - Enum-поля размножаются как
StatusEnum,Status2Enum— включите'ENUM_NAME_OVERRIDES'вSPECTACULAR_SETTINGS, чтобы задать одно имя для повторяющегося набора значений. Точный формат ключа сверьте с документацией вашей версии.
Что делать дальше
Поставьте python manage.py spectacular --validate --fail-on-warn в CI — так схема не разъедется с кодом незаметно. Дальше стоит посмотреть в сторону hooks для постобработки схемы (например, вырезать служебные эндпоинты) и генерации типизированного клиента: из готовой OpenAPI 3-схемы фронтенд-команда получает TypeScript-типы через сторонние генераторы, не сверяясь с бэкендом руками. Инвалидация и версионирование схемы при нескольких версиях API — отдельная тема, здесь мы её не разбирали.
Генератор изображений: Иллюстрация, обложка, баннер, схема — по текстовому описанию, без исходного фото.
Спросить за 20 ₽Источники: drf-spectacular — официальная документация, drf-spectacular на GitHub
Частые вопросы
У меня старая версия drf-spectacular, декораторы называются иначе?
Работает ли drf-spectacular со старым генератором схем DRF одновременно?
Что делать, если сгенерированная схема не проходит валидацию?
Нужно ли описывать параметры django-filter вручную?
Можно ли отдавать Swagger UI только в разработке, а в проде отключать?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.