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

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

@potapov_me

Платформа

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

Контент

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

Компания

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

Аккаунт

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

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

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. DRF: Сериализаторы
drf_serializers

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

Serializer, ModelSerializer, nested serializers, validation

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

Статус: актуально для Django 5.2/6.0 и современных версий Django REST Framework.

Сериализатор задаёт контракт API: какие данные приложение принимает, как их проверяет и какие поля возвращает. При этом он не «превращает модель в JSON» сам по себе. Свойство serializer.data содержит обычные Python-примитивы — словари, списки, строки и числа. В JSON их преобразует renderer DRF перед отправкой ответа.

Это различие полезно на практике: один сериализатор можно использовать с JSON, HTML-формой или другим renderer. Авторизация, транзакции и сложные правила предметной области при этом не должны незаметно растворяться в слое представления данных.

#Два направления работы

При чтении сериализатор получает объект и строит представление:

serializer = BookSerializer(book) payload = serializer.data

При записи он проходит другой путь:

serializer = BookSerializer(data=request.data) serializer.is_valid(raise_exception=True) book = serializer.save()

После успешной проверки вход доступен в validated_data, а после save() — созданный или изменённый объект в instance. Не обращайтесь к .data до .save(): после построения выходного представления DRF уже не разрешит сохранить этот сериализатор.

#Serializer и ModelSerializer

Обычный Serializer похож на Django Form: поля и сохранение задаются явно. Это удобно для команд, которые не совпадают один к одному с моделью.

from rest_framework import serializers from library.models import Book class BookSerializer(serializers.Serializer): id = serializers.IntegerField(read_only=True) title = serializers.CharField(max_length=200) price = serializers.DecimalField(max_digits=10, decimal_places=2) def validate_price(self, value): if value < 0: raise serializers.ValidationError("Цена не может быть отрицательной.") return value def create(self, validated_data): return Book.objects.create(**validated_data) def update(self, instance, validated_data): instance.title = validated_data.get("title", instance.title) instance.price = validated_data.get("price", instance.price) instance.save(update_fields=["title", "price"]) return instance

ModelSerializer выводит типы полей и часть валидаторов из модели, но контракт API всё равно следует перечислять явно.

from rest_framework import serializers from blog.models import Post class PostSerializer(serializers.ModelSerializer): class Meta: model = Post fields = ["id", "title", "slug", "body", "created_at"] read_only_fields = ["id", "slug", "created_at"]

Не используйте fields = "__all__" в публичном API. После добавления в модель служебного или персонального поля оно может случайно попасть в ответ или стать доступным для записи. Явный список делает изменение контракта осознанным.

#Поля только для чтения и записи

read_only=True исключает поле из входных данных, а write_only=True — из ответа. Это поведение сериализации, а не система прав доступа. Пользователь всё равно должен пройти permission-проверки, а связанный объект — принадлежать допустимому проекту или арендатору.

Типичный пример: автора поста нельзя принимать от клиента. View уже знает текущего пользователя:

class PostWriteSerializer(serializers.ModelSerializer): class Meta: model = Post fields = ["id", "title", "body", "category"] read_only_fields = ["id"]
def perform_create(self, serializer): serializer.save(author=self.request.user)

Так клиент не сможет подставить чужой author_id. Но одной этой меры недостаточно: доступ к категории и самому объекту также нужно ограничить на уровне queryset и permissions.

#Валидация поля и объекта

Метод validate_<field>() проверяет одно поле, а validate() — сочетание нескольких. При PATCH часть полей может отсутствовать, поэтому сравнивать только значения из attrs неверно: нужно учитывать текущее состояние объекта.

class EventSerializer(serializers.ModelSerializer): class Meta: model = Event fields = ["id", "name", "starts_at", "ends_at"] read_only_fields = ["id"] def validate(self, attrs): starts_at = attrs.get( "starts_at", getattr(self.instance, "starts_at", None), ) ends_at = attrs.get( "ends_at", getattr(self.instance, "ends_at", None), ) if starts_at and ends_at and starts_at >= ends_at: raise serializers.ValidationError( {"ends_at": "Окончание должно быть позже начала."} ) return attrs

Проверка уникальности в сериализаторе даёт понятную ошибку, но не защищает от гонки двух запросов. Инвариант должен поддерживать UniqueConstraint в базе данных, а приложение — корректно обрабатывать IntegrityError на границе транзакции.

Сложные бизнес-операции лучше передавать сервисной функции. Сериализатор отвечает за форму команды, сервис — за права на связанные объекты, блокировки, транзакцию и побочные эффекты.

#Связи и границы доступа

PrimaryKeyRelatedField удобен для записи внешних ключей и many-to-many связей. Опасный вариант — безусловный queryset=Category.objects.all() в многопользовательском приложении: валидным станет идентификатор категории чужой организации.

Queryset поля можно сузить при инициализации:

class PostWriteSerializer(serializers.ModelSerializer): category = serializers.PrimaryKeyRelatedField( queryset=Category.objects.none(), ) tags = serializers.PrimaryKeyRelatedField( many=True, queryset=Tag.objects.none(), required=False, ) class Meta: model = Post fields = ["title", "body", "category", "tags"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) tenant = self.context["tenant"] self.fields["category"].queryset = Category.objects.filter(tenant=tenant) self.fields["tags"].queryset = Tag.objects.filter(tenant=tenant)

Контекст должен задаваться сервером, а не данными клиента. Generic views DRF автоматически добавляют в context request, format и view; собственные значения добавляют через get_serializer_context().

StringRelatedField зависит от __str__() модели. Это удобно для внутреннего интерфейса, но нестабильно как публичный контракт: изменение человекочитаемой строки незаметно изменит API. Для внешнего API надёжнее явные поля или отдельный компактный сериализатор.

HyperlinkedModelSerializer и hyperlink-поля обычно требуют request в context, чтобы построить абсолютные URL.

#Вложенное чтение и запись

Вложенный сериализатор хорошо подходит для чтения:

class PublicAuthorSerializer(serializers.ModelSerializer): class Meta: model = User fields = ["id", "display_name"] class PostReadSerializer(serializers.ModelSerializer): author = PublicAuthorSerializer(read_only=True) class Meta: model = Post fields = ["id", "title", "body", "author", "created_at"]

Не включайте email в публичный профиль по привычке. Для кабинета пользователя создайте отдельный приватный сериализатор.

Записываемые вложенные структуры DRF не сохраняет автоматически. Нужно определить семантику: заменить весь список, дополнить его, обновить элементы по id или удалить отсутствующие. Такая операция обычно требует transaction.atomic() и проверки прав для каждого вложенного объекта. Если клиенту достаточно передать существующие идентификаторы, PrimaryKeyRelatedField(many=True) проще и прозрачнее.

#Производительность сериализации

SerializerMethodField вызывается для каждого объекта. Запрос вида obj.comments.count() внутри этого метода создаёт N+1 запрос. Сериализатор не должен тайно исправлять эту проблему: view обязана подготовить queryset.

class PostListSerializer(serializers.ModelSerializer): comment_count = serializers.IntegerField(read_only=True) is_liked = serializers.BooleanField(read_only=True) class Meta: model = Post fields = ["id", "title", "comment_count", "is_liked"]

Поля comment_count и is_liked здесь ожидают аннотации Count и Exists из queryset. Связи для вложенного чтения загружают через select_related() и prefetch_related(). Проверяйте число запросов отдельным тестом списка, а не только время ответа на маленькой базе.

#Пароли и регистрация

Пароль нельзя сохранять обычным присваиванием: оно запишет исходную строку. Используйте стандартную политику Django и менеджер пользователя.

from django.contrib.auth import get_user_model from django.contrib.auth.password_validation import validate_password from rest_framework import serializers User = get_user_model() class RegistrationSerializer(serializers.ModelSerializer): password = serializers.CharField( write_only=True, trim_whitespace=False, style={"input_type": "password"}, ) class Meta: model = User fields = [User.USERNAME_FIELD, "password"] def validate(self, attrs): user = User(**{User.USERNAME_FIELD: attrs[User.USERNAME_FIELD]}) validate_password(attrs["password"], user=user) return attrs def create(self, validated_data): return User.objects.create_user(**validated_data)

Ограничение уникальности логина или email закрепляют в модели и базе. Наличие endpoint регистрации, подтверждение адреса, защита от перебора и политика раскрытия ошибок — отдельные продуктовые решения.

#Файлы и массовые операции

FileField и ImageField проверяют форму входа, но не делают загруженный файл безопасным. Ограничивайте размер, проверяйте фактический формат, генерируйте имя на сервере, храните пользовательские файлы отдельно от исполняемого кода и при необходимости сканируйте их асинхронно.

many=True выбирает ListSerializer и умеет сериализовать список. Это не означает безопасный bulk create или bulk update. Для массовой записи нужно отдельно определить атомарность, соответствие элементов объектам, лимит размера запроса и формат частичных ошибок.

#Что считать legacy

  • Формулировка «сериализатор преобразует модель в JSON» допустима как первое приближение, но технически JSON создаёт renderer.
  • fields = "__all__" не удалено, однако для внешнего API считается хрупкой практикой.
  • Один универсальный сериализатор для списка, детального просмотра, записи и приватного кабинета быстро приводит к утечкам и лишним запросам. Разделение read/write/public/private-сериализаторов — нормальное развитие проекта, а не избыточность.

Главный практический критерий хорошего сериализатора: его поля образуют явный и стабильный контракт, а стоимость запросов, права доступа и транзакционные правила остаются видимыми в соответствующих слоях приложения.

Далее: DRF: Views & ViewSets