Skip to main content

Django REST Serializers

Introduction

Serializers are a core component of Django REST Framework (DRF) that allow complex data such as Django model instances and querysets to be converted to native Python data types. These Python data types can then be easily rendered into JSON, XML, or other content types for API responses. Serializers also handle deserialization, allowing parsed data to be converted back into complex types after validating the incoming data.

Think of serializers as translators between your Django models (Python objects) and the data formats that can be transmitted over the web (like JSON). They're similar to Django's Form and ModelForm classes but specifically designed for handling API data instead of HTML forms.

Basic Serializer Concepts

What Serializers Do

Serializers in DRF perform several important functions:

  1. Serialization: Convert Django models to Python native datatypes
  2. Deserialization: Convert Python native datatypes to Django models
  3. Validation: Ensure that data meets specific requirements before saving
  4. Create/Update operations: Create new instances or update existing ones

Types of Serializers

DRF provides several types of serializers:

  1. Serializer: The base serializer class
  2. ModelSerializer: A shortcut for creating serializers from models
  3. HyperlinkedModelSerializer: Similar to ModelSerializer but uses hyperlinks for relationships
  4. ListSerializer: For serializing lists of objects

Your First Serializer

Let's start with a basic example. Suppose we have a Book model:

python
from django.db import models

class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
publication_date = models.DateField()
isbn = models.CharField(max_length=13)
price = models.DecimalField(max_digits=6, decimal_places=2)

def __str__(self):
return self.title

Creating a Serializer

Here's a simple serializer for our Book model:

python
from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=100)
author = serializers.CharField(max_length=100)
publication_date = serializers.DateField()
isbn = serializers.CharField(max_length=13)
price = serializers.DecimalField(max_digits=6, decimal_places=2)

def create(self, validated_data):
"""Create and return a new Book instance"""
return Book.objects.create(**validated_data)

def update(self, instance, validated_data):
"""Update and return an existing Book instance"""
instance.title = validated_data.get('title', instance.title)
instance.author = validated_data.get('author', instance.author)
instance.publication_date = validated_data.get('publication_date', instance.publication_date)
instance.isbn = validated_data.get('isbn', instance.isbn)
instance.price = validated_data.get('price', instance.price)
instance.save()
return instance

Using the Serializer

To serialize a single Book instance:

python
from rest_framework.renderers import JSONRenderer
from .models import Book
from .serializers import BookSerializer

# Get a book instance
book = Book.objects.get(id=1)

# Serialize the book
serializer = BookSerializer(book)
serializer.data
# Output: {'id': 1, 'title': 'Django for Beginners', 'author': 'William Vincent', 'publication_date': '2020-01-01', 'isbn': '9781234567890', 'price': '29.99'}

# Convert to JSON
json_data = JSONRenderer().render(serializer.data)
# Output: b'{"id":1,"title":"Django for Beginners","author":"William Vincent","publication_date":"2020-01-01","isbn":"9781234567890","price":"29.99"}'

To deserialize data:

python
from rest_framework.parsers import JSONParser
import io

# Parse JSON data
stream = io.BytesIO(json_data)
data = JSONParser().parse(stream)

# Deserialize data
serializer = BookSerializer(data=data)
serializer.is_valid()
# Output: True
serializer.validated_data
# Output: OrderedDict([('title', 'Django for Beginners'), ('author', 'William Vincent'), ('publication_date', datetime.date(2020, 1, 1)), ('isbn', '9781234567890'), ('price', Decimal('29.99'))])

ModelSerializer

Writing out all fields for every model can be repetitive. DRF provides ModelSerializer to simplify this process:

python
from rest_framework import serializers
from .models import Book

class BookModelSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'author', 'publication_date', 'isbn', 'price']
# Alternatively, use fields = '__all__' to include all fields
# Or use exclude = ['field_to_exclude'] to exclude specific fields

ModelSerializer will:

  • Automatically generate fields based on the model
  • Create default implementations of create() and update() methods
  • Generate validators for the serializer based on model field attributes

Nested Serializers

Let's say we have an Author model related to our Book model:

python
class Author(models.Model):
name = models.CharField(max_length=100)
biography = models.TextField()
date_of_birth = models.DateField()

def __str__(self):
return self.name

class Book(models.Model):
# ... other fields
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')

We can nest serializers to represent related objects:

python
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name', 'biography', 'date_of_birth']

class BookSerializer(serializers.ModelSerializer):
# Nested serializer for author
author = AuthorSerializer()

class Meta:
model = Book
fields = ['id', 'title', 'author', 'publication_date', 'isbn', 'price']

def create(self, validated_data):
# Extract author data
author_data = validated_data.pop('author')
# Get or create the author
author, _ = Author.objects.get_or_create(**author_data)
# Create the book with the author
book = Book.objects.create(author=author, **validated_data)
return book

Validation in Serializers

Serializers provide several ways to validate data:

Field-level Validation

python
def validate_isbn(self, value):
"""
Check that the ISBN is a valid format.
"""
if not value.isdigit() or len(value) != 13:
raise serializers.ValidationError("ISBN must be 13 digits")
return value

Object-level Validation

python
def validate(self, data):
"""
Check that the publication date is not in the future.
"""
if data['publication_date'] > timezone.now().date():
raise serializers.ValidationError("Publication date cannot be in the future")
return data

Custom Validators

python
def positive_price(value):
if value <= 0:
raise serializers.ValidationError("Price must be positive")
return value

class BookSerializer(serializers.ModelSerializer):
price = serializers.DecimalField(max_digits=6, decimal_places=2, validators=[positive_price])
# ... rest of the serializer

SerializerMethodField

Sometimes you need to include fields in your serialization that don't directly correspond to model attributes:

python
class BookSerializer(serializers.ModelSerializer):
is_recent = serializers.SerializerMethodField()

class Meta:
model = Book
fields = ['id', 'title', 'author', 'publication_date', 'isbn', 'price', 'is_recent']

def get_is_recent(self, obj):
"""Return True if book was published in the last year"""
one_year_ago = timezone.now().date() - timedelta(days=365)
return obj.publication_date >= one_year_ago

Real-world Example: Building a Book API

Let's tie everything together with a complete example of a Book API:

Models

python
from django.db import models

class Author(models.Model):
name = models.CharField(max_length=100)
biography = models.TextField()
date_of_birth = models.DateField()

def __str__(self):
return self.name

class Category(models.Model):
name = models.CharField(max_length=100)

def __str__(self):
return self.name

class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
publication_date = models.DateField()
isbn = models.CharField(max_length=13)
price = models.DecimalField(max_digits=6, decimal_places=2)
categories = models.ManyToManyField(Category, related_name='books')

def __str__(self):
return self.title

Serializers

python
from rest_framework import serializers
from .models import Author, Category, Book

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

class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name', 'biography', 'date_of_birth']

class BookListSerializer(serializers.ModelSerializer):
author_name = serializers.ReadOnlyField(source='author.name')
categories = CategorySerializer(many=True, read_only=True)

class Meta:
model = Book
fields = ['id', 'title', 'author_name', 'publication_date', 'price', 'categories']

class BookDetailSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
categories = CategorySerializer(many=True, read_only=True)

class Meta:
model = Book
fields = ['id', 'title', 'author', 'publication_date', 'isbn', 'price', 'categories']

Views

python
from rest_framework import viewsets
from .models import Book
from .serializers import BookListSerializer, BookDetailSerializer

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()

def get_serializer_class(self):
if self.action == 'list':
return BookListSerializer
return BookDetailSerializer

URLs

python
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import BookViewSet

router = DefaultRouter()
router.register(r'books', BookViewSet)

urlpatterns = [
path('api/', include(router.urls)),
]

With this setup, your API will:

  • Return a list of books with basic information at /api/books/
  • Return detailed book information at /api/books/{id}/

Summary

Django REST Framework serializers provide a powerful mechanism for converting complex Django data types to and from Python native data types that can be easily rendered into JSON or other content types. They handle:

  • Serializing Django models to JSON
  • Deserializing JSON to Django models
  • Validating incoming data
  • Creating and updating model instances

The main types of serializers are:

  • Serializer: The base class requiring explicit field definitions
  • ModelSerializer: A shortcut for model-based serializers
  • HyperlinkedModelSerializer: Similar to ModelSerializer but using hyperlinks for relationships

By mastering serializers, you can build robust and flexible APIs that properly handle data conversion and validation.

Additional Resources

Exercises

  1. Create a serializer for a User model that excludes sensitive information like passwords
  2. Implement a nested serializer for a blog post model that includes comments
  3. Add custom validation to ensure book prices are always positive
  4. Create a serializer that uses SerializerMethodField to calculate the average rating of a book from related Review objects
  5. Implement a serializer for handling file uploads (hint: look into FileField in serializers)


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)