Skip to main content
Advertisement

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

ComponentRole
ModelSerializerAuto-serialization based on model
SerializerMethodFieldAdd computed fields
ModelViewSetAuto CRUD implementation
@actionAdd custom endpoints
DefaultRouterAuto URL registration
BasePermissionCustom permission class

DRF's ModelSerializer + ModelViewSet + Router combination builds a complete CRUD API with minimal code.

Advertisement