DRF Setup & Serializers

Difficulty: Advanced

Django REST Framework (DRF) is the most popular third-party package for building RESTful APIs with Django. It provides serializers, views, viewsets, routers, authentication, permissions, pagination, filtering, and throttling - everything you need for a production-quality API.

Install DRF with 'pip install djangorestframework' and add 'rest_framework' to INSTALLED_APPS. DRF configuration lives in settings.py under the REST_FRAMEWORK dictionary, where you can set default authentication, permissions, pagination, and other global settings.

Serializers are the core of DRF. They convert complex data types (like Django model instances and QuerySets) to Python native types that can be rendered into JSON, XML, or other content types. They also handle deserialization - converting parsed data back into complex types after validating the incoming data.

Serializer is the base class. You define fields explicitly, similar to Django forms. Each field type (CharField, IntegerField, DateTimeField, etc.) handles validation and conversion. ModelSerializer is a shortcut that automatically generates serializer fields from a model, similar to how ModelForm works with Django forms.

ModelSerializer introspects the model and creates fields automatically. You specify the model and fields in a Meta class. It generates validators from model constraints and provides default create() and update() methods. You can override fields, add extra fields, or customize validation just like with regular Serializers.

Serializer validation follows a similar pattern to Django forms: field-level validation with validate_fieldname() methods, and object-level validation with the validate() method. The validate() method receives the full dictionary of validated data and can check cross-field relationships.

Nested serializers allow you to represent related objects inline. A field defined as another serializer class will include the related object's data in the output. For write operations with nested data, you need to override the create() and update() methods to handle the nested creation/update logic.

Code examples

Setting Up DRF

# Install: pip install djangorestframework

# settings.py
INSTALLED_APPS = [
    # ...
    'rest_framework',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}

Add 'rest_framework' to INSTALLED_APPS and configure defaults in REST_FRAMEWORK. BrowsableAPIRenderer provides a web-based interface for testing your API. Default settings apply to all API views unless overridden.

ModelSerializer

from rest_framework import serializers
from .models import Article, Category

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name', 'slug']

class ArticleSerializer(serializers.ModelSerializer):
    # Read-only computed field
    author_name = serializers.CharField(source='author.get_full_name', read_only=True)
    # Nested serializer (read-only by default)
    category = CategorySerializer(read_only=True)
    # Write-only field for setting category by ID
    category_id = serializers.PrimaryKeyRelatedField(
        queryset=Category.objects.all(),
        source='category',
        write_only=True,
    )

    class Meta:
        model = Article
        fields = [
            'id', 'title', 'slug', 'content', 'excerpt',
            'author', 'author_name', 'category', 'category_id',
            'is_published', 'created_at', 'updated_at',
        ]
        read_only_fields = ['author', 'created_at', 'updated_at']

ModelSerializer auto-generates fields from the model. 'source' maps a serializer field to a model attribute. read_only_fields prevent those fields from being set via the API. Nested serializer shows full category data in responses.

Custom Validation

from rest_framework import serializers
from .models import Article

class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'status']

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

    def validate(self, data):
        """Object-level validation."""
        if data.get('status') == 'published' and not data.get('content'):
            raise serializers.ValidationError(
                'Published articles must have content.'
            )
        return data

    def create(self, validated_data):
        """Custom create with request user."""
        validated_data['author'] = self.context['request'].user
        return super().create(validated_data)

    def update(self, instance, validated_data):
        """Custom update logic."""
        instance.title = validated_data.get('title', instance.title)
        instance.content = validated_data.get('content', instance.content)
        instance.save()
        return instance

validate_fieldname() validates individual fields. validate() validates across fields. create() and update() handle object creation/modification. self.context['request'] gives access to the HTTP request.

Serializer Usage

from .models import Article
from .serializers import ArticleSerializer

# Serialization (object -> JSON data)
article = Article.objects.get(pk=1)
serializer = ArticleSerializer(article)
serializer.data
# {'id': 1, 'title': 'Hello', 'content': '...', ...}

# Serialization of multiple objects
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
serializer.data
# [{'id': 1, ...}, {'id': 2, ...}, ...]

# Deserialization (JSON data -> object)
data = {'title': 'New Article', 'content': 'Body text'}
serializer = ArticleSerializer(data=data)
serializer.is_valid(raise_exception=True)
article = serializer.save()

# Update existing object
serializer = ArticleSerializer(article, data=data, partial=True)
serializer.is_valid(raise_exception=True)
updated = serializer.save()

Pass an instance for serialization (reading). Pass data= for deserialization (writing). many=True handles lists. partial=True allows partial updates (PATCH). is_valid() runs validation and raise_exception=True auto-returns 400 errors.

Key points

Concepts covered

DRF, Serializer, ModelSerializer, Validation, Nested Serializers