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
| Action | HTTP Method | URL Pattern |
|---|---|---|
| list | GET | /articles/ |
| create | POST | /articles/ |
| retrieve | GET | /articles/{id}/ |
| update | PUT | /articles/{id}/ |
| partial_update | PATCH | /articles/{id}/ |
| destroy | DELETE | /articles/{id}/ |
| @action detail=True | Custom | /articles/{id}/publish/ |
| @action detail=False | Custom | /articles/featured/ |