Serializer, ModelSerializer, nested serializers, validation
Сериализаторы — основа DRF. Они преобразуют сложные типы данных (модели Django) в JSON и обратно.
💡 Правило: Сериализаторы — это не только про JSON. Это слой абстракции между вашими данными и API. Используйте их для валидации и бизнес-логики представления.
# serializers.py
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=200)
author = serializers.CharField(max_length=100)
published_date = serializers.DateField()
price = serializers.DecimalField(max_digits=10, decimal_places=2)
is_available = serializers.BooleanField(default=True)
# Валидация поля
def validate_title(self, value):
if len(value) < 3:
raise serializers.ValidationError('Title must be at least 3 characters')
return value
# Общая валидация
def validate(self, data):
if data.get('price', 0) < 0:
raise serializers.ValidationError('Price cannot be negative')
return data
# Создание объекта
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.author = validated_data.get('author', instance.author)
instance.price = validated_data.get('price', instance.price)
instance.save()
return instancefrom rest_framework import serializers
from blog.models import Post, Category, Tag
class PostSerializer(serializers.ModelSerializer):
"""Автоматический сериализатор из модели."""
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'content', 'author', 'category', 'tags', 'created_at']
# Или fields = '__all__' для всех полей
# exclude = ['is_deleted'] # Исключить поле
# Дополнительные настройки
read_only_fields = ['slug', 'created_at', 'updated_at']
extra_kwargs = {
'content': {'required': False},
'author': {'write_only': True},
}class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email']
class PostSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True) # Вложенный для чтения
category = serializers.StringRelatedField(read_only=True) # __str__ представление
tags = serializers.SlugRelatedField(
many=True,
read_only=True,
slug_field='name'
)
class Meta:
model = Post
fields = ['id', 'title', 'content', 'author', 'category', 'tags', 'created_at']class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name']
class PostCreateSerializer(serializers.ModelSerializer):
"""Сериализатор для создания поста с вложенными тегами."""
tags = TagSerializer(many=True, required=False)
class Meta:
model = Post
fields = ['id', 'title', 'content', 'category', 'tags']
def create(self, validated_data):
tags_data = validated_data.pop('tags', [])
post = Post.objects.create(**validated_data)
for tag_data in tags_data:
tag, created = Tag.objects.get_or_create(**tag_data)
post.tags.add(tag)
return post
def update(self, instance, validated_data):
tags_data = validated_data.pop('tags', None)
if tags_data is not None:
instance.tags.clear()
for tag_data in tags_data:
tag, created = Tag.objects.get_or_create(**tag_data)
instance.tags.add(tag)
return super().update(instance, validated_data)from rest_framework import serializers
class ExampleSerializer(serializers.Serializer):
# Строковые
char_field = serializers.CharField(max_length=100)
email_field = serializers.EmailField()
url_field = serializers.URLField()
slug_field = serializers.SlugField()
regex_field = serializers.RegexField(regex=r'^[A-Z]{3}$')
# Числовые
integer_field = serializers.IntegerField()
float_field = serializers.FloatField()
decimal_field = serializers.DecimalField(max_digits=10, decimal_places=2)
# Дата и время
date_field = serializers.DateField()
datetime_field = serializers.DateTimeField()
time_field = serializers.TimeField()
duration_field = serializers.DurationField()
# Логические
boolean_field = serializers.BooleanField()
null_boolean_field = serializers.BooleanField(allow_null=True)
# Выбор
choice_field = serializers.ChoiceField(choices=[('A', 'Option A'), ('B', 'Option B')])
multiple_choice_field = serializers.MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')])
# Файлы
file_field = serializers.FileField()
image_field = serializers.ImageField()
# Разное
json_field = serializers.JSONField()
uuid_field = serializers.UUIDField()
hidden_field = serializers.HiddenField(default='hidden_value')class PostSerializer(serializers.ModelSerializer):
# StringRelated: использует __str__()
author = serializers.StringRelatedField(read_only=True)
# PrimaryKeyRelatedField: только ID
category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all())
# SlugRelatedField: по slug
category = serializers.SlugRelatedField(
queryset=Category.objects.all(),
slug_field='slug'
)
# HyperlinkedRelatedField: URL
category = serializers.HyperlinkedRelatedField(
view_name='category-detail',
read_only=True
)
# Nested serializer: полный объект
author = AuthorSerializer(read_only=True)class PostSerializer(serializers.ModelSerializer):
"""Сериализатор с вычисляемыми полями."""
# Вычисляемое поле
is_author = serializers.SerializerMethodField()
comment_count = serializers.SerializerMethodField()
reading_time = serializers.SerializerMethodField()
full_author_name = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ['id', 'title', 'content', 'is_author', 'comment_count', 'reading_time', 'full_author_name']
def get_is_author(self, obj):
"""Проверка является ли текущий пользователь автором."""
request = self.context.get('request')
if request and hasattr(request, 'user'):
return obj.author == request.user
return False
def get_comment_count(self, obj):
"""Количество комментариев."""
return obj.comments.count()
def get_reading_time(self, obj):
"""Время чтения в минутах (примерно 200 слов в минуту)."""
word_count = len(obj.content.split())
return round(word_count / 200) + 1
def get_full_author_name(self, obj):
"""Полное имя автора."""
return f'{obj.author.first_name} {obj.author.last_name}'.strip()class UserRegistrationSerializer(serializers.ModelSerializer):
username = serializers.CharField(min_length=3, max_length=150)
email = serializers.EmailField()
password = serializers.CharField(write_only=True, min_length=8)
class Meta:
model = User
fields = ['username', 'email', 'password']
def validate_username(self, value):
"""Проверка username."""
if not value.isalnum():
raise serializers.ValidationError('Username can only contain letters and numbers')
if User.objects.filter(username=value).exists():
raise serializers.ValidationError('Username already taken')
return value
def validate_email(self, value):
"""Проверка email."""
if User.objects.filter(email=value).exists():
raise serializers.ValidationError('Email already registered')
# Проверка домена
allowed_domains = ['example.com', 'company.com']
domain = value.split('@')[1]
if domain not in allowed_domains:
raise serializers.ValidationError(f'Email must be from {allowed_domains}')
return value
def validate_password(self, value):
"""Проверка сложности пароля."""
if not any(c.isupper() for c in value):
raise serializers.ValidationError('Password must contain uppercase letter')
if not any(c.isdigit() for c in value):
raise serializers.ValidationError('Password must contain a digit')
return valueclass EventSerializer(serializers.ModelSerializer):
start_date = serializers.DateTimeField()
end_date = serializers.DateTimeField()
class Meta:
model = Event
fields = ['name', 'start_date', 'end_date']
def validate(self, data):
"""Общая валидация нескольких полей."""
if data.get('start_date') and data.get('end_date'):
if data['start_date'] > data['end_date']:
raise serializers.ValidationError({
'end_date': 'End date must be after start date'
})
# Проверка что событие не в прошлом
if data['start_date'] < timezone.now():
raise serializers.ValidationError({
'start_date': 'Start date cannot be in the past'
})
return dataclass PostSerializer(serializers.ModelSerializer):
is_liked = serializers.SerializerMethodField()
can_edit = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ['id', 'title', 'content', 'is_liked', 'can_edit']
def get_is_liked(self, obj):
"""Проверка лайка от текущего пользователя."""
request = self.context.get('request')
if request and hasattr(request, 'user'):
return obj.likes.filter(user=request.user).exists()
return False
def get_can_edit(self, obj):
"""Проверка права на редактирование."""
request = self.context.get('request')
if request and hasattr(request, 'user'):
user = request.user
return (
obj.author == user or
user.has_perm('blog.change_post') or
user.is_superuser
)
return False
# В view
serializer = PostSerializer(post, context={'request': request})class DynamicFieldsSerializer(serializers.ModelSerializer):
"""Сериализатор с динамическими полями."""
def __init__(self, *args, **kwargs):
# Извлечь какие поля включить/исключить
fields_to_include = kwargs.pop('include_fields', None)
fields_to_exclude = kwargs.pop('exclude_fields', None)
super().__init__(*args, **kwargs)
if fields_to_include:
allowed = set(fields_to_include)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
if fields_to_exclude:
for field_name in fields_to_exclude:
self.fields.pop(field_name, None)
# Использование
serializer = DynamicFieldsSerializer(
post,
include_fields=['id', 'title', 'author'],
exclude_fields=['content']
)# blog/serializers.py
from rest_framework import serializers
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from .models import Category, Tag, Post, Comment
User = get_user_model()
class CategorySerializer(serializers.ModelSerializer):
post_count = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'description', 'post_count']
read_only_fields = ['slug']
def get_post_count(self, obj):
return obj.posts.filter(is_published=True).count()
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug']
read_only_fields = ['slug']
class CommentSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField(read_only=True)
class Meta:
model = Comment
fields = ['id', 'post', 'author', 'content', 'is_approved', 'created_at']
read_only_fields = ['is_approved', 'created_at']
def create(self, validated_data):
validated_data['is_approved'] = False # Все комментарии требуют модерации
return super().create(validated_data)
class PostListSerializer(serializers.ModelSerializer):
"""Сериализатор для списка постов."""
author = serializers.StringRelatedField(read_only=True)
category = CategorySerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
comment_count = serializers.SerializerMethodField()
class Meta:
model = Post
fields = [
'id', 'title', 'slug', 'excerpt', 'author',
'category', 'tags', 'comment_count', 'created_at'
]
def get_comment_count(self, obj):
return obj.comments.filter(is_approved=True).count()
class PostDetailSerializer(serializers.ModelSerializer):
"""Сериализатор для детального просмотра."""
author = UserSerializer(read_only=True)
category = CategorySerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
comments = CommentSerializer(many=True, read_only=True)
is_liked = serializers.SerializerMethodField()
can_edit = serializers.SerializerMethodField()
class Meta:
model = Post
fields = [
'id', 'title', 'slug', 'content', 'excerpt',
'author', 'category', 'tags', 'comments',
'is_liked', 'can_edit', 'views', 'created_at', 'updated_at'
]
def get_is_liked(self, obj):
request = self.context.get('request')
if request and hasattr(request, 'user') and request.user.is_authenticated:
return obj.likes.filter(user=request.user).exists()
return False
def get_can_edit(self, obj):
request = self.context.get('request')
if request and hasattr(request, 'user'):
user = request.user
return obj.author == user or user.has_perm('blog.change_post')
return False
class PostCreateUpdateSerializer(serializers.ModelSerializer):
"""Сериализатор для создания/обновления поста."""
tags = TagSerializer(many=True, required=False)
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'content', 'excerpt', 'category', 'tags']
def validate_title(self, value):
if len(value) < 3:
raise serializers.ValidationError('Title must be at least 3 characters')
return value
def validate_content(self, value):
if len(value) < 50:
raise serializers.ValidationError('Content must be at least 50 characters')
return value
def create(self, validated_data):
tags_data = validated_data.pop('tags', [])
request = self.context.get('request')
post = Post.objects.create(author=request.user, **validated_data)
for tag_data in tags_data:
tag, created = Tag.objects.get_or_create(**tag_data)
post.tags.add(tag)
return post
def update(self, instance, validated_data):
tags_data = validated_data.pop('tags', None)
if tags_data is not None:
instance.tags.clear()
for tag_data in tags_data:
tag, created = Tag.objects.get_or_create(**tag_data)
instance.tags.add(tag)
return super().update(instance, validated_data)
class UserRegistrationSerializer(serializers.ModelSerializer):
"""Сериализатор регистрации пользователя."""
password = serializers.CharField(write_only=True, validators=[validate_password])
password_confirm = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ['username', 'email', 'password', 'password_confirm']
def validate(self, attrs):
if attrs['password'] != attrs['password_confirm']:
raise serializers.ValidationError({'password': 'Passwords do not match'})
return attrs
def create(self, validated_data):
validated_data.pop('password_confirm')
user = User.objects.create_user(**validated_data)
return user
class UserSerializer(serializers.ModelSerializer):
"""Сериализатор пользователя."""
post_count = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'post_count']
read_only_fields = ['id']
def get_post_count(self, obj):
return obj.posts.filter(is_published=True).count()Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.