Базовые сериализаторы, ModelSerializer, валидация, вложенные сериализаторы
Сериализаторы — это сердце DRF. Они преобразуют сложные объекты (модели Django) в простые типы данных для JSON и обратно.
Сериализация — процесс преобразования объекта в формат, который можно легко передать по сети (JSON, XML). Десериализация — обратный процесс.
# Сериализация: объект Python → JSON
article = Article.objects.get(pk=1)
serializer = ArticleSerializer(article)
print(serializer.data) # {'id': 1, 'title': '...', ...}
# Десериализация: JSON → объект Python
data = {'title': 'Новая статья', 'content': 'Текст'}
serializer = ArticleSerializer(data=data)
if serializer.is_valid():
article = serializer.save()Можно создать сериализатор вручную, определив все поля:
from rest_framework import serializers
class ArticleSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=200)
content = serializers.CharField()
author = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
created_at = serializers.DateTimeField(read_only=True)
def create(self, validated_data):
return Article.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.content = validated_data.get('content', instance.content)
instance.save()
return instanceНедостатки:
create() и update()ModelSerializer автоматически создаёт поля на основе модели:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author', 'created_at']
# или fields = '__all__' для всех полейПреимущества:
create() и update()class ExampleSerializer(serializers.Serializer):
# Строковые
char_field = serializers.CharField(max_length=100)
email = serializers.EmailField()
url = serializers.URLField()
slug = serializers.SlugField()
# Числовые
integer = serializers.IntegerField()
float_field = serializers.FloatField()
decimal = serializers.DecimalField(max_digits=10, decimal_places=2)
# Дата и время
date = serializers.DateField()
time = serializers.TimeField()
datetime = serializers.DateTimeField()
duration = serializers.DurationField()
# Булевы
boolean = serializers.BooleanField()
null_boolean = serializers.BooleanField(allow_null=True)
# Выбор вариантов
choice = serializers.ChoiceField(choices=[('a', 'Option A'), ('b', 'Option B')])
# Файлы
file = serializers.FileField()
image = serializers.ImageField()
# Связи
foreign_key = serializers.PrimaryKeyRelatedField(queryset=OtherModel.objects.all())
many_to_many = serializers.ManyRelatedField(child=serializers.PrimaryKeyRelatedField(...))
# Прочие
json_field = serializers.JSONField()
readonly = serializers.CharField(read_only=True)Метод validate_<field>() вызывается для конкретного поля:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['title', 'content', 'price']
def validate_title(self, value):
"""Проверка заголовка"""
if len(value) < 5:
raise serializers.ValidationError("Заголовок слишком короткий")
if Article.objects.filter(title=value).exists():
raise serializers.ValidationError("Такой заголовок уже существует")
return value
def validate_price(self, value):
"""Проверка цены"""
if value <= 0:
raise serializers.ValidationError("Цена должна быть положительной")
return valueМетод validate() проверяет несколько полей одновременно:
def validate(self, data):
"""Проверка нескольких полей"""
if data.get('title') and data.get('content'):
if data['title'] in data['content']:
raise serializers.ValidationError(
"Заголовок не должен содержаться в тексте статьи"
)
return dataclass UserSerializer(serializers.ModelSerializer):
# Только для чтения — отображается в ответе, но не принимается
created_at = serializers.DateTimeField(read_only=True)
# Только для записи — принимается, но не отображается
password = serializers.CharField(write_only=True, min_length=8)
# Через extra_kwargs
class Meta:
model = User
fields = ['id', 'username', 'email', 'created_at', 'password']
read_only_fields = ['created_at', 'id']
extra_kwargs = {
'password': {'write_only': True},
'email': {'required': True},
}class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name', 'email']
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True) # Вложенный сериализатор
author_id = serializers.PrimaryKeyRelatedField(
queryset=Author.objects.all(),
source='author',
write_only=True
)
class Meta:
model = Book
fields = ['id', 'title', 'author', 'author_id']Результат:
{
"id": 1,
"title": "Python Guide",
"author": {
"id": 5,
"name": "John Doe",
"email": "john@example.com"
}
}class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'author']
def create(self, validated_data):
# author уже является объектом Author благодаря PrimaryKeyRelatedField
return Book.objects.create(**validated_data)Поле, значение которого вычисляется методом:
class ArticleSerializer(serializers.ModelSerializer):
author_name = serializers.SerializerMethodField()
is_recent = serializers.SerializerMethodField()
class Meta:
model = Article
fields = ['id', 'title', 'author_name', 'is_recent']
def get_author_name(self, obj):
return obj.author.username
def get_is_recent(self, obj):
from datetime import timedelta
recent = timezone.now() - timedelta(days=7)
return obj.created_at > recentПравило: Метод должен называться get_<field_name>(self, obj).
Контекст передаёт дополнительные данные (например, request):
# В view
serializer = ArticleSerializer(article, context={'request': request})
# В сериализаторе
class ArticleSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
is_owner = serializers.SerializerMethodField()
class Meta:
model = Article
fields = ['id', 'title', 'url', 'is_owner']
def get_url(self, obj):
request = self.context['request']
return request.build_absolute_uri(obj.get_absolute_url())
def get_is_owner(self, obj):
request = self.context['request']
return obj.author == request.userМожно автоматически сериализовать связанные объекты:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
depth = 1 # Автоматически сериализует связанные объекты на 1 уровеньНедостаток: Меньше контроля над выводом. Лучше использовать явные вложенные сериализаторы.
Можно изменять поля сериализатора динамически:
class DynamicFieldsSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields', None)
super().__init__(*args, **kwargs)
if fields is not None:
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# Использование
serializer = DynamicFieldsSerializer(article, fields=['id', 'title'])fields = '__all__' для контроля APIselect_related в view для оптимизации запросов к связанным полямunique_together, constraintshelp_text в модели или сериализатореВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.