Skip to content

Examples

Runnable versions of these examples live in examples/.

A complete blog API

# models.py
from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()


class Tag(models.Model):
    label = models.CharField(max_length=100)


class Article(models.Model):
    title = models.CharField(max_length=255)
    body = models.TextField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="articles")
    tags = models.ManyToManyField(Tag, related_name="articles")
    created_at = models.DateTimeField(auto_now_add=True)
# serializers.py
from rest_framework import serializers
from drf_partial_response_fields import PartialFieldsModelSerializer, requires_related


class AuthorSerializer(PartialFieldsModelSerializer):
    class Meta:
        model = Author
        fields = ["id", "name", "email"]


class TagSerializer(PartialFieldsModelSerializer):
    class Meta:
        model = Tag
        fields = ["id", "label"]


class ArticleSerializer(PartialFieldsModelSerializer):
    author = AuthorSerializer()
    tags = TagSerializer(many=True)
    comment_count = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = ["id", "title", "body", "author", "tags", "comment_count", "created_at"]

    def get_comment_count(self, obj):
        return obj.comments.count()
# views.py
from rest_framework import viewsets
from drf_partial_response_fields import PartialResponseMixin


class ArticleViewSet(PartialResponseMixin, viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

Requests and responses

GET /articles/?fields=title,author(name)
[{"id": 1, "title": "...", "author": {"id": 3, "name": "Ada Lovelace"}}]
GET /articles/?fields=title,tags(label),comment_count
[{"id": 1, "title": "...", "tags": [{"id": 1, "label": "django"}], "comment_count": 4}]

A dashboard endpoint requesting only aggregate fields

class ArticleStatsSerializer(PartialFieldsModelSerializer):
    tag_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Article
        fields = ["id", "title", "tag_count"]


class ArticleStatsViewSet(PartialResponseMixin, viewsets.ReadOnlyModelViewSet):
    serializer_class = ArticleStatsSerializer

    def get_queryset(self):
        return Article.objects.annotate(tag_count=Count("tags"))
GET /article-stats/?fields=title,tag_count

The optimizer recognizes tag_count as a queryset annotation (not a model relation) and includes it in only() without attempting to resolve it as a foreign key — see Architecture.

A SerializerMethodField with optimizer hints

class ArticleSerializer(PartialFieldsModelSerializer):
    editor_note = serializers.SerializerMethodField()

    @requires_related(select_related=["editor__profile"])
    def get_editor_note(self, obj):
        return f"Reviewed by {obj.editor.profile.display_name}"
GET /articles/?fields=editor_note

Only when editor_note is requested does the queryset gain select_related("editor__profile") — a request that omits it never pays for the extra join.

Renaming fields for a public API

GET /articles/?fields=publishedAt:created_at,writer:author(name)
{"publishedAt": "2026-01-01T00:00:00Z", "writer": {"name": "Ada Lovelace"}}

Useful when your internal field names (created_at, author) don't match the naming convention a public API contract requires (publishedAt, writer), without maintaining two parallel serializers.