Django REST Framework Guide

Build REST APIs with DRF: serializers, class-based views, ViewSets, Routers, JWT authentication, and custom permissions.

1. Serializers

from rest_framework import serializers
from .models import Article, Tag

class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ["id", "name", "color"]

class ArticleSerializer(serializers.ModelSerializer):
    tags = TagSerializer(many=True, read_only=True)
    author_name = serializers.CharField(source="author.get_full_name", read_only=True)
    word_count  = serializers.SerializerMethodField()

    class Meta:
        model  = Article
        fields = ["id", "title", "slug", "content", "author_name", "tags", "word_count", "created_at"]
        read_only_fields = ["slug", "created_at"]

    def get_word_count(self, obj):
        return len(obj.content.split())

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

    def validate(self, data):
        if data.get("status") == "published" and not data.get("content"):
            raise serializers.ValidationError("Content is required to publish.")
        return data

2. ViewSets & Routers

from rest_framework import viewsets, permissions
from rest_framework.decorators import action
from rest_framework.response import Response

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.select_related("author").prefetch_related("tags")
    serializer_class = ArticleSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    filterset_fields = ["status", "category"]
    search_fields    = ["title", "content"]
    ordering_fields  = ["created_at", "view_count"]
    ordering         = ["-created_at"]

    def get_queryset(self):
        qs = super().get_queryset()
        if self.request.user.is_authenticated and not self.request.user.is_staff:
            qs = qs.filter(status="published") | qs.filter(author=self.request.user)
        return qs

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    @action(detail=True, methods=["post"])
    def publish(self, request, pk=None):
        article = self.get_object()
        article.status = "published"
        article.save(update_fields=["status"])
        return Response({"status": "published"})

# urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register("articles", ArticleViewSet)
urlpatterns = router.urls

3. Authentication & Permissions

from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsOwnerOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True
        return obj.author == request.user

class IsAdminOrReadOnly(BasePermission):
    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        return request.user and request.user.is_staff

# settings.py — JWT authentication
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticatedOrReadOnly",
    ],
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework.filters.SearchFilter",
        "rest_framework.filters.OrderingFilter",
    ],
}

4. JWT with SimpleJWT

from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class MyTokenSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        token["name"]  = user.get_full_name()
        token["email"] = user.email
        token["role"]  = user.role
        return token

class MyTokenView(TokenObtainPairView):
    serializer_class = MyTokenSerializer

# urls.py
from rest_framework_simplejwt.views import TokenRefreshView
urlpatterns = [
    path("auth/token/",         MyTokenView.as_view()),
    path("auth/token/refresh/", TokenRefreshView.as_view()),
]

5. Custom Pagination

from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response

class StandardPagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = "size"
    max_page_size = 100

    def get_paginated_response(self, data):
        return Response({
            "count": self.page.paginator.count,
            "next": self.get_next_link(),
            "previous": self.get_previous_link(),
            "pages": self.page.paginator.num_pages,
            "page": self.page.number,
            "size": self.get_page_size(self.request),
            "results": data,
        })

6. ViewSet Actions Reference

ActionHTTP MethodURL Pattern
listGET/articles/
createPOST/articles/
retrieveGET/articles/{id}/
updatePUT/articles/{id}/
partial_updatePATCH/articles/{id}/
destroyDELETE/articles/{id}/
@action detail=TrueCustom/articles/{id}/publish/
@action detail=FalseCustom/articles/featured/