Сериализаторы

⏱ 30 мин · 🧩 заданий: 6

Роль сериализатора

Сериализатор делает для API то же, что форма для HTML:

Направление Что делает
Объект → JSON (чтение) Выбирает и форматирует поля
JSON → объект (запись) Проверяет данные, создаёт или обновляет объект

Serializer: всё вручную

from rest_framework import serializers


class PostSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=250)
    body = serializers.CharField()
    publish = serializers.DateTimeField(read_only=True)

    def create(self, validated_data):
        return Post.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.title = validated_data.get("title", instance.title)
        instance.body = validated_data.get("body", instance.body)
        instance.save()
        return instance

ModelSerializer: по модели

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ["id", "title", "slug", "author", "body", "publish", "status"]
        read_only_fields = ["id", "author", "publish"]

Поля, их типы, валидация (max_length, unique, choices) и методы create()/update() создаются автоматически.

Представление связей

По умолчанию ForeignKey выводится как id. Варианты:

class PostSerializer(serializers.ModelSerializer):
    # 1. Одно поле связанного объекта
    author = serializers.ReadOnlyField(source="author.username")

    # 2. Строковое представление (__str__)
    author = serializers.StringRelatedField()

    # 3. Слаг вместо id — и для чтения, и для записи
    category = serializers.SlugRelatedField(slug_field="slug", queryset=Category.objects.all())

    # 4. Ссылка на ресурс
    author = serializers.HyperlinkedRelatedField(view_name="api:user_detail", read_only=True)

Теги django-taggit можно вывести как список строк:

tags = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")

Вложенные сериализаторы

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ["id", "name", "body", "created"]


class PostDetailSerializer(serializers.ModelSerializer):
    author = serializers.ReadOnlyField(source="author.username")
    comments = CommentSerializer(many=True, read_only=True)

    class Meta:
        model = Post
        fields = ["id", "title", "author", "body", "publish", "comments"]
{
  "id": 1,
  "title": "Первый пост",
  "author": "admin",
  "comments": [
    {"id": 7, "name": "Анна", "body": "Отличный пост!", "created": "2026-10-25T12:00:00+03:00"}
  ]
}

Часто делают два сериализатора: короткий для списка и подробный для детальной страницы.

N+1 в API

Вложенные сериализаторы и source="author.username" обращаются к связанным объектам. Не забывайте select_related("author") и prefetch_related("comments") в queryset представления.

Вычисляемые поля

class PostSerializer(serializers.ModelSerializer):
    comments_count = serializers.SerializerMethodField()
    url = serializers.SerializerMethodField()

    class Meta:
        model = Post
        fields = ["id", "title", "comments_count", "url"]

    def get_comments_count(self, obj):
        return obj.comments.count()

    def get_url(self, obj):
        request = self.context.get("request")
        return request.build_absolute_uri(obj.get_absolute_url())

Generic-представления DRF сами передают request в self.context.

Валидация

Как в формах, только методы называются иначе:

class PostSerializer(serializers.ModelSerializer):
    ...

    def validate_title(self, value):          # одно поле (в формах — clean_title)
        if len(value) < 5:
            raise serializers.ValidationError("Слишком короткий заголовок")
        return value

    def validate(self, data):                 # несколько полей (в формах — clean)
        if data.get("status") == Post.Status.PUBLISHED and not data.get("body"):
            raise serializers.ValidationError("Нельзя опубликовать пустой пост")
        return data

Ошибки возвращаются клиенту с кодом 400:

{"title": ["Слишком короткий заголовок"]}

Сохранение

serializer = PostSerializer(data=request.data)
serializer.is_valid(raise_exception=True)   # при ошибке DRF вернёт 400
post = serializer.save(author=request.user) # дополнительные поля передаются в save()

serializer = PostSerializer(post, data=request.data, partial=True)  # обновление (PATCH)

save(author=request.user) — аналог form.save(commit=False) + post.author = ....

Итог

  • ModelSerializer строит поля и валидацию по модели; read_only_fields — поля только для чтения.
  • Связи: ReadOnlyField(source=...), StringRelatedField, SlugRelatedField, вложенные сериализаторы.
  • SerializerMethodField + get_<поле> — вычисляемые значения.
  • Валидация: validate_<поле> и validate; сохранение: is_valid() → save(**доп_поля).

Практика решено 0 из 6

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Написать код

В PostSerializer выведите автора по имени пользователя вместо id: объявите поле author = serializers.ReadOnlyField(source="author.username").

Задание 2 Написать код

Добавьте в сериализатор вычисляемое поле comments_count через SerializerMethodField и метод get_comments_count, возвращающий количество комментариев поста (obj.comments.count()).

Задание 3 Написать код

Добавьте в сериализатор проверку поля title: если длина меньше 5 символов, выбросьте serializers.ValidationError("Слишком короткий заголовок"), иначе верните значение.

Задание 4 Один вариант ответа

Как вывести у поста список комментариев с их полями (name, body), а не только id?

Задание 5 Расставить по порядку

Расставьте шаги создания объекта через сериализатор.

  1. return Response(serializer.data, status=201)
  2. serializer.save(author=request.user)
  3. serializer.is_valid(raise_exception=True)
  4. serializer = PostSerializer(data=request.data)
Задание 6 Несколько вариантов

Какие поля сериализатора только для чтения (не принимаются при записи)?

Войдите, чтобы отмечать пройденные уроки.