Гайды

Django REST Framework: сериализаторы и viewsets

ModelSerializer и валидация, APIView, generic views, ModelViewSet и DefaultRouter, пагинация и версионирование API.

~11 мин чтения

Django REST Framework: сериализаторы и viewsets

Django REST Framework (DRF) добавляет слой сериализаторов, generic views, viewsets и роутеров поверх Django. Этот гайд — ядро API без углубления в permissions (их легко добавить по документации DRF). Основа Django — Django: первый проект; ORM — оптимизация запросов.


1. Установка

bash
pip install djangorestframework

settings.py:

python
INSTALLED_APPS = [
    ...
    "rest_framework",
]

2. Сериализатор

python
from rest_framework import serializers
from shop.models import Product

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ["id", "name", "price", "created_at"]
        read_only_fields = ["id", "created_at"]

Serializer вручную — для нетипичных структур; ModelSerializer — быстрый старт.

Валидация:

python
def validate_price(self, value):
    if value <= 0:
        raise serializers.ValidationError("Price must be positive")
    return value

3. APIView (явный контроль)

python
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class ProductListView(APIView):
    def get(self, request):
        qs = Product.objects.all()[:50]
        return Response(ProductSerializer(qs, many=True).data)

    def post(self, request):
        ser = ProductSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        ser.save()
        return Response(ser.data, status=status.HTTP_201_CREATED)

4. Generic views

python
from rest_framework import generics

class ProductListCreate(generics.ListCreateAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

class ProductDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

5. ViewSet и Router

python
from rest_framework import viewsets

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

urls.py:

python
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r"products", ProductViewSet, basename="product")

urlpatterns = router.urls

Получите /products/, /products/{pk}/, при DefaultRouter — и /products/{pk}/ с trailing slash по настройкам Django.


6. Пагинация

python
REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
}

Для больших списков рассмотрите cursor pagination.


7. Версионирование API

Заголовок Accept: application/vnd.myapi+json; version=1, поддомен или URL path /api/v1/ — закрепите в команде одну стратегию; см. REST: HATEOAS и версионирование.


8. Поля, фильтры, ограничения

SerializerMethodField — вычисляемое поле из get_<name>; не злоупотребляйте N+1: лучше annotate() в queryset и обычное поле сериализатора. django-filter + DjangoFilterBackend снимают с эндпоинта ручной разбор query string.

throttle_scope / ScopedRateThrottle` защищают публичные списки и поиск; для аутентифицированных пользователей комбинируйте с permissions и квотами на уровне API gateway — см. CORS и лимиты в FastAPI для параллелей.

Глобально в REST_FRAMEWORK:

python
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": [
        "rest_framework.throttling.AnonRateThrottle",
        "rest_framework.throttling.UserRateThrottle",
    ],
    "DEFAULT_THROTTLE_RATES": {"anon": "100/hour", "user": "1000/hour"},
}

9. ViewSet: perform_* и владелец объекта

Не кладите бизнес-логику «кто может создать» только в сериализатор: в ModelViewSet переопределяйте perform_create (проставить user=request.user), perform_update (запрет смены owner чужим), get_queryset() — базовая фильтрация по тенанту/пользователю до любого retrieve.

python
class NoteViewSet(viewsets.ModelViewSet):
    serializer_class = NoteSerializer

    def get_queryset(self):
        return Note.objects.filter(owner=self.request.user)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

10. Кастомные действия @action

Для подресурсов и не-CRUD операций используйте @action(detail=True|False, methods=["post"]) вместо «левых» URL вне router — так проще permissions и OpenAPI-схема (если подключён drf-spectacular и т.п.).


11. Чек-лист

  • Не отдавать queryset с N+1 — select_related / prefetch_related в get_queryset().
  • Раздельные сериализаторы Read vs Write при разных полях.
  • perform_create / get_queryset согласованы с моделью прав доступа.
  • Тесты API — APITestCase или pytest-django — см. тестирование FastAPI для идей по фикстурам.

Дальше: REST и дизайн API · тег DRF