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:
AllowAnyIsAuthenticatedIsAdminUserIsAuthenticatedOrReadOnlyDjangoModelPermissions
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=Trueon lists. read_only_fieldsincludes 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 .