- 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.
```python
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):
# Custom creation logic
post = Post.objects.create(**validated_data)
# Send notification, etc.
return post
```
## Custom Fields and Validation
Create custom serializer fields for complex data types.
```python
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']
# Custom validators
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')
return value
class CommentSerializer(serializers.ModelSerializer):
content = serializers.CharField(validators=[validate_no_profanity])
class Meta:
model = Comment
fields = ['id', 'content', 'created_at']
```
## Nested Serializers
Handle complex nested relationships.
```python
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']
# Writable nested serializers
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
# Dynamic nested serialization
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'content']
def __init__(self, *args, **kwargs):
include_comments = kwargs.pop('include_comments', False)
super().__init__(*args, **kwargs)
if include_comments:
self.fields['comments'] = CommentSerializer(many=True, read_only=True)
```
## ViewSets
Create RESTful endpoints with ViewSets.
```python
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(self, request, pk=None):
post = self.get_object()
post.published = True
post.save()
return Response({'status': 'published'})
@action(detail=False, methods=['get'])
def recent(self, request):
recent_posts = self.get_queryset()[:10]
serializer = self.get_serializer(recent_posts, many=True)
return Response(serializer.data)
# ReadOnly ViewSet
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
```
## Routers
Configure URL routing for ViewSets.
```python
from rest_framework.routers import DefaultRouter, SimpleRouter
from django.urls import path, include
# Default router (with API root view)
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 (no API root)
simple_router = SimpleRouter()
simple_router.register(r'posts', PostViewSet)
# Custom routing
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={}
),
# Add custom routes
]
```
## Permissions
Implement authentication and authorization.
```python
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):
# Read permissions for any request
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions only for author
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
# Usage in ViewSet
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated, IsAuthorOrReadOnly]
# Multiple permission classes
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']:
return [IsAdminUser()]
return [IsAuthenticated()]
```
## Authentication
Configure various authentication methods.
```python
from rest_framework.authentication import TokenAuthentication, SessionAuthentication
from rest_framework.authtoken.models import Token
from rest_framework.permissions import IsAuthenticated
# Token Authentication
class PostViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
queryset = Post.objects.all()
serializer_class = PostSerializer
# Create token for user
from rest_framework.authtoken.views import obtain_auth_token
from django.urls import path
urlpatterns = [
path('api-token-auth/', obtain_auth_token),
]
# Custom token authentication
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
})
# JWT Authentication (using djangorestframework-simplejwt)
from rest_framework_simplejwt.authentication import JWTAuthentication
class PostViewSet(viewsets.ModelViewSet):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
```
## Filtering and Search
Implement advanced filtering capabilities.
```python
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']
# Custom filter backend
class IsOwnerFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(owner=request.user)
```
## Pagination
Configure pagination for large datasets.
```python
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
# Cursor pagination for better performance
class PostCursorPagination(CursorPagination):
page_size = 20
ordering = '-created_at'
# Custom pagination
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.
```python
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]
# Custom throttle
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.
```python
from rest_framework.versioning import URLPathVersioning, NamespaceVersioning
# URL path versioning
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
# URLs
urlpatterns = [
path('v1/posts/', PostViewSet.as_view({'get': 'list'})),
path('v2/posts/', PostViewSet.as_view({'get': 'list'})),
]
# Accept header versioning
from rest_framework.versioning import AcceptHeaderVersioning
Ver no GitHub