Skip to content

Serializers#

What are Serializers?#

Convert model instances ↔ JSON. Handle validation and data transformation.

ModelSerializer (80% use case)#

# myapp/serializers.py
from rest_framework import serializers
from .models import Post, Category

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'  # All fields
        # Or specify fields
        # fields = ['id', 'title', 'content', 'created_at']
        # Or exclude
        # exclude = ['slug', 'views']

    # Custom field
    author_name = serializers.CharField(source='author.username', read_only=True)

    # Computed field
    excerpt = serializers.SerializerMethodField()

    def get_excerpt(self, obj):
        return obj.content[:100] + '...' if len(obj.content) > 100 else obj.content

Field Types#

class PostSerializer(serializers.ModelSerializer):
    # String fields
    title = serializers.CharField(max_length=200)
    content = serializers.CharField(allow_blank=True)
    slug = serializers.SlugField(read_only=True)

    # Numeric
    views = serializers.IntegerField(read_only=True)
    price = serializers.DecimalField(max_digits=10, decimal_places=2)

    # Boolean
    is_published = serializers.BooleanField(default=False)

    # Date/Time
    created_at = serializers.DateTimeField(read_only=True)
    published_at = serializers.DateTimeField(required=False)

    # Related (ForeignKey)
    author = serializers.PrimaryKeyRelatedField(read_only=True)
    category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all())

    # ManyToMany
    tags = serializers.PrimaryKeyRelatedField(many=True, queryset=Tag.objects.all())

    # Nested (see below)
    # category_detail = CategorySerializer(source='category', read_only=True)

    class Meta:
        model = Post
        fields = '__all__'

Nested Serializers#

# Simple nested (read-only)
class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name', 'slug']

class PostSerializer(serializers.ModelSerializer):
    category_detail = CategorySerializer(source='category', read_only=True)

    class Meta:
        model = Post
        fields = ['id', 'title', 'category', 'category_detail']

# Writable nested
class PostSerializer(serializers.ModelSerializer):
    category = CategorySerializer()

    class Meta:
        model = Post
        fields = ['id', 'title', 'category']

    def create(self, validated_data):
        category_data = validated_data.pop('category')
        category = Category.objects.get_or_create(**category_data)[0]
        return Post.objects.create(category=category, **validated_data)

    def update(self, instance, validated_data):
        if 'category' in validated_data:
            category_data = validated_data.pop('category')
            category = Category.objects.get_or_create(**category_data)[0]
            instance.category = category
        return super().update(instance, validated_data)

Validation#

class PostSerializer(serializers.ModelSerializer):
    title = serializers.CharField(max_length=200)

    # Field-level validation
    def validate_title(self, value):
        if len(value) < 10:
            raise serializers.ValidationError("Title must be at least 10 characters")
        return value

    # Object-level validation
    def validate(self, data):
        if data.get('is_published') and not data.get('published_at'):
            raise serializers.ValidationError("Published posts must have published_at")
        return data

    class Meta:
        model = Post
        fields = '__all__'

Custom Serializers (non-model)#

class ContactSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=100)
    email = serializers.EmailField()
    message = serializers.CharField()

    def create(self, validated_data):
        # Handle creation logic
        return Contact.objects.create(**validated_data)

    def update(self, instance, validated_data):
        # Handle update logic
        instance.name = validated_data.get('name', instance.name)
        instance.save()
        return instance

Read/Write Fields#

class PostSerializer(serializers.ModelSerializer):
    # Read-only
    created_at = serializers.DateTimeField(read_only=True)
    author_name = serializers.CharField(source='author.username', read_only=True)

    # Write-only (for passwords, etc.)
    password = serializers.CharField(write_only=True)

    class Meta:
        model = Post
        fields = '__all__'

SerializerMethodField#

class PostSerializer(serializers.ModelSerializer):
    # Computed field
    status_label = serializers.SerializerMethodField()
    comment_count = serializers.SerializerMethodField()

    def get_status_label(self, obj):
        return 'Published' if obj.is_published else 'Draft'

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

    class Meta:
        model = Post
        fields = '__all__'

Common Patterns#

# Include related object details
class PostSerializer(serializers.ModelSerializer):
    author_detail = serializers.SerializerMethodField()

    def get_author_detail(self, obj):
        return {
            'id': obj.author.id,
            'username': obj.author.username,
            'email': obj.author.email
        }

    class Meta:
        model = Post
        fields = ['id', 'title', 'author', 'author_detail']

Next: Views & URLs