django-rest-framework

安装量: 111
排名: #7663

安装

npx skills add https://github.com/thebushidocollective/han --skill django-rest-framework

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):
    # 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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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

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

settings.py

REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'myapp.utils.custom_exception_handler' }

Custom exceptions

from rest_framework.exceptions import APIException

class ServiceUnavailable(APIException): status_code = 503 default_detail = 'Service temporarily unavailable' default_code = 'service_unavailable'

Usage

from rest_framework import status from rest_framework.response import Response

class PostViewSet(viewsets.ModelViewSet): def create(self, request): try: # Logic pass except Exception as e: raise ServiceUnavailable(detail=str(e))

Advanced Serializer Patterns

Master complex serialization scenarios.

from rest_framework import serializers

Dynamic field selection

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'

Usage:

serializer = PostSerializer(post, fields=('id', 'title', 'author')) serializer = PostSerializer(post, exclude=('content',))

Serializer method field with context

class PostSerializer(serializers.ModelSerializer): is_liked = serializers.SerializerMethodField() like_count = serializers.SerializerMethodField()

class Meta:
    model = Post
    fields = ['id', 'title', 'is_liked', 'like_count']

def get_is_liked(self, obj):
    request = self.context.get('request')
    if request and request.user.is_authenticated:
        return obj.likes.filter(user=request.user).exists()
    return False

def get_like_count(self, obj):
    return obj.likes.count()

Nested writable serializers

class CommentSerializer(serializers.ModelSerializer): author_name = serializers.CharField(source='author.name', read_only=True)

class Meta:
    model = Comment
    fields = ['id', 'content', 'author', 'author_name']

class PostSerializer(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

def update(self, instance, validated_data):
    comments_data = validated_data.pop('comments', None)

    instance.title = validated_data.get('title', instance.title)
    instance.content = validated_data.get('content', instance.content)
    instance.save()

    if comments_data is not None:
        # Clear existing comments
        instance.comments.all().delete()

        # Create new comments
        for comment_data in comments_data:
            Comment.objects.create(post=instance, **comment_data)

    return instance

Polymorphic serialization

class ContentSerializer(serializers.Serializer): """Base serializer for polymorphic content."""

def to_representation(self, instance):
    if isinstance(instance, Article):
        return ArticleSerializer(instance, context=self.context).data
    elif isinstance(instance, Video):
        return VideoSerializer(instance, context=self.context).data
    elif isinstance(instance, Image):
        return ImageSerializer(instance, context=self.context).data
    return super().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()

    # Filter by query parameters
    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')

    # Optimize based on action
    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'
        )

    return queryset

def get_serializer_class(self):
    if self.action == 'list':
        return PostListSerializer
    elif self.action in ['create', 'update', 'partial_update']:
        return PostWriteSerializer
    return PostSerializer

@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
    """Publish a post."""
    post = self.get_object()
    post.published = True
    post.published_at = timezone.now()
    post.save()

    serializer = self.get_serializer(post)
    return Response(serializer.data)

@action(detail=True, methods=['post'])
def like(self, request, pk=None):
    """Like a post."""
    post = self.get_object()
    user = request.user

    like, created = Like.objects.get_or_create(post=post, user=user)

    if not created:
        like.delete()
        return Response({'status': 'unliked'})

    return Response({'status': 'liked'}, status=status.HTTP_201_CREATED)

@action(detail=False, methods=['get'])
def trending(self, request):
    """Get trending posts."""
    posts = self.get_queryset().annotate(
        like_count=Count('likes')
    ).filter(
        created_at__gte=timezone.now() - timedelta(days=7)
    ).order_by('-like_count')[:10]

    serializer = self.get_serializer(posts, many=True)
    return Response(serializer.data)

@action(detail=False, methods=['get'])
def stats(self, request):
    """Get post statistics."""
    queryset = self.get_queryset()

    stats = {
        'total': queryset.count(),
        'published': queryset.filter(published=True).count(),
        'drafts': queryset.filter(published=False).count(),
        'total_likes': Like.objects.filter(post__in=queryset).count()
    }

    return Response(stats)

@action(detail=True, methods=['get'])
def comments(self, request, pk=None):
    """Get comments for a post."""
    post = self.get_object()
    comments = post.comments.select_related('author').all()

    page = self.paginate_queryset(comments)
    if page is not None:
        serializer = CommentSerializer(page, many=True)
        return self.get_paginated_response(serializer.data)

    serializer = CommentSerializer(comments, many=True)
    return Response(serializer.data)

def perform_create(self, serializer):
    """Save with current user as author."""
    serializer.save(author=self.request.user)

def perform_destroy(self, instance):
    """Soft delete instead of hard delete."""
    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(self, request, view):
    user = request.user

    if not user.is_authenticated:
        return False

    # Check rate limit based on user tier
    if user.tier == 'premium':
        rate = 1000  # requests per day
    else:
        rate = 100

    # Implement rate limiting logic
    cache_key = f'rate_limit_{user.id}'
    current_count = cache.get(cache_key, 0)

    if current_count >= rate:
        return False

    cache.set(cache_key, current_count + 1, timeout=86400)
    return True

Combine multiple permissions

class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer

def get_permissions(self):
    if self.action in ['create', 'update', 'partial_update', 'destroy']:
        permission_classes = [permissions.IsAuthenticated, IsAuthorOrReadOnly]
    elif self.action == 'list':
        permission_classes = [permissions.AllowAny]
    else:
        permission_classes = [IsPublishedOrAuthor]

    return [permission() for permission in 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): # Text filters title = filters.CharFilter(lookup_expr='icontains') title_exact = filters.CharFilter(field_name='title', lookup_expr='exact')

# Date range filters
created_after = filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')
created_before = filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')

# Number range filters
min_views = filters.NumberFilter(field_name='views', lookup_expr='gte')
max_views = filters.NumberFilter(field_name='views', lookup_expr='lte')

# Choice filter
status = filters.ChoiceFilter(choices=(
    ('published', 'Published'),
    ('draft', 'Draft'),
    ('archived', 'Archived')
))

# Multiple choice filter
tags = filters.ModelMultipleChoiceFilter(
    queryset=Tag.objects.all(),
    field_name='tags',
    conjoined=False  # OR instead of AND
)

# Custom method filter
has_comments = filters.BooleanFilter(method='filter_has_comments')

class Meta:
    model = Post
    fields = ['author', 'published', 'category']

def filter_has_comments(self, queryset, name, value):
    if value:
        return queryset.filter(comments__isnull=False).distinct()
    return queryset.filter(comments__isnull=True)

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 configuration
search_fields = [
    'title',
    'content',
    'author__name',
    '=author__username',  # Exact match
    '@description',  # Full-text search (PostgreSQL)
]

# Ordering configuration
ordering_fields = ['created_at', 'updated_at', 'views', 'title']
ordering = ['-created_at']

Custom filter backend

class IsOwnerFilterBackend(filters.BaseFilterBackend): """Filter objects to show only user's own objects."""

def filter_queryset(self, request, queryset, view):
    if not request.user.is_authenticated:
        return queryset.none()

    return queryset.filter(author=request.user)

class MyPostViewSet(viewsets.ReadOnlyModelViewSet): queryset = Post.objects.all() 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):
    return Response({
        'next': self.get_next_link(),
        'previous': self.get_previous_link(),
        'results': data
    })

class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer

def get_pagination_class(self):
    if self.action == 'list':
        return StandardPagination
    elif self.action == 'trending':
        return SmallResultsPagination
    return None

pagination_class = StandardPagination

API Versioning Strategies

Manage API versions effectively.

from rest_framework.versioning import ( URLPathVersioning, NamespaceVersioning, AcceptHeaderVersioning, QueryParameterVersioning )

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
    elif self.request.version == 'v2':
        return PostSerializerV2
    return PostSerializer

URLs configuration

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'), ]

Accept header versioning

REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning', 'DEFAULT_VERSION': 'v1', 'ALLOWED_VERSIONS': ['v1', 'v2', 'v3'], 'VERSION_PARAM': 'version', }

Version-specific serializers

class PostSerializerV1(serializers.ModelSerializer): class Meta: model = Post fields = ['id', 'title', 'content'] # Minimal fields

class PostSerializerV2(serializers.ModelSerializer): author = UserSerializer(read_only=True)

class Meta:
    model = Post
    fields = ['id', 'title', 'content', 'author', 'created_at']

class PostSerializerV3(serializers.ModelSerializer): author = UserSerializer(read_only=True) comments = CommentSerializer(many=True, read_only=True) tags = TagSerializer(many=True, read_only=True)

class Meta:
    model = Post
    fields = '__all__'

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('/api/posts/')
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    self.assertEqual(len(response.data['results']), 2)

def test_update_post(self):
    post = Post.objects.create(title='Old Title', author=self.user)
    data = {'title': 'New Title'}
    response = self.client.patch(f'/api/posts/{post.id}/', data)
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    post.refresh_from_db()
    self.assertEqual(post.title, 'New Title')

def test_delete_post(self):
    post = Post.objects.create(title='Test', author=self.user)
    response = self.client.delete(f'/api/posts/{post.id}/')
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
    self.assertEqual(Post.objects.count(), 0)

def test_unauthenticated_access(self):
    self.client.force_authenticate(user=None)
    response = self.client.post('/api/posts/', {})
    self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

def test_permission_denied(self):
    other_user = User.objects.create_user('other', password='pass')
    post = Post.objects.create(title='Test', author=other_user)

    response = self.client.patch(f'/api/posts/{post.id}/', {'title': 'Hacked'})
    self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_filtering(self):
    Post.objects.create(title='Python Post', author=self.user, published=True)
    Post.objects.create(title='Django Post', author=self.user, published=False)

    response = self.client.get('/api/posts/?published=true')
    self.assertEqual(len(response.data['results']), 1)
    self.assertEqual(response.data['results'][0]['title'], 'Python Post')

def test_search(self):
    Post.objects.create(title='Python Tutorial', author=self.user)
    Post.objects.create(title='Django Guide', author=self.user)

    response = self.client.get('/api/posts/?search=Python')
    self.assertEqual(len(response.data['results']), 1)

def test_ordering(self):
    post1 = Post.objects.create(title='A Post', author=self.user)
    post2 = Post.objects.create(title='Z Post', author=self.user)

    response = self.client.get('/api/posts/?ordering=title')
    self.assertEqual(response.data['results'][0]['title'], 'A Post')

    response = self.client.get('/api/posts/?ordering=-title')
    self.assertEqual(response.data['results'][0]['title'], 'Z Post')

def test_pagination(self):
    for i in range(25):
        Post.objects.create(title=f'Post {i}', author=self.user)

    response = self.client.get('/api/posts/')
    self.assertEqual(len(response.data['results']), 20)  # Default page size
    self.assertIsNotNone(response.data['next'])

def test_custom_action(self):
    post = Post.objects.create(title='Test', author=self.user)
    response = self.client.post(f'/api/posts/{post.id}/publish/')
    self.assertEqual(response.status_code, status.HTTP_200_OK)

    post.refresh_from_db()
    self.assertTrue(post.published)

Testing with APIRequestFactory

class PostViewSetTestCase(APITestCase): def setUp(self): self.factory = APIRequestFactory() self.user = User.objects.create_user('testuser', password='testpass')

def test_list_action(self):
    request = self.factory.get('/api/posts/')
    request.user = self.user

    view = PostViewSet.as_view({'get': 'list'})
    response = view(request)

    self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_create_action(self):
    data = {'title': 'Test', 'content': 'Content'}
    request = self.factory.post('/api/posts/', data)
    request.user = self.user

    view = PostViewSet.as_view({'post': 'create'})
    response = view(request)

    self.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()

    # Optimize based on action
    if self.action == 'list':
        # Minimal fields for list view
        queryset = queryset.select_related('author').only(
            'id', 'title', 'created_at', 'author__name'
        )
    elif self.action == 'retrieve':
        # Full data for detail view
        queryset = queryset.select_related(
            'author', 'category'
        ).prefetch_related(
            'comments__author',
            'tags'
        )

    return queryset

# Cache list view for 5 minutes
@method_decorator(cache_page(60 * 5))
@method_decorator(vary_on_headers('Authorization'))
def list(self, request, *args, **kwargs):
    return super().list(request, *args, **kwargs)

Use only() and defer() in serializers

class PostListSerializer(serializers.ModelSerializer): author_name = serializers.CharField(source='author.name', read_only=True)

class Meta:
    model = Post
    fields = ['id', 'title', 'author_name', 'created_at']

class PostDetailSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) comments = CommentSerializer(many=True, read_only=True)

class Meta:
    model = Post
    fields = '__all__'

Batch requests

from rest_framework.response import Response from rest_framework import status

class BatchCreateMixin: """Allow batch creation of objects."""

def create(self, request, *args, **kwargs):
    many = isinstance(request.data, list)

    if not many:
        return super().create(request, *args, **kwargs)

    serializer = self.get_serializer(data=request.data, many=True)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)

    return Response(serializer.data, status=status.HTTP_201_CREATED)

class PostViewSet(BatchCreateMixin, viewsets.ModelViewSet): queryset = Post.objects.all() 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(
    parameters=[
        OpenApiParameter(
            name='author',
            type=OpenApiTypes.INT,
            location=OpenApiParameter.QUERY,
            description='Filter by author ID'
        ),
        OpenApiParameter(
            name='published',
            type=OpenApiTypes.BOOL,
            location=OpenApiParameter.QUERY,
            description='Filter by published status'
        )
    ]
)
def list(self, request, *args, **kwargs):
    """List posts with optional filtering."""
    return super().list(request, *args, **kwargs)

settings.py

REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', }

SPECTACULAR_SETTINGS = { 'TITLE': 'My API', 'DESCRIPTION': 'API for managing posts and comments', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, }

urls.py

from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

urlpatterns = [ path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), ]

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 Django REST Framework Documentation DRF Serializers Guide DRF ViewSets DRF Authentication DRF Testing

返回排行榜