Django REST Framework
DRF (Django REST Framework) is a library for building powerful REST APIs on top of Django. The combination of Serializer, ViewSet, and Router lets you build APIs quickly.
Installation and Setup
pip install djangorestframework
# settings.py
INSTALLED_APPS = [..., "rest_framework"]
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}
Serializer — Serialization/Deserialization
from rest_framework import serializers
from .models import Product, Category, Review
class CategorySerializer(serializers.ModelSerializer):
product_count = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ["id", "name", "slug", "product_count"]
read_only_fields = ["id"]
def get_product_count(self, obj) -> int:
return obj.products.count()
class ProductListSerializer(serializers.ModelSerializer):
"""For list views — minimal info"""
category_name = serializers.CharField(source="category.name", read_only=True)
class Meta:
model = Product
fields = ["id", "name", "price", "stock", "category_name", "is_active"]
class ProductDetailSerializer(serializers.ModelSerializer):
"""For detail views — full info + reviews"""
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(), source="category", write_only=True
)
reviews = ReviewSerializer(many=True, read_only=True)
avg_rating = serializers.SerializerMethodField()
class Meta:
model = Product
fields = [
"id", "name", "description", "price", "stock",
"category", "category_id", "reviews", "avg_rating",
"is_active", "created_at",
]
read_only_fields = ["id", "created_at"]
def get_avg_rating(self, obj) -> float | None:
reviews = obj.reviews.all()
if not reviews:
return None
return sum(r.rating for r in reviews) / len(reviews)
class ProductCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["name", "description", "price", "stock", "category"]
def validate_price(self, value):
if value <= 0:
raise serializers.ValidationError("Price must be greater than 0")
return value
def validate(self, data):
if data.get("stock", 0) > 0 and not data.get("is_active", True):
raise serializers.ValidationError("Products with stock must be active")
return data
ViewSet — Automated CRUD
from rest_framework import viewsets, permissions, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
class ProductViewSet(viewsets.ModelViewSet):
"""
list: GET /products/
create: POST /products/
retrieve: GET /products/{id}/
update: PUT /products/{id}/
partial_update: PATCH /products/{id}/
destroy: DELETE /products/{id}/
"""
queryset = Product.objects.select_related("category", "owner").prefetch_related("reviews")
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ["category", "is_active"]
search_fields = ["name", "description"]
ordering_fields = ["price", "created_at", "stock"]
ordering = ["-created_at"]
def get_serializer_class(self):
if self.action == "list":
return ProductListSerializer
if self.action == "create":
return ProductCreateSerializer
return ProductDetailSerializer
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
# Custom action (non-standard endpoint)
@action(detail=True, methods=["post"], url_path="toggle-active")
def toggle_active(self, request, pk=None):
"""POST /products/{id}/toggle-active/"""
product = self.get_object()
product.is_active = not product.is_active
product.save()
return Response({"is_active": product.is_active})
@action(detail=False, methods=["get"], url_path="in-stock")
def in_stock(self, request):
"""GET /products/in-stock/"""
products = self.get_queryset().filter(stock__gt=0)
serializer = ProductListSerializer(products, many=True)
return Response(serializer.data)
Router — Auto URL Generation
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r"products", views.ProductViewSet, basename="product")
router.register(r"categories", views.CategoryViewSet, basename="category")
urlpatterns = router.urls
# Auto-generated URLs:
# GET /products/ → list
# POST /products/ → create
# GET /products/{id}/ → retrieve
# PUT /products/{id}/ → update
# PATCH /products/{id}/ → partial_update
# DELETE /products/{id}/ → destroy
# POST /products/{id}/toggle-active/ → toggle_active (custom)
# GET /products/in-stock/ → in_stock (custom)
Permission Classes
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsOwnerOrReadOnly(BasePermission):
"""Anyone can read, only owner can write"""
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj.owner == request.user
class IsVerifiedUser(BasePermission):
def has_permission(self, request, view):
return bool(
request.user and
request.user.is_authenticated and
request.user.is_email_verified
)
class ProductViewSet(viewsets.ModelViewSet):
def get_permissions(self):
if self.action in ["list", "retrieve"]:
return [permissions.AllowAny()]
if self.action == "destroy":
return [permissions.IsAdminUser()]
return [permissions.IsAuthenticated(), IsOwnerOrReadOnly()]
Summary
| Component | Role |
|---|---|
ModelSerializer | Auto-serialization based on model |
SerializerMethodField | Add computed fields |
ModelViewSet | Auto CRUD implementation |
@action | Add custom endpoints |
DefaultRouter | Auto URL registration |
BasePermission | Custom permission class |
DRF's ModelSerializer + ModelViewSet + Router combination builds a complete CRUD API with minimal code.