| name | django-rest-framework |
| user-invocable | false |
| description | Use when Django REST Framework for building APIs with serializers, viewsets, and authentication. Use when creating RESTful APIs. |
| allowed-tools | ["Bash","Read"] |
Django REST Framework
Master Django REST Framework for building robust, scalable RESTful
APIs with proper serialization and authentication.
Serializers
Build type-safe data serialization with Django REST Framework serializers.
from rest_framework import serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
post_count = serializers.IntegerField(read_only=True)
full_name = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id', 'email', 'name', 'post_count', 'full_name']
read_only_fields = ['id', 'created_at']
extra_kwargs = {
'email': {'required': True},
'password': {'write_only': True}
}
def get_full_name(self, obj):
return f"{obj.first_name} {obj.last_name}"
class PostSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
author_id = serializers.IntegerField(write_only=True)
class Meta:
model = Post
fields = '__all__'
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('published') and not data.get('content'):
raise serializers.ValidationError('Published posts must have content')
return data
def create(self, validated_data):
post = Post.objects.create(**validated_data)
return post
Custom Fields and Validation
Create custom serializer fields for complex data types.
from rest_framework import serializers
class Base64ImageField(serializers.ImageField):
"""Handle base64 encoded images."""
def to_internal_value(self, data):
import base64
from django.core.files.base import ContentFile
if isinstance(data, str) and data.startswith('data:image'):
format, imgstr = data.split(';base64,')
ext = format.split('/')[-1]
data = ContentFile(base64.b64decode(imgstr), name=f'temp.{ext}')
return super().to_internal_value(data)
class PostSerializer(serializers.ModelSerializer):
image = Base64ImageField(required=False)
class Meta:
model = Post
fields = ['id', 'title', 'image']
def validate_no_profanity(value):
profanity_words = ['bad', 'worse']
if any(word in value.lower() for word in profanity_words):
raise serializers.ValidationError('Content contains profanity')
value
(serializers.ModelSerializer):
content = serializers.CharField(validators=[validate_no_profanity])
:
model = Comment
fields = [, , ]
Nested Serializers
Handle complex nested relationships.
class CommentSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
class Meta:
model = Comment
fields = ['id', 'content', 'author', 'created_at']
class PostSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ['id', 'title', 'content', 'author', 'comments']
class PostCreateSerializer(serializers.ModelSerializer):
comments = CommentSerializer(many=True, required=False)
class Meta:
model = Post
fields = ['id', 'title', 'content', 'comments']
def create(self, validated_data):
comments_data = validated_data.pop('comments', [])
post = Post.objects.create(**validated_data)
for comment_data in comments_data:
Comment.objects.create(post=post, **comment_data)
return post
class PostSerializer(serializers.ModelSerializer):
class :
model = Post
fields = [, , ]
():
include_comments = kwargs.pop(, )
().__init__(*args, **kwargs)
include_comments:
.fields[] = CommentSerializer(many=, read_only=)
ViewSets
Create RESTful endpoints with ViewSets.
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filterset_fields = ['author', 'published']
search_fields = ['title', 'content']
ordering_fields = ['created_at', 'title']
def get_queryset(self):
queryset = super().get_queryset()
if self.action == 'list':
queryset = queryset.filter(published=True)
return queryset.select_related('author').prefetch_related('comments')
def get_serializer_class(self):
if self.action == 'create':
return PostCreateSerializer
return PostSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=['post'])
def publish():
post = .get_object()
post.published =
post.save()
Response({: })
():
recent_posts = .get_queryset()[:]
serializer = .get_serializer(recent_posts, many=)
Response(serializer.data)
(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.()
serializer_class = CategorySerializer
Routers
Configure URL routing for ViewSets.
from rest_framework.routers import DefaultRouter, SimpleRouter
from django.urls import path, include
router = DefaultRouter()
router.register(r'posts', PostViewSet, basename='post')
router.register(r'users', UserViewSet, basename='user')
router.register(r'comments', CommentViewSet, basename='comment')
urlpatterns = [
path('api/', include(router.urls)),
]
simple_router = SimpleRouter()
simple_router.register(r'posts', PostViewSet)
from rest_framework.routers import Route, DynamicRoute
class CustomRouter(DefaultRouter):
routes = [
Route(
url=r'^{prefix}/$',
mapping={'get': 'list', 'post': 'create'},
name='{basename}-list',
detail=False,
initkwargs={}
),
]
Permissions
Implement authentication and authorization.
from rest_framework import permissions
class IsAuthorOrReadOnly(permissions.BasePermission):
"""Custom permission to only allow authors to edit."""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
class IsOwnerOrAdmin(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.user or request.user.is_staff
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated, IsAuthorOrReadOnly]
from rest_framework.permissions import IsAuthenticated, IsAdminUser
class AdminPostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_permissions(self):
if self.action in ['create', 'update', 'partial_update', 'destroy']:
[IsAdminUser()]
[IsAuthenticated()]
Authentication
Configure various authentication methods.
from rest_framework.authentication import TokenAuthentication, SessionAuthentication
from rest_framework.authtoken.models import Token
from rest_framework.permissions import IsAuthenticated
class PostViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
queryset = Post.objects.all()
serializer_class = PostSerializer
from rest_framework.authtoken.views import obtain_auth_token
from django.urls import path
urlpatterns = [
path('api-token-auth/', obtain_auth_token),
]
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'email': user.email
})
rest_framework_simplejwt.authentication JWTAuthentication
(viewsets.ModelViewSet):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
Filtering and Search
Implement advanced filtering capabilities.
from django_filters import rest_framework as filters
from rest_framework import filters as drf_filters
class PostFilter(filters.FilterSet):
title = filters.CharFilter(lookup_expr='icontains')
created_after = filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')
created_before = filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')
class Meta:
model = Post
fields = ['author', 'published', 'title']
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
filter_backends = [
filters.DjangoFilterBackend,
drf_filters.SearchFilter,
drf_filters.OrderingFilter
]
filterset_class = PostFilter
search_fields = ['title', 'content', 'author__name']
ordering_fields = ['created_at', 'title', 'views']
ordering = ['-created_at']
class IsOwnerFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(owner=request.user)
Pagination
Configure pagination for large datasets.
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
class StandardResultsSetPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
pagination_class = StandardResultsSetPagination
class PostCursorPagination(CursorPagination):
page_size = 20
ordering = '-created_at'
class CustomPagination(PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'total_pages': self.page.paginator.num_pages,
'results': data
})
Throttling
Rate limit API requests.
from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
class BurstRateThrottle(UserRateThrottle):
rate = '60/min'
class SustainedRateThrottle(UserRateThrottle):
rate = '1000/day'
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
throttle_classes = [BurstRateThrottle, SustainedRateThrottle]
from rest_framework.throttling import SimpleRateThrottle
class UploadRateThrottle(SimpleRateThrottle):
rate = '10/hour'
def get_cache_key(self, request, view):
if request.user.is_authenticated:
ident = request.user.pk
else:
ident = self.get_ident(request)
return self.cache_format % {'scope': self.scope, 'ident': ident}
Versioning
Handle API versioning.
from rest_framework.versioning import URLPathVersioning, NamespaceVersioning
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
versioning_class = URLPathVersioning
def get_serializer_class(self):
if self.request.version == 'v1':
return PostSerializerV1
return PostSerializerV2
urlpatterns = [
path('v1/posts/', PostViewSet.as_view({'get': 'list'})),
path('v2/posts/', PostViewSet.as_view({'get': 'list'})),
]
from rest_framework.versioning import AcceptHeaderVersioning
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
'DEFAULT_VERSION': 'v1',
'ALLOWED_VERSIONS': ['v1', 'v2'],
}
Error Handling
Implement custom error responses.
from rest_framework.views import exception_handler
from rest_framework.response import Response
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is not None:
response.data = {
'error': {
'status_code': response.status_code,
'message': response.data,
'detail': str(exc)
}
}
return response
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'myapp.utils.custom_exception_handler'
}
from rest_framework.exceptions import APIException
class ServiceUnavailable(APIException):
status_code = 503
default_detail = 'Service temporarily unavailable'
default_code = 'service_unavailable'
from rest_framework import status
from rest_framework.response import Response
class PostViewSet(viewsets.ModelViewSet):
def create(self, request):
try:
pass
except Exception as e:
ServiceUnavailable(detail=(e))
Advanced Serializer Patterns
Master complex serialization scenarios.
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""Serializer that accepts 'fields' parameter to dynamically include/exclude fields."""
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields', None)
exclude = kwargs.pop('exclude', None)
super().__init__(*args, **kwargs)
if fields is not None:
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
if exclude is not None:
for field_name in exclude:
self.fields.pop(field_name, None)
class PostSerializer(DynamicFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
serializer = PostSerializer(post, fields=('id', 'title', 'author'))
serializer = PostSerializer(post, exclude=('content',))
(serializers.ModelSerializer):
is_liked = serializers.SerializerMethodField()
like_count = serializers.SerializerMethodField()
:
model = Post
fields = [, , , ]
():
request = .context.get()
request request.user.is_authenticated:
obj.likes.(user=request.user).exists()
():
obj.likes.count()
(serializers.ModelSerializer):
author_name = serializers.CharField(source=, read_only=)
:
model = Comment
fields = [, , , ]
(serializers.ModelSerializer):
comments = CommentSerializer(many=, required=)
:
model = Post
fields = [, , , ]
():
comments_data = validated_data.pop(, [])
post = Post.objects.create(**validated_data)
comment_data comments_data:
Comment.objects.create(post=post, **comment_data)
post
():
comments_data = validated_data.pop(, )
instance.title = validated_data.get(, instance.title)
instance.content = validated_data.get(, instance.content)
instance.save()
comments_data :
instance.comments.().delete()
comment_data comments_data:
Comment.objects.create(post=instance, **comment_data)
instance
(serializers.Serializer):
():
(instance, Article):
ArticleSerializer(instance, context=.context).data
(instance, Video):
VideoSerializer(instance, context=.context).data
(instance, Image):
ImageSerializer(instance, context=.context).data
().to_representation(instance)
ViewSet Composition and Actions
Build sophisticated ViewSets with custom actions.
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Count, Q
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_queryset(self):
queryset = super().get_queryset()
author = self.request.query_params.get('author')
if author:
queryset = queryset.filter(author_id=author)
published = self.request.query_params.get('published')
if published is not None:
queryset = queryset.filter(published=published == 'true')
if self.action == 'list':
queryset = queryset.select_related('author').only(
'id', 'title', 'created_at', 'author__name'
)
elif self.action == 'retrieve':
queryset = queryset.select_related('author').prefetch_related(
'comments__author', 'tags'
)
queryset
():
.action == :
PostListSerializer
.action [, , ]:
PostWriteSerializer
PostSerializer
():
post = .get_object()
post.published =
post.published_at = timezone.now()
post.save()
serializer = .get_serializer(post)
Response(serializer.data)
():
post = .get_object()
user = request.user
like, created = Like.objects.get_or_create(post=post, user=user)
created:
like.delete()
Response({: })
Response({: }, status=status.HTTP_201_CREATED)
():
posts = .get_queryset().annotate(
like_count=Count()
).(
created_at__gte=timezone.now() - timedelta(days=)
).order_by()[:]
serializer = .get_serializer(posts, many=)
Response(serializer.data)
():
queryset = .get_queryset()
stats = {
: queryset.count(),
: queryset.(published=).count(),
: queryset.(published=).count(),
: Like.objects.(post__in=queryset).count()
}
Response(stats)
():
post = .get_object()
comments = post.comments.select_related().()
page = .paginate_queryset(comments)
page :
serializer = CommentSerializer(page, many=)
.get_paginated_response(serializer.data)
serializer = CommentSerializer(comments, many=)
Response(serializer.data)
():
serializer.save(author=.request.user)
():
instance.deleted_at = timezone.now()
instance.save()
Advanced Permission Patterns
Implement granular permission control.
from rest_framework import permissions
class IsAuthorOrReadOnly(permissions.BasePermission):
"""Object-level permission to only allow authors to edit."""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
class IsPublishedOrAuthor(permissions.BasePermission):
"""Only show published posts unless user is the author."""
def has_object_permission(self, request, view, obj):
if obj.published:
return True
return obj.author == request.user
class HasAPIKey(permissions.BasePermission):
"""Check for valid API key in header."""
def has_permission(self, request, view):
api_key = request.META.get('HTTP_X_API_KEY')
if not api_key:
return False
return APIKey.objects.filter(
key=api_key,
is_active=True
).exists()
class RateLimitPermission(permissions.BasePermission):
"""Custom rate limiting based on user tier."""
def has_permission():
user = request.user
user.is_authenticated:
user.tier == :
rate =
:
rate =
cache_key =
current_count = cache.get(cache_key, )
current_count >= rate:
cache.(cache_key, current_count + , timeout=)
(viewsets.ModelViewSet):
queryset = Post.objects.()
serializer_class = PostSerializer
():
.action [, , , ]:
permission_classes = [permissions.IsAuthenticated, IsAuthorOrReadOnly]
.action == :
permission_classes = [permissions.AllowAny]
:
permission_classes = [IsPublishedOrAuthor]
[permission() permission permission_classes]
Advanced Filtering and Search
Implement sophisticated filtering capabilities.
from django_filters import rest_framework as filters
from rest_framework import filters as drf_filters
class PostFilter(filters.FilterSet):
title = filters.CharFilter(lookup_expr='icontains')
title_exact = filters.CharFilter(field_name='title', lookup_expr='exact')
created_after = filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')
created_before = filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')
min_views = filters.NumberFilter(field_name='views', lookup_expr='gte')
max_views = filters.NumberFilter(field_name='views', lookup_expr='lte')
status = filters.ChoiceFilter(choices=(
('published', 'Published'),
('draft', 'Draft'),
('archived', 'Archived')
))
tags = filters.ModelMultipleChoiceFilter(
queryset=Tag.objects.all(),
field_name='tags',
conjoined=False
)
has_comments = filters.BooleanFilter(method='filter_has_comments')
class Meta:
model = Post
fields = ['author', 'published', 'category']
():
value:
queryset.(comments__isnull=).distinct()
queryset.(comments__isnull=)
(viewsets.ModelViewSet):
queryset = Post.objects.()
serializer_class = PostSerializer
filter_backends = [
filters.DjangoFilterBackend,
drf_filters.SearchFilter,
drf_filters.OrderingFilter
]
filterset_class = PostFilter
search_fields = [
,
,
,
,
,
]
ordering_fields = [, , , ]
ordering = []
(filters.BaseFilterBackend):
():
request.user.is_authenticated:
queryset.none()
queryset.(author=request.user)
(viewsets.ReadOnlyModelViewSet):
queryset = Post.objects.()
serializer_class = PostSerializer
filter_backends = [IsOwnerFilterBackend]
Pagination Strategies
Implement various pagination approaches.
from rest_framework.pagination import (
PageNumberPagination,
LimitOffsetPagination,
CursorPagination
)
class StandardPagination(PageNumberPagination):
page_size = 20
page_size_query_param = 'page_size'
max_page_size = 100
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'total_pages': self.page.paginator.num_pages,
'current_page': self.page.number,
'results': data
})
class LargeResultsPagination(PageNumberPagination):
page_size = 1000
max_page_size = 10000
class SmallResultsPagination(PageNumberPagination):
page_size = 10
class PostCursorPagination(CursorPagination):
page_size = 20
ordering = '-created_at'
cursor_query_param = 'cursor'
def get_paginated_response(self, data):
Response({
: .get_next_link(),
: .get_previous_link(),
: data
})
(viewsets.ModelViewSet):
queryset = Post.objects.()
serializer_class = PostSerializer
():
.action == :
StandardPagination
.action == :
SmallResultsPagination
pagination_class = StandardPagination
API Versioning Strategies
Manage API versions effectively.
from rest_framework.versioning import (
URLPathVersioning,
NamespaceVersioning,
AcceptHeaderVersioning,
QueryParameterVersioning
)
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
versioning_class = URLPathVersioning
def get_serializer_class(self):
if self.request.version == 'v1':
return PostSerializerV1
elif self.request.version == 'v2':
return PostSerializerV2
return PostSerializer
urlpatterns = [
path('v1/posts/', PostViewSet.as_view({'get': 'list'}), name='post-list-v1'),
path('v2/posts/', PostViewSet.as_view({'get': 'list'}), name='post-list-v2'),
]
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
'DEFAULT_VERSION': 'v1',
'ALLOWED_VERSIONS': ['v1', 'v2', 'v3'],
'VERSION_PARAM': 'version',
}
class PostSerializerV1(serializers.ModelSerializer):
class Meta:
model = Post
fields = [, , ]
(serializers.ModelSerializer):
author = UserSerializer(read_only=)
:
model = Post
fields = [, , , , ]
(serializers.ModelSerializer):
author = UserSerializer(read_only=)
comments = CommentSerializer(many=, read_only=)
tags = TagSerializer(many=, read_only=)
:
model = Post
fields =
Testing DRF APIs
Write comprehensive tests for your API.
from rest_framework.test import APITestCase, APIClient, APIRequestFactory
from rest_framework import status
from django.contrib.auth.models import User
from django.urls import reverse
class PostAPITestCase(APITestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user('testuser', 'test@test.com', 'testpass')
self.client.force_authenticate(user=self.user)
def test_create_post(self):
data = {'title': 'Test Post', 'content': 'Test content'}
response = self.client.post('/api/posts/', data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Post.objects.count(), 1)
self.assertEqual(Post.objects.get().title, 'Test Post')
def test_list_posts(self):
Post.objects.create(title='Post 1', author=self.user)
Post.objects.create(title='Post 2', author=self.user)
response = self.client.get()
.assertEqual(response.status_code, status.HTTP_200_OK)
.assertEqual((response.data[]), )
():
post = Post.objects.create(title=, author=.user)
data = {: }
response = .client.patch(, data)
.assertEqual(response.status_code, status.HTTP_200_OK)
post.refresh_from_db()
.assertEqual(post.title, )
():
post = Post.objects.create(title=, author=.user)
response = .client.delete()
.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
.assertEqual(Post.objects.count(), )
():
.client.force_authenticate(user=)
response = .client.post(, {})
.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
():
other_user = User.objects.create_user(, password=)
post = Post.objects.create(title=, author=other_user)
response = .client.patch(, {: })
.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
():
Post.objects.create(title=, author=.user, published=)
Post.objects.create(title=, author=.user, published=)
response = .client.get()
.assertEqual((response.data[]), )
.assertEqual(response.data[][][], )
():
Post.objects.create(title=, author=.user)
Post.objects.create(title=, author=.user)
response = .client.get()
.assertEqual((response.data[]), )
():
post1 = Post.objects.create(title=, author=.user)
post2 = Post.objects.create(title=, author=.user)
response = .client.get()
.assertEqual(response.data[][][], )
response = .client.get()
.assertEqual(response.data[][][], )
():
i ():
Post.objects.create(title=, author=.user)
response = .client.get()
.assertEqual((response.data[]), )
.assertIsNotNone(response.data[])
():
post = Post.objects.create(title=, author=.user)
response = .client.post()
.assertEqual(response.status_code, status.HTTP_200_OK)
post.refresh_from_db()
.assertTrue(post.published)
():
():
.factory = APIRequestFactory()
.user = User.objects.create_user(, password=)
():
request = .factory.get()
request.user = .user
view = PostViewSet.as_view({: })
response = view(request)
.assertEqual(response.status_code, status.HTTP_200_OK)
():
data = {: , : }
request = .factory.post(, data)
request.user = .user
view = PostViewSet.as_view({: })
response = view(request)
.assertEqual(response.status_code, status.HTTP_201_CREATED)
When to Use This Skill
Use django-rest-framework when building modern, production-ready
applications that require
advanced patterns, best practices, and optimal performance.
Performance Optimization
Optimize DRF API performance for production.
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_headers
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_queryset(self):
queryset = super().get_queryset()
if self.action == 'list':
queryset = queryset.select_related('author').only(
'id', 'title', 'created_at', 'author__name'
)
elif self.action == 'retrieve':
queryset = queryset.select_related(
'author', 'category'
).prefetch_related(
'comments__author',
'tags'
)
return queryset
@method_decorator(cache_page(60 * 5))
@method_decorator(vary_on_headers('Authorization'))
def list():
().(request, *args, **kwargs)
(serializers.ModelSerializer):
author_name = serializers.CharField(source=, read_only=)
:
model = Post
fields = [, , , ]
(serializers.ModelSerializer):
author = UserSerializer(read_only=)
comments = CommentSerializer(many=, read_only=)
:
model = Post
fields =
rest_framework.response Response
rest_framework status
:
():
many = (request.data, )
many:
().create(request, *args, **kwargs)
serializer = .get_serializer(data=request.data, many=)
serializer.is_valid(raise_exception=)
.perform_create(serializer)
Response(serializer.data, status=status.HTTP_201_CREATED)
(BatchCreateMixin, viewsets.ModelViewSet):
queryset = Post.objects.()
serializer_class = PostSerializer
Documentation and Schema
Generate API documentation automatically.
from rest_framework import serializers, viewsets
from rest_framework.decorators import action
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiExample
from drf_spectacular.types import OpenApiTypes
class PostSerializer(serializers.ModelSerializer):
"""Serializer for Post objects."""
class Meta:
model = Post
fields = ['id', 'title', 'content', 'author', 'created_at']
read_only_fields = ['id', 'created_at']
class PostViewSet(viewsets.ModelViewSet):
"""
ViewSet for managing posts.
Provides CRUD operations for posts with additional
custom actions for publishing and liking.
"""
queryset = Post.objects.all()
serializer_class = PostSerializer
@extend_schema(
summary="Publish a post",
description="Set the post's published status to true",
responses={200: PostSerializer}
)
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
post = self.get_object()
post.published = True
post.save()
serializer = self.get_serializer(post)
return Response(serializer.data)
@extend_schema()
():
().(request, *args, **kwargs)
REST_FRAMEWORK = {
: ,
}
SPECTACULAR_SETTINGS = {
: ,
: ,
: ,
: ,
}
drf_spectacular.views SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
path(, SpectacularAPIView.as_view(), name=),
path(, SpectacularSwaggerView.as_view(url_name=), name=),
]
DRF Best Practices
- Use ModelSerializer - Leverage ModelSerializer to reduce boilerplate
code
- Validate at serializer level - Implement validation in serializers, not
views
- Use ViewSets for standard CRUD - ViewSets reduce code duplication for
standard operations
- Optimize with select_related - Always optimize queries in
get_queryset()
- Version your API - Plan for versioning from the start
- Use proper permissions - Implement granular permissions at object level
- Implement pagination - Always paginate list endpoints
- Add throttling - Protect your API with rate limiting
- Use filtering backends - Enable search and filtering for better UX
- Write comprehensive tests - Test all endpoints and permission scenarios
- Cache expensive operations - Use cache decorators for list views
- Separate read/write serializers - Use different serializers for
different actions
- Document your API - Use drf-spectacular or similar for auto-generated
docs
- Handle errors gracefully - Provide clear error messages for API
consumers
- Use bulk operations - Support batch creation/updates for better
performance
DRF Common Pitfalls
- Not optimizing queries - N+1 problems in serializers accessing related
objects
- Overly complex serializers - Too much logic in serializers instead of
models
- Missing validation - Not validating data at both field and object level
- Inconsistent API design - Not following REST conventions
- No pagination - Returning unbounded lists causes performance issues
- Weak authentication - Not implementing proper token expiration or
refresh
- Missing permissions - Not implementing object-level permissions
- No API versioning - Breaking changes affect existing clients
- Poor error messages - Generic errors that don't help API consumers
- Inadequate testing - Not testing permissions, edge cases, and error
scenarios
- Exposing sensitive data - Returning password hashes or internal IDs
- Not using read_only_fields - Allowing modification of computed fields
- Ignoring CORS - Not configuring CORS for frontend applications
- Missing rate limiting - APIs vulnerable to abuse without throttling
- Not handling file uploads - Improper handling of multipart/form-data
requests
Resources