DRF basics cheatsheet.

Install

uv add djangorestframework
# settings.py
INSTALLED_APPS = [..., "rest_framework"]

Serializer

from rest_framework import serializers

class PostSerializer(serializers.ModelSerializer):
    author_name = serializers.CharField(source="author.username", read_only=True)
    
    class Meta:
        model = Post
        fields = ["id", "title", "body", "author", "author_name", "created_at"]
        read_only_fields = ["id", "created_at"]

Plain Serializer

class CommentSerializer(serializers.Serializer):
    body = serializers.CharField(max_length=1000)
    rating = serializers.IntegerField(min_value=1, max_value=5)
    
    def create(self, data):
        return Comment.objects.create(**data)
    
    def update(self, instance, data):
        for k, v in data.items():
            setattr(instance, k, v)
        instance.save()
        return instance

Function-based view

from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(["GET", "POST"])
def post_list(request):
    if request.method == "GET":
        posts = Post.objects.all()
        return Response(PostSerializer(posts, many=True).data)
    
    serializer = PostSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save(author=request.user)
    return Response(serializer.data, status=201)

APIView

from rest_framework.views import APIView

class PostList(APIView):
    def get(self, request):
        return Response(PostSerializer(Post.objects.all(), many=True).data)
    
    def post(self, request):
        s = PostSerializer(data=request.data)
        s.is_valid(raise_exception=True)
        s.save(author=request.user)
        return Response(s.data, status=201)

Generic views

from rest_framework import generics

class PostList(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

ViewSet

from rest_framework import viewsets

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)
    
    def get_queryset(self):
        qs = super().get_queryset()
        return qs.filter(published=True)

Router

# urls.py
from rest_framework.routers import DefaultRouter
from .views import PostViewSet

router = DefaultRouter()
router.register(r"posts", PostViewSet, basename="post")

urlpatterns = [
    path("api/", include(router.urls)),
]

Generates: /api/posts/, /api/posts/:id/.

Custom actions

from rest_framework.decorators import action

class PostViewSet(viewsets.ModelViewSet):
    ...
    
    @action(detail=True, methods=["post"])
    def publish(self, request, pk=None):
        post = self.get_object()
        post.published = True
        post.save()
        return Response({"status": "published"})
    
    @action(detail=False, methods=["get"])
    def trending(self, request):
        posts = self.queryset.filter(views__gt=1000)
        return Response(self.get_serializer(posts, many=True).data)

URLs: /api/posts/:id/publish/, /api/posts/trending/.

Permissions

from rest_framework import permissions

class PostViewSet(viewsets.ModelViewSet):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

Built-in:

  • AllowAny
  • IsAuthenticated
  • IsAdminUser
  • IsAuthenticatedOrReadOnly
  • DjangoModelPermissions

Custom permission

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

Pagination

# settings.py
REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
}
# Per-view override
from rest_framework.pagination import CursorPagination

class PostPagination(CursorPagination):
    page_size = 50
    ordering = "-created_at"

class PostViewSet(viewsets.ModelViewSet):
    pagination_class = PostPagination

Filtering / search / ordering

uv add django-filter
REST_FRAMEWORK = {
    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework.filters.SearchFilter",
        "rest_framework.filters.OrderingFilter",
    ],
}
class PostViewSet(viewsets.ModelViewSet):
    filterset_fields = ["author", "published"]
    search_fields = ["title", "body"]
    ordering_fields = ["created_at", "views"]

/api/posts/?author=1&search=foo&ordering=-views

Authentication

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],
}
INSTALLED_APPS = [..., "rest_framework.authtoken"]
uv run python manage.py migrate
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=user)
# Send as: Authorization: Token <token>

Nested serializers

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ["id", "body"]

class PostSerializer(serializers.ModelSerializer):
    comments = CommentSerializer(many=True, read_only=True)
    
    class Meta:
        model = Post
        fields = ["id", "title", "comments"]

Validation

class PostSerializer(serializers.ModelSerializer):
    def validate_title(self, value):
        if "spam" in value.lower():
            raise serializers.ValidationError("No spam")
        return value
    
    def validate(self, data):
        if data["start"] > data["end"]:
            raise serializers.ValidationError("start must be before end")
        return data

Common mistakes

  • Forgetting many=True on lists.
  • read_only_fields includes editable fields → silently dropped on input.
  • Not using serializer.save(author=request.user) → author missing.
  • Bypassing serializer to skip validation.
  • Nesting serializers too deeply — slow and noisy. Flatten or paginate.

Read this next

If you want my DRF starter (JWT + filters + OpenAPI), it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .